飞扬的小鸟

题目描述

Flappy Bird是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
现在小鸟们遇到了一个难题,他们遇到了一堵巨大的墙,墙上仅有m个洞供他们通过,由于小鸟们的体型不同且墙上洞的形状也不同,所以每种体型的鸟通过每个洞的时间都不同,鸟的体型共有n种,第i种体型的鸟通过第j个洞需要的时间记为T(i,j),且一个洞必须前一只鸟通过之后后一只鸟才能开始通过。
从时刻0开始,鸟开始通过,而每一只鸟的等待时间为从时刻0到自己已经通过洞的时间。现在知道了第i种体型的鸟有pi只,请求出使所有鸟都通过墙的最少的等待时间之和。

数据范围,n<=40,m<=100, p<=800,T(i,j)<=1000
这种代价复杂麻烦的题,一般只能上网络流

考虑如果一只种类为i的鸟倒数第一个通过洞j,那么对于总代价的贡献为T(i,j),倒数第k则贡献为T(i,j)*k。
根据这个,我们就可以建图了。
源点向每一种鸟连边,对于第i种鸟,连一条流量为p[i]费用为0的边。
对于每一个洞,我们拆成 p个洞,也就是总共拆成了m* p的洞,每个洞向汇点连一条流量为1费用为0的边。
然后每一种鸟向每一个洞都连一条边。第i种鸟,向第(k-1)*m+j个洞(这里指的是拆后的洞)连一条流量为1,费用为T(i,j)*k的边,这条边若满流则表示的意义为一只种类为i的鸟倒数第k个通过了洞,且该洞为第j个洞(这里指原来的洞)。
然而点数最大为40+100*800,边数最大为40+100*80+40*100*80,直接做显然会超时。
注意到若第(k-1)*m+j个洞向汇点的边没有满流,则第k*m+j个洞显然也不会满流(即还用不到),所以可以动态加边。

代码

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define ll long long
using namespace std;
const int maxn=3500000+5;
const int ma=100000+5;
int w[maxn],i,j,n,m,num,p[maxn],st,en,sump,t[45][105],c[maxn],k[ma],next[maxn],g[maxn],re[maxn],flow[maxn],now[maxn],last;
int bz[maxn],b1,d[maxn],ans;
void add(int x,int y,int z,int co){
    next[++num]=k[x];
    k[x]=num;g[num]=y;flow[num]=z,c[num]=co;
    re[num]=++num,re[num]=num-1;
    next[num]=k[y];k[y]=num;g[num]=x;c[num]=-co;
}
int dfs(int x,int las,int a){
    if (x==en){
        ans+=d[1];
        last=las;
        return a;
    }
    bz[x]=b1;
    int i=now[x];
    while (i){
        int y=g[i];
        if ((bz[y]!=b1&&flow[i])&&(d[x]==d[g[i]]+c[i])){
            int j=dfs(y,x,min(flow[i],a));
            if (j){
                now[x]=i;
                flow[i]-=j;
                flow[re[i]]+=j;
                return j;
            }
        } 
        i=next[i];
    }
    now[x]=0;
    return 0;
}
bool update(){
    int z=100000000,i,j;
    fo(j,1,en) if (bz[j]==b1){
        int x=j,i=k[x];
        while (i){
            int y=g[i];
            if (bz[y]!=b1&&flow[i]) z=min(z,d[g[i]]+c[i]-d[x]);
            i=next[i];
        }
    }
    if (z==100000000) return 0;
    fo(j,1,en) if (bz[j]==b1) d[j]+=z; 
    return 1;
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,n) scanf("%d",&p[i]),sump+=p[i];
    fo(i,1,n) fo(j,1,m) scanf("%d",&t[i][j]);
    st=1,en=1+n+m*sump;
    fo(i,1,n) add(st,i+1,p[i],0);
    if (m==1) {
        num=0;fo(i,1,n) fo(j,1,p[i]) w[++num]=t[i][1];
        sort(w+1,w+1+sump);
        fo(i,1,sump) ans+=w[i]*(sump-i+1);
        printf("%d\n",ans);
        return 0;
    }
    fo(i,1,m) {
        add(i+1+n,en,1,0);
        fo(j,1,n) add(j+1,i+1+n,1,t[j][i]);
    }
    while (1){
        b1++;
        fo(i,1,en) now[i]=k[i];
        while (dfs(1,1,1)){
            b1++;
            int w=(last-n-1)%m,w1=(last-n-2)/m+2;if (w==0) w=m;
            add(last+m,en,1,0);
            fo(i,1,n) add(i+1,last+m,1,t[i][w]*w1);
        } 
        if (!update())break;
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值