【DP】Codeforces EDU37 Tanks

题目大意:

给出n个水柜,每个水柜初始有一些水,每个水柜都可以装无限大的水。
有一个勺子,勺子的最大容量为k
用勺子舀水有以下规则:
不能从多个水柜中舀水,倒水时也必须一次性倒完,不能剩余
每一次装下的水为min(v,k)其中v为该水柜剩余水量
现在需要使其中一个水柜中的水恰好为V,求是否有一种舀水方案能够满足,若不能,输出“NO”,若能,输出“YES”,并且输出任意一种舀水方案
输出规则为3个数,分别为cnt,x,y表示从x向y中舀cnt次水。
并且,你所输出的方案行数必须不超过n+5


分析:

其实是一道很简单的背包模型,很容易发现,对于每个水柜,我们可以取到与k不同的值为 aimod k a i m o d   k ,这样我们就可以得到一个DP式
DP[i]=max(DP[(iaj)mod k])DP[0]=1 D P [ i ] = m a x ( D P [ ( i − a j ) m o d   k ] ) , 其 中 D P [ 0 ] = 1 最后我们再将其中多的或少的处理一下即可(都是k的倍数)。
对于所输出方案的行数的要求,其实是没有任何必要的。
我们很容易发现,假设最终有一个合法方案,那么我们可以把每个水柜的值直接移动到最终状态下的位置,那么就可以保证总共输出的行数在n以内了。
另外,对于v%k=0的情况,我们要特殊处理。。其实很简单,把所有的值怼在一块,然后从中选取v就可以了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define SF scanf
#define PF printf
#define MAXN 5010
using namespace std;
int n,k,now,t;
long long a[MAXN],v;
int dp[2][MAXN],fa[2][MAXN];
int get_ans(int x){
    //PF("[%d %I64d]\n",x,a[fa[now][x]]%k);
    if(x==a[fa[now][x]]%k)
        return fa[now][x];
    int t1=get_ans((x-(a[fa[now][x]]%k)+k)%k);
    PF("%I64d %d %d\n",a[t1],t1,fa[now][x]);
    a[fa[now][x]]+=a[t1];
    a[t1]=0;
    return fa[now][x];
}
int find_less(){
    int fir=0;
    for(int i=1;i<=n;i++)
        if(a[i]!=0&&i!=t){
            if(fir!=0){
                PF("%I64d %d %d\n",a[i],i,fir);
                a[i]=0;
            }
            else{
                fir=i;
            }
        }
    return fir;
}
int main(){
    int sum=0;
    SF("%d%d%I64d",&n,&k,&v);
    for(int i=1;i<=n;i++){
        SF("%I64d",&a[i]);
        sum+=a[i];
    }
    if(sum<v){
        PF("NO");
        return 0;
    }
    if(v%k==0){
        if(n==1&&a[1]!=v)
            PF("NO");
        else{
            PF("YES\n");
            if(a[1]!=0)
                PF("%I64d %d %d\n",a[1],1,2);
            a[2]+=a[1];
            a[1]=0;
            if(v!=0){
                int t2=find_less();
                PF("%I64d %d %d",v/k,t2,1);
            }
        }
        return 0;
    }
    dp[0][0]=1;
    now=0;
    for(int i=1;i<=n;i++){
        now^=1;
        int x=a[i]%k;
        memset(dp[now],0,sizeof dp[now]);
        for(int j=0;j<=k;j++){
            if(dp[now^1][j]==1&&dp[now^1][(j+x)%k]==0){
                dp[now][(j+x)%k]=1;
                fa[now][(j+x)%k]=i;
            }
            if(dp[now^1][j]==1){
                dp[now][j]=dp[now^1][j];
                fa[now][j]=fa[now^1][j];
            }
        }
    }
    if(dp[now][v%k]==1){
        PF("YES\n");
        t=get_ans(v%k);
        //PF("[%d %lld]\n",t,a[t]);
        if(a[t]>v)
            PF("%I64d %d %d\n",(a[t]-v)/k,t,t%n+1);
        else if(a[t]==v){
            return 0;
        }
        else{
            int t2=find_less();
            PF("%I64d %d %d\n",(v-a[t])/k,t2,t);
        }
    }
    else
        PF("NO");
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值