题目
输入一个长度为n(n<=100000)的序列a,满足1<=aiai<=i,要求确定每个数的正负号,使得所有数的和为0.
思路
1.首先我们需要证明,对于满足1≤aiai≤i的序列,1~ sum[i]的任意一个整数都可以用a[1~i]的和表示。证明方法是数学强归纳法:
假设对于任何n≤k,上述结论成立。那么只需证明n=k+1也成立即可,即sum[k]+1~ sum[k+1]的任何整数都能被表示即可。
设有p,1≤p≤a[k+1],由此
sum[k]+p=sum[k]+a[k+1]−(a[k+1]−p)sum[k]+p=sum[k]+a[k+1]−(a[k+1]−p)
因为i≤aiai≤i,可得
sum[k]≥k,a[k+1]−p≤ksum[k]≥k,a[k+1]−p≤k
所以一定可以凑出a[k+1]-p,所以就可以凑出sum[k]+p,即sum[k]+1~ sum[k+1]的任何整数。
2.主要步骤:先求出总sum,若sum为奇数直接输出no,否则在序列中寻找着和为sum/2的若干元素,并给它们加上符号。
3.在序列中寻找和为sum/2的若干元素是,需要将其排序,并从大的开始,否则找不出来。
(原因不明,但这样的方法在很多题都用到了)
代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define ll long long
using namespace std;
const int maxn = 100000+100;
int n;
bool flag[maxn];
struct node{
int a, k;
bool operator < (const struct node& rhs) const {
return a > rhs.a;
}
}A[maxn];
int main(){
while (scanf("%d",&n) == 1) {
memset(flag, 0, sizeof(flag));
ll sum = 0;
_for(i, 0, n){
scanf("%d",&A[i].a);
A[i].k = i;
sum+=A[i].a;
}
if (sum % 2 != 0) printf("No\n");
else {
printf("Yes\n");
sort(A, A+n);
ll last = sum/2;
_for(i, 0, n)
if (A[i].a <= last){
last -= A[i].a;
flag[A[i].k] = true;
}
_for(i, 0, n)
if (flag[i]) printf("1 ");
else printf("-1 ");
printf("\n");
}
}
return 0;
}
码农小技巧
1.scanf读到文件结尾时,返回的是EOF而不是0,所以如果这道题写成while (scanf(“%d”,&n))的话,会陷入死循环TLE。
(因为此浪费了40min的人生)
感悟
1.刚开始做这道题的时候,也想了很多方法,比如中途相遇,转化法等等。。而这道题的证明结论再贪心是没有见过的,应该得好好理解理解。

本文探讨了一类特殊序列的问题,即如何确定每个数的正负号以使序列和为0。通过数学归纳法证明了任意整数可以用序列的和表示,并提供了一种有效的算法实现方案。
3520

被折叠的 条评论
为什么被折叠?



