• AtCoder Beginner Contest 269 G(DP)


    题目链接
    G - Reversible Cards 2(DP)

    题意
    N N N张卡片,正面写有数字 A i A_i Ai,背面写有数字 B i B_i Bi ∑ ( A i + B i ) = M \sum(A_i+B_i)=M (Ai+Bi)=M。对于 k ∈ [ 0 , M ] k \in [0,M] k[0,M],求最少翻转多少张卡片,使得所有卡片可见数字之和为 k k k,不存在输出 − 1 -1 1

    分析
    f [ i ] f[i] f[i]表示最少翻转 f [ i ] f[i] f[i]次可以使得可见数字之和为 i i i,根据状态表示有 f [ S ] = 0 f[S]=0 f[S]=0 S = ∑ A i S=\sum A_i S=Ai.
    考虑翻转一张卡片带来的影响,翻转第 i i i张卡片可见数字之和增加 B i − A i B_i-A_i BiAi.接下来对问题进行抽象,将每张卡片看做一个物品,体积为 B i − A i B_i-A_i BiAi,花费为 1 1 1,求最少选择多少个物品可以使花费最少,每个物品只能选一次,这样就转化为01背包问题,容易想到 O ( N M ) O(NM) O(NM) 的做法。

    const int N=2e5+10;
    int a[N],b[N],f[N],g[N];
    int n,m,sum;
    
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
    	memset(f,0x3f,sizeof(f));
    	for(int i=1;i<=n;i++) sum+=a[i],a[i]=b[i]-a[i];
    	f[sum]=0;
    	for(int i=1;i<=n;i++)
    	{
    		int x=a[i];
    		for(int j=0;j<=m;j++) g[j]=f[j];
    		for(int j=0;j<=m;j++)
    		{
    			if(x+j>=0&&x+j<=m)
    			{
    				g[x+j]=min(g[x+j],f[j]+1);
    			}
    		}
    		for(int j=0;j<=m;j++) f[j]=g[j];
    	}
    	for(int i=0;i<=m;i++)
    	{
    		if(f[i]==0x3f3f3f3f) cout<<-1<<endl;
    		else cout<<f[i]<<endl;
    	}
    	return 0;
    }
    

    O ( N M ) O(NM) O(NM)的复杂度不能通过本题,需要进行优化。由于 ∑ ∣ B i − A i ∣ ≤ ∑ ( A i + B i ) = M \sum|B_i-A_i| \leq \sum(A_i+B_i)=M BiAi(Ai+Bi)=M,可知最多有 M \sqrt M M 种不同的 ∣ B i − A i ∣ |B_i-A_i| BiAi B i − A i B_i-A_i BiAi 最多有 2 M 2 \sqrt M 2M 种取值。通过上面的分析,可以对具有相同体积的物品进行合并,这样问题从01背包转化为多重背包。多重背包可以通过二进制拆分进行优化,时间复杂度为 O ( M M ) O(M \sqrt M) O(MM ),可以通过本题。虽然对于单个物品来说,复杂度还有一个 l o g log log,但通过均摊最终的复杂度不带 l o g log log。可以从直观上理解,物品种类越多时,单个物品的个数就越少, l o g log log接近一个常数,当物品种类很少时,时间复杂度变为 M l o g ( M ) Mlog(M) Mlog(M),运行速度相对更快。

    AC代码

    const int N=2e5+10;
    int a[N],b[N],f[N],cnt[N<<1];
    int n,m,sum;
    
    void solve(int x,int v)
    {
    	if(x>0)
    	{
    		for(int i=m-x;i>=0;i--)
    		{
    			f[i+x]=min(f[i+x],f[i]+v);
    		}
    	}
    	else
    	{
    		for(int i=-x;i<=m;i++)
    		{
    			f[i+x]=min(f[i+x],f[i]+v);
    		}
    	}
    }
    
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
    	memset(f,0x3f,sizeof(f));
    	for(int i=1;i<=n;i++)
    	{
    		sum+=a[i];
    		cnt[b[i]-a[i]+m]++;
    	}
    	f[sum]=0;
    	for(int i=-m;i<=m;i++)
    	{
    		if(cnt[i+m]&&i)
    		{
    			int c=cnt[i+m];
    			int j=1;
    			while(c>=j)
    			{
    				c-=j;
    				solve(i*j,j);
    				j<<=1;
    			}
    			if(c) solve(i*c,c);
    		}
    	}
    	for(int i=0;i<=m;i++)
    	{
    		if(f[i]==0x3f3f3f3f) cout<<-1<<endl;
    		else cout<<f[i]<<endl;
    	}
    	return 0;
    }
    

    注意
    使用一维数组表示状态,要注意状态转移顺序,一个状态只能被同一个物品更新一次。

  • 相关阅读:
    如何使用HTTP代理爬虫,防止对网站造成负面影响
    paddle ocr 训练数字识别模型
    K8S上安装LongHorn(分布式块存储) --use
    springAOP落地实现
    sql语句 如果为空值显示为0
    Linux笔记--硬链接与软链接
    推荐使用 Kotlin 关键字 Reified
    CocosCreator3.8研究笔记(十六)CocosCreator 2D对象
    微软不再允许Windows 11通过1@1.com绕过登录 但还有其他办法可以继续用
    MySQL之MHA高可用配置及故障切换实例
  • 原文地址:https://blog.csdn.net/weixin_46155777/article/details/126978953