Codeforces 1348 E Phoenix and Berries —— 思维,两种DP方法

202 篇文章 5 订阅

This way

题意:

现在有n个灌木,每个灌木上有a[i]个红色的果实,b[i]个蓝色的果实,现在有一只不死鸟,他有很多篮子,每个篮子要么只能装同一个颜色,但是允许不同灌木上的果实,要么只能装同一个灌木上,颜色允许不同的果实。现在告诉你篮子的大小,问你最多可以装满多少个篮子。

题解:

我是这样认为的:首先假设所有的红色果实数量之和为sa,蓝色为sb,那么答案就在 s a k + s b k \frac{sa}{k}+\frac{sb}{k} ksa+ksb s a k + s b k + 1 \frac{sa}{k}+\frac{sb}{k}+1 ksa+ksb+1之间,因为剩下的红色果实和蓝色果实可能能找一些灌木内部消化一下组成。那么我就想有没有0s的方法直接暴力判断出来,但是很可惜最终失败了,不得已被迫使用dp。
那么有两种方法,第一种的时间复杂度较高:
在这里插入图片描述
第二种是有点像我上面所说的这种方法,但是还是用到了简单的dp,复杂度较低:
在这里插入图片描述

第一种方法:

那么这种就是比较直观的直接做dp
dp[i][j][k]表示到第i个灌木,剩余j个红色果实,k个蓝色果实时,答案最大是多少,但是这有一个问题就是我们这样会有3个循环,然后再加一个枚举用多少个红色果实组成第i个灌木内部装满篮子的循环的话,就是 O ( n 4 ) O(n^4) O(n4)的时间复杂度,怎么优化掉一维。
可以发现,如果知道到第i个灌木为止,总共有多少个果实,装满了几个篮子,现在剩下几个红色果实的话,可以直接求出蓝色果实的数量。
dp[i][j]表示到第i个灌木时,剩余j个红色果实的时候,最大的答案。注意这里的j是不包括a[i]的,是从前面留下来的j个果实。那么假设之前的果实的总数为sum,当前的蓝色果实的数量为 b [ i ] + s u m − d p [ i ] [ j ] ∗ k − j b[i]+sum-dp[i][j]*k-j b[i]+sumdp[i][j]kj。那么我们接下就枚举这次要同个灌木中合并的红色果实的数量,并且检查蓝色果实的数量是否能满足要求。
当然还有一种情况就是没有同灌木中合并。

#include<bits/stdc++.h>
using namespace std;
const int N=505;
#define ll long long
ll dp[N][N],a[N],b[N];
int main()
{
    ll n,k;
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&a[i],&b[i]);
    memset(dp,-1,sizeof(dp));
    dp[1][0]=0;
    ll ans=0,sum=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=k-1;j++){
            if(~dp[i][j]){
                ll blu=sum-dp[i][j]*k-j+b[i];//到现在为止蓝色果实的数量
                for(ll now_a=0;now_a<=min(k,a[i]);now_a++){//同灌木合并的红色果实的数量
                    ll now_b=k-now_a;//需要的蓝色果实的数量
                    ll s_a=j+a[i]-now_a;//可以不同灌木合并的红色果实的数量
                    ll s_b=blu-now_b;//可以不同灌木合并的蓝色果实的数量
                    if(b[i]-now_b>=0){
                        dp[i+1][s_a%k]=max(dp[i+1][s_a%k],dp[i][j]+1+s_a/k+s_b/k);
                        ans=max(ans,dp[i+1][s_a%k]);
                    }
                }
                dp[i+1][(j+a[i])%k]=max(dp[i+1][(j+a[i])%k],dp[i][j]+(j+a[i])/k+blu/k);
                ans=max(ans,dp[i+1][(j+a[i])%k]);
            }
        }
        sum+=a[i]+b[i];
    }
    printf("%lld\n",ans);
    return 0;
}

第二种:

第一种做完我感觉不甘心啊,那这样的话我之前不都白想了吗?但是我觉得我的思路一定是可以继续下去的并且更加优秀,但是我自己又想不出来,网上看了几个也找不到,于是我就去看cf大佬们的操作。
最终让我找到了一个31ms的人,他的想法和我类似,但是之后的操作让我感到有丶东西。
首先确定答案一定在 s a k + s b k \frac{sa}{k}+\frac{sb}{k} ksa+ksb s a k + s b k + 1 \frac{sa}{k}+\frac{sb}{k}+1 ksa+ksb+1之间,那么对于 s a % k + s b % k > = k sa\%k+sb\%k>=k sa%k+sb%k>=k的时候,使用bitset来做,它可以很方便的进行位运算,bt[i]表示剩余红色的果实的数量%k为i的解的可能性。
并且最后查看的是
在这里插入图片描述
表示如果在0到sa%k+sb%k-k这些值之间如果有可能有解的话,就可以将剩余的果实分解到一些灌木中让他们进行内部消化从而使得答案+1.
具体的怎么来做?
首先将bt[sa%k]置为1,表示当前有剩余sa%k个红色果实的情况,之后枚举每个灌木,如果当前的灌木的sa[i]+sb[i]>=k就表示它有可能被选中进行秘密操作。
再将tmp=bt;因为接下来我们要进行一些影响到bt本身的位运算,所以需要复制一个bt
然后我们枚举的j是指,第i个灌木用j个红色果实和k-j个蓝色果实进行内部合并是否有可能,如果有可能的话,bt|=tmp。
注意在枚举j的时候tmp每次都要向右循环移位,就表示每次使用的红色果实数量增加1.因为我们bitset维护的是剩余红色果实的数量。所以向右循环移位表示减少。
举个例子:
假设我当前有
a[i]=1,b[i]=5,k=4,sa%k=3,sb%k=3
那么当j=1的时候,这个就是可以将i进行内部合并,
于是一开始我们的bt是1000,然后进行操作之后bt就是1100,表示在剩余的红色果实的数量为2的时候有解。
然后我们发现sa%k+sb%k-k=2,于是答案+1.
其实这个位运算相当于一个dp,不过变得简单了一点,也就是说到第i个灌木的时候是否进行内部合并,然后枚举红色果实%k为j的时候是否进行内部合并。将所有情况做出来之后查看 红色果实的数量<=总的剩余果实数量%k是否有解。
嗐,有点难理解,自己意会吧

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=505;
ll a[N],b[N];
bitset<N>bt,tmp;
ll deal(ll n,ll k){
    ll ans=0,sa=0,sb=0;
    for(int i=1;i<=n;i++)
        sa+=a[i],sb+=b[i];
    ans=sa/k+sb/k;
    if(sa%k+sb%k<k)return ans;
    bt[sa%k]=1;
    for(int i=1;i<=n;i++){
        if(a[i]+b[i]>=k){
            tmp=bt;
            for(int j=1;j<k;j++){
                tmp[k]=tmp[0];
                tmp>>=1;
                if(a[i]>=j&&b[i]>=k-j)
                    bt|=tmp;
            }
        }
    }
    for(int i=0;i<=sa%k+sb%k-k;i++){
        if(bt[i]==1){
            ans++;
            break;
        }
    }
    return ans;
}
int main()
{
    ll n,k;
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&a[i],&b[i]);
        printf("%lld\n",deal(n,k));
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值