题解:P15246 [WC2026] 猫和老鼠
更好的阅读体验
qoj 7,非常困难!
首先我们考虑以位置为
那么我们考虑两条线段要以怎样放置才能阻止老鼠通过这两条线。为了方便,我们现在将这个平面旋转
现在考虑两条线段如何排布可以使老鼠无法通过。
- 若两条线段相交,则显然老鼠无法通过两条线段得交点。
- 若两条线段都与
y 轴平行,且较左的线段的上端点y 坐标\ge 较右的线段的下端点y 坐标,由于老鼠不能向下走,因此这种情况下,两条线段之间的空隙老鼠无法通过。 - 若两条线段都与
x 轴平行,且较下的线段的右端点x 坐标\ge 较上线段左端点的x 坐标,由于老鼠不能往左走,因此这种情况下,两条线段之间的空隙老鼠无法通过。
我们先考虑
- 若一条线段与
y = x + 2m 相交,则让源点S 向这条线段连有向边。 - 若一条线段与
y = x 相交,则让这条线段向汇点T 连有向边。 - 如果两条线段
i, j 满足i, j 相交,则i, j 互相连有向边。 - 若
i, j 都与y 轴平行,且i, j 这两条线段能够阻挡老鼠通过,则i 向j 连有向边,其中i 的x 坐标< j 的x 坐标。 - 若
i, j 都与x 轴平行,且i, j 这两条线段能够阻挡老鼠通过,则i 向j 连有向边,其中i 的y 坐标< j 的y 坐标。
最大流(最小割)等价于对偶图上的最短路。注意这里是点权最短路。所以
接下来考虑
首先和
对于线段中间的边,我们考虑一个很高妙的建边方式:对于一个出点,将这个出点向该点左上方(包括正左,正上)的所有入点连边。
有何道理?如上图,绿色是按照上述规则新加入的边,容易验证,三种线段之间的连边方式,都可以使用上述的连边规则统一实现!
但是现在仍然无法通过,因为我们的总边数是
具体地,现在我们将所有出点和入点放在一起按照
那么我们把
那么现在最小费用最大流的点数和边数都是
#include<bits/stdc++.h>
//#include "game.h"
#define endl '\n'
#define N 1000006
#define M 6000006
using namespace std;
using i64=long long;
struct MCMF_Graph {
struct Node {
int u; i64 d;
friend bool operator >(Node x,Node y) {return x.d>y.d;}
};
i64 dis[N],h[N];
int tot,ecnt,head[N],s,t,vis[N];
struct Edge {int to,next; i64 w,c;} E[M],fr[M];
void addedge(int u,int v,i64 w,i64 c) {E[ecnt]={v,head[u],w,c},head[u]=ecnt++;}
void addflow(int u,int v,i64 w,i64 c) {addedge(u,v,w,c),addedge(v,u,0,-c);}
void init(int Tot,int S,int T)
{
for(int i=1;i<=tot;i++)dis[i]=h[i]=head[i]=vis[i]=0;
for(int i=1;i<=ecnt;i++)E[i]=fr[i]={0,0,0,0};
tot=Tot,s=S,t=T,ecnt=2;
}
int dijkstra()
{
priority_queue<Node,vector<Node>,greater<Node> > q;
for(int i=0;i<=tot;i++)vis[i]=0,dis[i]=2e15;
q.push({s,0}),dis[s]=0;
while(q.size())
{
auto [u,d]=q.top(); q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=E[i].next)
{
int v=E[i].to; i64 w=h[u]-h[v]+E[i].c;
if(E[i].w&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w,fr[v].next=i,fr[v].to=u;
if(!vis[v])q.push({v,dis[v]});
}
}
}
return dis[t]<2e15;
}
pair<i64,i64> get_flow()
{
i64 maxflow=0,mincost=0;
while(dijkstra())
{
i64 fl=1e18;
for(int i=1;i<=tot;i++)h[i]+=dis[i];
for(int i=t;i^s;i=fr[i].to)fl=min(fl,E[fr[i].next].w);
for(int i=t;i^s;i=fr[i].to)
E[fr[i].next].w-=fl,E[fr[i].next^1].w+=fl;
maxflow+=fl,mincost+=fl*h[t];
}
return {maxflow,mincost};
}
} G;
int tot,bn,suf_id[N];
vector<int> vec[N];
i64 b[N];
struct Node {i64 x,y; int id;} p[N];
void init(int c,int y) {}
void cdq(int l,int r)
{
if(l==r)return;
int mid=l+r>>1; cdq(l,mid),cdq(mid+1,r),bn=0;
for(int i=l;i<=mid;i++)b[++bn]=p[i].y;
sort(b+1,b+1+bn),bn=unique(b+1,b+1+bn)-b-1;
for(int i=1;i<=bn;i++)vec[i].clear();
for(int i=l,t;i<=mid;i++)if(p[i].id&1)
t=lower_bound(b+1,b+1+bn,p[i].y)-b,vec[t].push_back(p[i].id);
for(int i=bn;i;i--)
{
suf_id[i]=++tot;
if(i!=bn)G.addflow(suf_id[i],suf_id[i+1],2e10,0);
for(int j:vec[i])G.addflow(suf_id[i],j,2e10,0);
}
for(int i=mid+1,t;i<=r;i++)if(!(p[i].id&1)&&p[i].y<=b[bn])
t=lower_bound(b+1,b+1+bn,p[i].y)-b,G.addflow(p[i].id,suf_id[t],2e10,0);
}
i64 game(int n,int m,int k,vector<int> a,vector<int> b,vector<int> t,vector<int> w)
{
int S=n*2+1,fake_S=n*2+2,T=n*2+3;
G.init(0,S,T),tot=n*2+3;
G.addflow(S,fake_S,k,0);
for(int i=0;i<n;i++)
{
i64 st_x=t[i]-a[i],st_y=t[i]+a[i];
i64 ed_x=((i64)abs(a[i]-b[i]))+t[i]-b[i],ed_y=((i64)abs(a[i]-b[i]))+t[i]+b[i];
// 奇数: in; 偶数: out
if(st_x==ed_x)
{
if(st_y<ed_y)swap(st_x,ed_x),swap(st_y,ed_y);
p[i*2+1]={st_x,st_y,i*2+1};
p[i*2+2]={ed_x,ed_y,i*2+2};
} else {
if(st_x>ed_x)swap(st_x,ed_x),swap(st_y,ed_y);
p[i*2+1]={st_x,st_y,i*2+1};
p[i*2+2]={ed_x,ed_y,i*2+2};
}
if(st_y==st_x+2*m)G.addflow(fake_S,i*2+1,1,0);
if(ed_y==ed_x)G.addflow(i*2+2,T,1,0);
G.addflow(i*2+1,i*2+2,1,w[i]);
}
sort(p+1,p+1+n*2,[](Node x,Node y) {
if(x.x!=y.x)return x.x<y.x;
if(x.y!=y.y)return x.y>y.y;
int typ_x=x.id&1,typ_y=y.id&1;
return typ_x>typ_y;
}),cdq(1,n*2),G.tot=tot;
auto [maxflow,mincost]=G.get_flow();
if(maxflow<k)return -1;
return mincost;
}