题目描述 Description
由于人类对自然的疯狂破坏,人们意识到在大约2300年之后,地球不能再居住了,于是在月球上建立了新的绿地,以便在需要时移民。令人意想不到的是,2177年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。
现有n个太空站处于地球与月球之间(编号1..n),m艘公共交通太空船在其中来回穿梭,每个太空站Si可容纳无限的人,每艘太空船pi只可容纳Hpi人。对于每一艘太空船pi,将周期性地停靠一系列的太空站(Si1,Si2…Sir),如:(1,3,4)表示停靠太空站1 3 4 1 3 4 1 3 4 …。 任一艘太空船从任一个太空站驶往另一个任意的太空站耗时为1。人只能在太空船停靠太空站(或地球、月球)时上船或下船。初始时的人全在地球上,太空船全在初始站(太空船pi处于Si1),目标是让所有的人尽快地全部转移到月球上。
输入描述 Input Description
文件第一行为三个正整数 n(太空站个数)、 m(太空船个数)、 k(需要运送的地球上的人的个数),其中 1<=m<=13, 1<=n<=20, 1<=k<=50。
接下来的m(wiki原题此处描述错误)行给出了太空船的信息,第i+1行说明太空船pi,此行第一个数表示pi可容纳的人数Hpi,第二个数表示pi停靠一个周期的太空站个数r,1<=r<=n+2, 随后r个数便是停靠的太空站的编号(Si1,Si2,…,Sir), 地球用0表示,月球用-1表示。0时刻时,所有太空船都在初始站,随后开始运行,在时刻1,2,3…等正点时刻各艘太空船停靠相应的太空站,即人只有在0,1,2…等正点时刻才能上下太空船。
样例输入 Sample Input
2 2 1
1 3 0 1 2
1 3 1 2 -1 (坑爹的wiki此处的负号用了全角!!让我找了半天错)
样例输出Sample Output
5
数据范围及提示Data Size & Hint
1<=m<=13, 1<=n<=20, 1<=k<=50。
分析
该题不愧为CTSC国家队选拔赛题目,是一道综合了爱因斯坦狭义相对论中四维时空观和网络流的信息物理学题目,在题面中甚至对300年后的世界作出了预言(预言的甚至精确到了年),最终要求选手利用网络流对太空移民的可能性进行探讨。
此题是明显到一眼就能看出的网络流,地球是源,月球是汇,边的容量是太空船载客量。但是由于太空船载客的时间影响最终结果,所以不能直接建图。这里我们引入狭义相对论中的四维空间理论:可以思考,对于太空船mi,1秒前在U太空站,1秒后在V太空站,按常理可直接建U到V容量h[i]的边,但是!!用时空思维来分析这道题,1秒后的V和1秒前的V真的是同一个太空站吗?爱因斯坦告诉我们,我们生活的空间是四维空间,第四维度是时间,所以V[t]与V[t-1]并不是完全相同的太空站,就如同身后1米的篮球与身前1米的篮球不是完全相同的篮球,所以也就不能只把太空站建作一个点……
其实以上内容纯属扯淡=。=不过确实体现了本题的思想即按时间拆点。。构建网络的元素只有结点和边,所以要实现对时间的控制可以从边或结点上进行。拆边较繁琐,相比之下拆点更加直观。对于三维世界的每个点V[i](不好意思又at到爱因斯坦了)拆成V[i][1].....V[i][t],源S地球和汇T月球为了方便最好也拆。
然后根据太空船的循环往返规律建边。如,太空船m[1]的航程是1->2->3->-1,则建边V[1][0]->V[2][1],用以表示太空船从第0秒时的V1航行到第1秒时的V2。同理建V[2][1]->V[3][2],V[3][2]->V[-1][3],容量为h[1]。又因为每个太空站和地球每一时刻都能允许无限多的人逗留,所以对每个点再建边V[i][t]->V[i][t+1],容量无穷大(该边见证时间一分一秒的流逝0,0第四维度的不断向前)。另附加超级源super_S和超级汇super_T,因为地球只能向外输送k个人所以从s_S向第一个地球连一条边,容量为k;再从每个t的T向s_T连一条容量为无限大的边来收集流量。
这样建图就能控制时间了(不是指暂停时间之类=。=),按上述方法建t层结点后求最大流,如果maxflow<k即在t秒内不可能全部送达;如果maxflow=k则可以送达。建立新一层结点后可直接进行增广,所以直接while循环每次t++建立新一层结点然后求最大流,并累加最大流值,当maxflow=k的时候退出循环,此时t就是答案。对于无解的情况,把t卡到一两百就可以。
另外因为在求最大流的过程中图是不断被更新的,isap的时候需要注意细节。例如每次增加一层结点isap的tot变量要相应增加;新增结点的距离标号应为0,所以记录距离标号数量的数组num[0]也要对应增加。每层结点共n+2个,找不到好方法所以我写了个弱弱的函数把二维的结点标号转化为一维。。
代码
//wikioi AC 245ms蒟蒻代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=30100,M=200005,inf=999999999;
int s_S=0,s_T=1,S,T,Time,d[N],nums[N],tot;
// 超级源 s_S=0 / s_T = 1
int n,m,k,leftK,h[30],round[30][30];
struct edge{int u,v,w,cost; edge *next,*other;}e[M],*P=e,*point[N],*last[N];
queue<int>Qe;
int num(int a,int t)//计算节点编号 V(a,t)
{
// 对每一Time : S=0,T=n+1; total=n+2
// ->eg: Time=0时 S= 2 + 0; T= 2 + n + m + 1;
if(a==-1) return num(T,t);
return (n+2)*t+a+2;
}
inline void add_edge(int x,int y,int w)
{
edge *Q=++P;++P;
P->u = x; P->v = y; P->w = w; P->next = point[x]; point[x] = P;
Q->u = y; Q->v = x; Q->w = 0; Q->next = point[y]; point[y] = Q;
P->other = Q; Q->other = P;
}
int dfs(int now, int flow) {
if (now == s_T) return flow;
int ans = 0;
for (edge *j = last[now]; j; j = j -> next)
if (j -> w && d[j -> u] == d[j -> v] + 1) {
last[now] = j;
int p = dfs(j -> v, min(flow - ans, j -> w));
j -> w -= p; j -> other -> w += p; ans += p;
if (ans == flow) return flow;
}
if (d[s_S] >= tot) return ans;
if (--nums[d[now]] == 0) d[s_S] = tot;
++nums[++d[now]]; last[now] = point[now];
return ans;
}
void freshGraph(int time)//建新一层图
{
tot+=(n+2);
nums[0]+=(n+2);
if (time==0) add_edge(s_S,num(S,time),k);
add_edge(num(T,time),s_T,inf);
if(time)
{
for(int i=S;i<=n;i++)
add_edge(num(i,time-1),num(i,time),inf);
add_edge(num(T,time-1),num(T,time),inf);
for(int i=1;i<=m;i++)
{
int site = (time+1) % round[i][0],old_site = time % round[i][0];
if(!site) site = round[i][0]; if(!old_site) old_site = round[i][0];
site = round[i][site]; old_site = round[i][old_site];
add_edge(num(old_site,time-1),num(site,time),h[i]);
}
}
for(int i=num(S,time);i<=num(T,time);i++)
last[i]=point[i];
last[s_S]=point[s_S];
}
int main()
{
cin>>n>>m>>k;
S=0;T=n+1;leftK=k;
tot=2;
for(int i=1;i<=m;i++)
{
cin>>h[i]>>round[i][0];
for(int j=1;j<=round[i][0];j++)
cin>>round[i][j];
}
Time=-1;
memset(nums,0,sizeof(nums));
memset(d,0,sizeof(d));
nums[0]=2;
while(Time++<200)
{
freshGraph(Time);
memset(nums,0,sizeof(nums));
memset(d,0,sizeof(d));
int flow=0;
while(d[s_S]<tot) flow+=dfs(s_S,inf);
leftK-=flow;
if(!leftK)
{
cout<<Time<<endl;
return 0;
}
}
cout<<0<<endl;
}
思考
看到网上有人发现,对于同一组数据k不同时的答案符合一定规律,但没有证明为什么。仔细想想既然飞船的往返是有规律的,那一定时间内输送到月球的人流量应该也是有规律的。例如对于样例的数据,修改k值后收集答案有:
k= 1 2 3 4 5
ans= 5 8 11 14 17
所以我想如果原题k范围更大,大到图无法完全存储时正解就应该是在数据提供的图下求解k较小时的答案,然后根据规律得出k较大时的答案。