题意:给定一个长度为 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(" "); } return 0; }