2502: 清理雪道
Time Limit: 10 Sec
Memory Limit: 128 MB
Submit: 1218
Solved: 663
[ Submit][ Status][ Discuss]
Description
滑雪场坐落在
FJ
省西北部的若干座山上。
从空中鸟瞰,滑雪场可以看作一个有向无环图,每条弧代表一个斜坡(即雪道),弧的方向代表斜坡下降的方向。
你的团队负责每周定时清理雪道。你们拥有一架直升飞机,每次飞行可以从总部带一个人降落到滑雪场的某个地点,然后再飞回总部。从降落的地点出发,这个人可以顺着斜坡向下滑行,并清理他所经过的雪道。
由于每次飞行的耗费是固定的,为了最小化耗费,你想知道如何用最少的飞行次数才能完成清理雪道的任务。
Input
输入文件的第一行包含一个整数
n
(2 <= n <= 100) –
代表滑雪场的地点的数量。接下来的
n
行,描述
1~n
号地点出发的斜坡,第
i
行的第一个数为
mi
(0 <= mi < n)
,后面共有
mi
个整数,由空格隔开,每个整数
aij
互不相同,代表从地点
i
下降到地点
aij
的斜坡。每个地点至少有一个斜坡与之相连。
Output
输出文件的第一行是一个整数
k
–
直升飞机的最少飞行次数。
Sample Input
8
1 3
1 7
2 4 5
1 8
1 8
0
2 6 5
0
Sample Output
4
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
#include
#include
#include
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 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;
}