[贪心] UVa1614 股市 (结论题)

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

题目

输入一个长度为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]pksum[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.刚开始做这道题的时候,也想了很多方法,比如中途相遇,转化法等等。。而这道题的证明结论再贪心是没有见过的,应该得好好理解理解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值