uva 1614奇怪的股市(归纳法证明,贪心)
输入一个长度为n的序列a,满足\(1\le a_i\le i\),要求确定每个数的正负号,使得所有数的总和为0.例如a={1, 2, 3, 4},则4个数的符号分别是1, -1, -1, 1即可。但若a={1, 2, 3, 3},则无解。n<=1e5。
这道题相当于要找到两堆相等的数。若序列中数的总和为奇数,那么拆出来的两堆数无论如何都不可能相等,所以无解。由于这道题的特殊性质,可用归纳法证明总和为偶数时一定有解。
现在要证明,用前i个数的全部或部分可以凑出0到sum[i]的所有整数。n=1时这是成立的。假设n=k之前所有项都成立,那么\(sum[k+1]=sum[k]+a[k+1]\)。只需证明能凑出\(sum[k]+1\)到\(sum[k]+a[k+1]\)的所有整数即可。由于\(1\le a_[k+1]\le k+1\),而\(sum[k]\ge k\),所以\(sum[k]+p=x+a[k+1]\ (1\le p\le a[k+1],0\le x\le sum[k])\)恒成立。
这样一来,就证明了用前n个数,可以凑出sum[n]/2。当sum[n]是偶数时,另一半也就是sum[n]/2。现在的问题就是找出和等于sum[n]/2的数。直接将a数组从大到小排序,然后贪心的取即可。
为什么贪心的取是正确的呢?因为\(1\le a_i\le i\),排序完的数组必定也满足这个条件。所以只要贪心的取显然有解。
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
struct node{
int data, id;
}a[maxn];
int n, chose[maxn];
long long sum;
bool cmp1(node &x, node &y){
return x.data>y.data; }
//100万才需要读优
int main(){
while (~scanf("%d", &n)){
sum=0;
for (int i=0; i<n; ++i){
scanf("%d", &a[i].data);
sum+=a[i].data; a[i].id=i;
}
if (sum&1){
puts("No"); continue; }
else sum>>=1;
sort(a, a+n, cmp1);
for (int i=0; i<n; ++i)
if (a[i].data<=sum){
sum-=a[i].data;
chose[a[i].id]=1;
} else chose[a[i].id]=-1;
puts("Yes");
for (int i=0; i<n; ++i)
printf("%d ", chose[i]);
puts("");
}
return 0;
}