Codeforces 1552 D. Array Differentiation —— 背包(双语)

202 篇文章 6 订阅

This way

题意:

给你长度为n的数组a,问你是否有长度为n的数组b使得任意a[i],都是b中某两个(可以是同一个)数相减得到的

题解:

首先这道题数据范围很小,让我不得不想到状压,但是想了一会不知道怎么压,于是就开始尝试找出b是否满足什么条件。
其实在写了几个公式之后我就大胆猜测:a数组中某一个数是一些数的加减得到的。就为YES,否则就是NO,但是写博课如果这样未免过于草率。于是写完之后便又思考了一番:如果将a[i]=b[j]+b[k]看成b数组中j和k练了一条边,那么这个b数组肯定能够构造出一棵树来满足a中n-1个数的生成。
那么a中最后一个数就是在b树上加一条边,也就变成了基环树。如果能够成功构造,那么我们只需要随便定下b[1]的值,剩余的值也全都能推出来。
那么如何找到这个环,我们发现由于a是b中两个值相减得来的,那么这个环上每个节点都会被用到两次,不过不容易找到正负号。但是可以知道至少有一种情况,改变环上a的正负号之后,所有a相加=0.也就是 b x 1 − b x 2 + b x 2 − b x 3 + . . . + b x m − b x 1 b_{x1}-b_{x2}+b_{x2}-b_{x3}+...+b_{xm}-b_{x1} bx1bx2+bx2bx3+...+bxmbx1
那么每个a就有三种情况:
+a,-a,+0
那么我们只需要背包一下,最后查看dp[0]是否不为0即可。
dp[i]表示运算后值为i的可能性
当然,搜索的时间复杂度好像更低

C++代码:

#include<bits/stdc++.h>
using namespace std;
const int N=15,M=2e6+55,z=1e6;
int a[N];
int dp[M],tmp[M];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        memset(dp,0,sizeof(dp));
        int n,f=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),a[i]=abs(a[i]);
        int r=0;
        for(int i=1;i<=n;i++){
            for(int j=z-r-a[i];j<=z+r+a[i];j++)tmp[j]=dp[j];
            for(int j=z-r;j<=z+r;j++){
                if(!dp[j])continue;
                tmp[j-a[i]]+=dp[j];
                tmp[j+a[i]]+=dp[j];
            }
            r+=a[i];
            for(int j=z-r;j<=z+r;j++)dp[j]=tmp[j];
            dp[z-a[i]]++,dp[z+a[i]]++;
        }
        printf("%s\n",dp[z]?"YES":"NO");
    }
    return 0;
}

Python代码:

dp = [0] * 2000005
tmp = dp.copy()
t=int(input())
while t>0:
    t-=1
    n=int(input())
    a=list(map(int,input().split(' ')))
    for i in range(n):
        a[i]=abs(a[i])
    r=0;f=0;z=1000000
    for i in range(n):
        for j in range(z-r-a[i],z+r+a[i]+1):
            tmp[j]=dp[j]
        for j in range(z-r,z+r+1):
            if dp[j]==0:continue
            tmp[j-a[i]]=1
            tmp[j+a[i]]=1
        r+=a[i]
        for j in range(z-r,z+r+1):
            dp[j]=tmp[j]
        dp[z-a[i]]=dp[z+a[i]]=1
    print("YES" if dp[z]!=0 else "NO")
    for i in range(z-r,z+r+1):
        dp[i]=0
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值