题意:给定一个长度为 n 的序列,满足 1 <= ai <= i,要求确实每一个的符号,使得它们和为0.
析:首先这一个贪心的题目,再首先不是我想出来的,是我猜的,但并不知道为什么,然后在网上搜了一下题解,是什么数学归纳法。。。。。。
贪心策略:1~i的和或者部分和一定能凑出来1~sum[i]。
利用这个策略,也就是说,只要最后和是偶数,那么就一定有解,那这个解怎么求呢??我们先把所有的数从大到小排序,然后逆向计算,如果前 i-1 项和大于0,
那么就减掉第 i 项,否则就加上第 i 项,这样的话,最后的和肯定是0.是我猜,但是AC了。
下面是证明过程,利用的是数学归纳法,当 i = 1时,成立,当 i = k时,假设成立,sum[k+1] = sum[k] + a[k+1],那么只要能求出sum[k]+1~sum[k+1],
设1≤p≤a[k+1],sum[k]+p=sum[k]+a[k+1]-(a[k+1]-p)。因为1 ≤ a[i] ≤ i,易得 sum[k] ≥ k, a[k+1] - p ≤ k。所以一定可以凑出 a[k+1] - p。
所以只需从之前凑出sum[k]里面剪掉凑出a[k+1]-p的数就可以凑 出sum[k]+p。所以从1~sum[k+1]都可以凑出。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector> using namespace std;
const int maxn = 1E5 + 5;
struct node{
int id, num, ans;
};
node a[maxn]; bool cmp1(const node &p, const node &q){
return p.id < q.id;
} bool cmp2(const node &p, const node &q){
return p.num > q.num;
} int main(){
int n;
while(scanf("%d", &n) == 1){
int s = 0;
for(int i = 0; i < n; ++i){ scanf("%d", &a[i].num); a[i].id = i; s += a[i].num; }
if(s & 1){ puts("No"); continue; }
else puts("Yes"); s = 0;
sort(a, a+n, cmp2);
for(int i = 0; i < n; ++i)
if(s > 0){ s -= a[i].num; a[i].ans = -1; }
else { s += a[i].num; a[i].ans = 1; } sort(a, a+n, cmp1);
printf("%d", a[0].ans);
for(int i = 1; i < n; ++i) printf(" %d", a[i].ans);
printf("\n");
}
return 0;
}