题意:
给你长度为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}
bx1−bx2+bx2−bx3+...+bxm−bx1
那么每个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