飞扬的小鸟
【问题描述】
Flappy Bird是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机
屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果
小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
现在小鸟们遇到了一个难题,他们遇到了一堵巨大的墙,墙上仅有m个洞供
他们通过,由于小鸟们的体型不同且墙上洞的形状也不同,所以每种体型的鸟通
过每个洞的时间都不同,鸟的体型共有n种,第i种体型的鸟通过第j个洞需要的
时间记为T(i,j),且一个洞必须前一只鸟通过之后后一只鸟才能开始通过。
从时刻0开始,鸟开始通过,而每一只鸟的等待时间为从时刻0到自己已经通
过洞的时间。现在知道了第i种体型的鸟有pi只,请求出使所有鸟都通过墙的最
少的等待时间之和。
【输入格式】
第1行包含两个正整数n和m,表示鸟的体型的种数和墙洞的数量。
第2行包含n个正整数,其中第i个数为pi,表示点第i种体型的鸟的只数。
接下来有n行,每行包含m个非负整数,这n行中的第i行的第j个数为t(i,j),
表示第i种体型的鸟通过第j个墙洞所需的时间。输入文件中每行相邻的两个数之
间均由一个空格隔开,行末均没有多余空格。
【输出格式】
输出仅一行包含一个整数,为总等待时间的最小值。
【输入输出样例】
全国信息学奥林匹克联赛( NOIP2016 ) 复赛模拟 提高组
第 5 页 共 5 页
【数据范围】
每组数据的n、 m和p值如下:
对于 100%的数据, n<=40,m<=100,p<=800,t(i,j)<=1000(p 为鸟的总只数)
【问题描述】
Flappy Bird是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机
屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果
小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
现在小鸟们遇到了一个难题,他们遇到了一堵巨大的墙,墙上仅有m个洞供
他们通过,由于小鸟们的体型不同且墙上洞的形状也不同,所以每种体型的鸟通
过每个洞的时间都不同,鸟的体型共有n种,第i种体型的鸟通过第j个洞需要的
时间记为T(i,j),且一个洞必须前一只鸟通过之后后一只鸟才能开始通过。
从时刻0开始,鸟开始通过,而每一只鸟的等待时间为从时刻0到自己已经通
过洞的时间。现在知道了第i种体型的鸟有pi只,请求出使所有鸟都通过墙的最
少的等待时间之和。
【输入格式】
第1行包含两个正整数n和m,表示鸟的体型的种数和墙洞的数量。
第2行包含n个正整数,其中第i个数为pi,表示点第i种体型的鸟的只数。
接下来有n行,每行包含m个非负整数,这n行中的第i行的第j个数为t(i,j),
表示第i种体型的鸟通过第j个墙洞所需的时间。输入文件中每行相邻的两个数之
间均由一个空格隔开,行末均没有多余空格。
【输出格式】
输出仅一行包含一个整数,为总等待时间的最小值。
【输入输出样例】
bird.in | bird.out |
3 2 3 1 1 5 7 3 6 8 9 | 47 |
全国信息学奥林匹克联赛( NOIP2016 ) 复赛模拟 提高组
第 5 页 共 5 页
【数据范围】
每组数据的n、 m和p值如下:
对于 100%的数据, n<=40,m<=100,p<=800,t(i,j)<=1000(p 为鸟的总只数)
序号 | n= | m= | p= |
1 | 5 | 5 | 10 |
2 | 40 | 1 | 400 |
3 | 40 | 2 | 300 |
4 | 40 | 40 | 40 |
5 | 5 | 40 | 100 |
6 | 10 | 50 | 200 |
7 | 20 | 60 | 400 |
8 | 40 | 80 | 600 |
9 | 40 | 100 | 800 |
10 | 40 | 100 | 800 |
题解:
一个费用流的经典模型,对于这种问题,我们可以认为,对于一个洞,最后一个的鸟通
过的时间,只有他一个在等待,总答案贡献为 Time*1,倒数第二只鸟为 Time*2,一次类
推,于是我们可以建立出一张费用流的图
将源点连向 n 种鸟,费用 0,流量为这种鸟的 shuliang
将 m 个洞分别拆成 p 份,将这 m*p 个点连向汇点,费用 0,流量 1
将 n 种鸟分别连向 m*p 个洞的点,费用为 f(m,n)*1,f(m,n)*2,f(m,n)*3,……
,f(m,n)*p,流量为 1
然而 m*p 有 80000,n 有 40,连边有 3200000 条,所以需要优化,我们发现,一个洞的
f(m,n)*1 没有满流时,f(m,n)*2 一定没有用,于是我们可以一开始只将鸟连向每个洞的第
一个点,当一个点流满后,再在下一个点上建立新边,动态加边就可以过这道题了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int oo=1e9; //无穷
const int mm=11111111; //边
const int mn=888888; //点
int node,src,dest,edge;
int ver[mm],flow[mm],cost[mm],nex[mm];
int head[mn],dis[mn],p[mn],q[mn],vis[mn];
/**这些变量基本与最大流相同,增加了
cost 表示边的费用,
p 记录可行流上节点对应的反向边
*/
inline void prepare(int _node,int _src,int _dest) //预处理 点的个数 起点 终点
{
node=_node,src=_src,dest=_dest;
for(int i=0; i<node; i++)head[i]=-1,vis[i]=0;
edge=0;
}
void addedge(int u,int v,int f,int c)
{
ver[edge]=v,flow[edge]=f,cost[edge]=c,nex[edge]=head[u],head[u]=edge++;
ver[edge]=u,flow[edge]=0,cost[edge]=-c,nex[edge]=head[v],head[v]=edge++;
}
/**以上同最大流*/
/**spfa 求最短路,并用 p 记录最短路上的边*/
bool spfa()
{
int i,u,v,l,r=0,tmp;
for(i=0; i<node; ++i)dis[i]=oo;
dis[q[r++]=src]=0;
p[src]=p[dest]=-1;
for(l=0; l!=r; (++l>=mn)?l=0:l)
for(i=head[u=q[l]],vis[u]=0; i>=0; i=nex[i])
if(flow[i]&&dis[v=ver[i]]>(tmp=dis[u]+cost[i]))
{
dis[v]=tmp;
p[v]=i^1;
if(vis[v]) continue;
vis[q[r++]=v]=1;
if(r>=mn)r=0;
}
return p[dest]>-1;
}
int num[45],cot[45][105],n,m,sum;
/**源点到汇点的一条最短路即可行流,不断的找这样的可行流*/
int SpfaFlow()
{
int i,ret=0,delta;
while(spfa())
{
for(i=p[dest],delta=oo; i>=0; i=p[ver[i]])
if(flow[i^1]<delta)delta=flow[i^1];
for(i=p[dest]; i>=0; i=p[ver[i]])
flow[i]+=delta,flow[i^1]-=delta;
ret+=delta*dis[dest];
int pt=ver[p[dest]];
int x=(pt-n-1)%sum+1,y=(pt-n+sum-1)/sum;
if(x==sum)break;
for(int i=1;i<=n;i++)addedge(i,n+(y-1)*sum+x+1,1,(x+1)*cot[i][y]);
}
return ret;
}
int main(){
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&num[i]),sum+=num[i];
prepare(n+sum*m+2,0,n+sum*m+1);
for(i=1;i<=n;i++){
addedge(0,i,num[i],0);
for(j=1;j<=m;j++)scanf("%d",&cot[i][j]),addedge(i,n+(j-1)*sum+1,1,cot[i][j]);
}
for(i=1;i<=sum;i++){
for(j=1;j<=m;j++)addedge(n+(j-1)*sum+i,n+sum*m+1,1,0);
}
printf("%d\n",SpfaFlow());
return 0;
}
/*
3 2
3 1 1
5 7
3 6
8 9
47
*/