2502: 清理雪道
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1218 Solved: 663
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
Sample Output
DAG用最少的路径覆盖所有的点:最长反链(复杂度O(n^3))
而这题是DAG用最少的路径覆盖所有的边,如果把边当成点的话复杂度就为O(m^3)会TLE
考虑带上下界的网络流
看了网上所有题解都是求两次网络流再相减,其实根本没有必要,只要1次就OK了,并且建的边的也少很多
建边方式如下:
初始化ans=0
①存在边(u, v)那么u到v连接一条流量无穷大的边
②对于点u,和一般带下界的网络流处理方法一样,设u点入度为in[u],出度为out[u],x=in[u]-out[u]
如果x<0,那么从u向汇点T连接一条流量为|x|的边,中间ans += |x|
如果x>0,那么从源点S向u连接一条流量为x的边
最后答案就是ans - S到T的最大流
OK!
为什么这是对的呢?给出一个简要证明
一个很简单但错误的思路是如果一个点的入度<出度,那么答案就一定要加上这个点的出度-入度
也就是说ans = ∑max(out[u]-in[u], 0) (u∈G)(正好就是上面红色的ans!)
很好想到但其实是有反例的如下图:
(自己脑补)
而考虑带上下界网络流方法的证明:对于一条下界为1的边(u, v),操作②就同等于直接把这条边删掉,只要保证最后u点会多出1点流量,v点少1点流量就OK,可是怎么样才能保证呢?那就连一条u到汇点容量为1的边,再连一条源点到v点的边并保证这两条边满流!
而刚好如果这两条边满流了正说明可以在不引入额外流量的情况下满足点u和点v互补,从而流量总和+1,ans-1
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
int n, m, cnt, S, T, head[2005], h[2005], cur[2005], in[2005], out[2005];
typedef struct
{
int to, next;
int flow;
}Road;
Road G[20005];
void Add(int u, int v, int flow)
{
cnt++;
G[cnt].next = head[u];
head[u] = cnt;
G[cnt].to = v;
G[cnt].flow = flow;
}
int Jud()
{
int now, i;
queue<int> q;
memset(h, -1, sizeof(h));
q.push(S);
h[S] = 0;
while(q.empty()==0)
{
now = q.front();
q.pop();
for(i=head[now];i!=0;i=G[i].next)
{
if(G[i].flow && h[G[i].to]==-1)
{
h[G[i].to] = h[now]+1;
q.push(G[i].to);
}
}
}
if(h[T]!=-1)
return 1;
return 0;
}
int Sech(int x, int flow)
{
int w, used, i;
if(x==T)
return flow;
used = 0;
for(i=cur[x];i!=0;i=G[i].next)
{
if(h[G[i].to]==h[x]+1)
{
w = Sech(G[i].to, min(flow-used, G[i].flow));
G[i].flow -= w;
G[i^1].flow += w;
if(G[i].flow)
cur[x] = i;
used += w;
if(used==flow)
return flow;
}
}
if(used==0)
h[x] = -1;
return used;
}
int Dinic()
{
int i, flow = 0;
while(Jud())
{
for(i=S;i<=T;i++)
cur[i] = head[i];
flow += Sech(S, 1<<25);
}
return flow;
}
int main(void)
{
int n, i, m, x, ans;
cnt = 1;
scanf("%d", &n);
for(i=1;i<=n;i++)
{
scanf("%d", &m);
while(m--)
{
scanf("%d", &x);
in[x]++, out[i]++;
Add(i, x, 1<<25);
Add(x, i, 0);
}
}
S = 0, T = n+1;
ans = 0;
for(i=1;i<=n;i++)
{
if(in[i]-out[i]<0)
{
Add(i, T, out[i]-in[i]);
ans += out[i]-in[i];
Add(T, i, 0);
}
else
{
Add(S, i, in[i]-out[i]);
Add(i, S, 0);
}
}
printf("%d\n", ans-Dinic());
return 0;
}