NOIP 提高组 飞扬的小鸟

9 篇文章 0 订阅
3 篇文章 0 订阅

Description

Flappy Bird是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。

现在小鸟们(n只)遇到了一个难题,他们遇到了一堵巨大的墙,墙上仅有m个洞供他们通过,由于小鸟们的体型不同且墙上洞的形状也不同,所以每种体型的鸟通过每个洞的时间都不同,鸟的体型共有n种,第i种体型的鸟通过第j个洞需要的时间记为T(i,j),且一个洞必须前一只鸟通过之后后一只鸟才能开始通过。

从时刻0开始,鸟开始通过,而每一只鸟的等待时间为从时刻0到自己已经通过洞的时间。现在知道了第i种体型的鸟有pi只,请求出使所有鸟都通过墙的最少的等待时间之和。

Data Constraint

这里写图片描述

Solution

看到这种题首先就想到了网络流(问:恩!?等等!!,网络流是NOIP提高组的?别吓我! 答:别问我,我也不知道,反正题目就摆在那,我在比赛时也别吓到了,搞到我第一时间就否决了网络流的想法,拼死去想了一个小时dp,毛都没有!!!)

我们考虑用费用流解决这道题。首先源点向第i种小鸟连一条流量为pi,费用为0的边,接着将每个洞拆成p个洞(p为小鸟总数)。每只小鸟分别向m* p个洞连一条流量为1,费用为 f(m,n)* 1,f(m,n)* 2,f(m,n)* 3,…… ,f(m,n)* p。m* p个洞分别向汇点连一条流量为1,费用为0的边。

由于有m*p=80000个洞,40种小鸟,总共会连3.2 106 ,条边,边数太过巨大!!!,所以我们考虑动态加边。具体操作为:我们会发现,对于第i个洞,不流第一个费用为f(i,j),反而流第二个费用为f(i,j) 2显然不优,所以我们对于第一个洞,只有流满第一个费用为f(i,j),才建流第二个费用为f(i,j)* 2边,即每种小鸟向新建的点连一条流量为1,费用为f(i,j)*2的边。

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=6600000,maxn1=802;
int first[maxn],last[maxn],next[maxn],value[maxn],cost[maxn],d[maxn],dui[maxn],bz[maxn],a[maxn1][maxn1];
int n,m,num,x,i,t,j,k,l,ans,p,m1;
void lian(int x,int y,int z,int z1){
    last[++num]=y;value[num]=z;cost[num]=z1,next[num]=first[x];first[x]=num;
}
int pan(int x){
    if (x%m) return x%m;return m;
}
int pan1(int x){
    if (x%m) return x/m;return x/m-1;
}
int dg(int x,int sum){
    if (x==n+m*maxn1+1){
        ans+=d[0]*sum;
        return sum;
    }
    int t,k,q=sum,l;
    bz[x]=p;
    for (t=first[x];t;t=next[t])
        if (bz[last[t]]!=p && d[x]==d[last[t]]+cost[t] && value[t]){
            k=dg(last[t],min(q,value[t]));
            if (k){
                value[t]-=k;
                value[dui[t]]+=k;
                if (last[t]==n+m*maxn1+1){
                    for (i=1;i<=n;i++){
                        l=a[i][pan(x-n)]*(pan1(x-n)+2);
                        lian(i,x+m,1,l),lian(x+m,i,0,-l),dui[num]=num-1,dui[num-1]=num;
                    }
                    lian(x+m,n+m*maxn1+1,1,0),lian(n+m*maxn1+1,x+m,0,0),dui[num]=num-1,dui[num-1]=num;
                }
                q-=k;
                if (!q) break;
            }
        }
    return sum-q;
}
bool pan(){
    if (!p) return true;
    int i,j,t,k=maxn,l;
    for (i=0;i<=n+m*maxn1+1;i++)
        if (bz[i]==p)
            for (t=first[i];t;t=next[t])
                if (bz[last[t]]!=p && value[t]) k=min(k,d[last[t]]+cost[t]-d[i]);
    if (k==maxn) return false;
    for (i=0;i<=n+m*maxn1+1;i++)
        if (bz[i]==p) d[i]+=k;
    return true;
}
int main(){
//  freopen("data.in","r",stdin);
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++)
        scanf("%d",&x),lian(0,i,x,0),lian(i,0,0,0),dui[num]=num-1,dui[num-1]=num;
    for (i=1;i<=n;i++)
        for (j=1;j<=m;j++){
            scanf("%d",&x);a[i][j]=x;
            lian(i,n+j,1,x),lian(n+j,i,0,-x),dui[num]=num-1,dui[num-1]=num;
        }
    for (i=n+1;i<=n+m;i++)
        lian(i,n+m*maxn1+1,1,0),lian(n+m*maxn1+1,i,0,0),dui[num]=num-1,dui[num-1]=num;
    while (pan()){
        p++;
        while (dg(0,maxn)) p++;
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值