AtCoder Grand Contest 020 C - Median Sum

题目大意

N N 个正整数数,A1,A2,,AN, 可在这 N N 个数中选任意个数(但不能不选),得到一个和;总共可以得到 2N1 个和,按照从小到大的顺序排好后,问第 2N1 2 N − 1 个数是多少(即问中位数是哪个)?

题目链接

AGC020-C-Median Sum

数据范围:

1N20001Ai2000 1 ≤ N ≤ 2000 1 ≤ A i ≤ 2000

解题思路:

sum=i=1NAi s u m = ∑ i = 1 N A i ,由题意可知: S2N1=sum S 2 N − 1 = s u m . 现在,任取几个数,它们的和记为 Xi X i ,剩下的数的和记为 Yi Y i ; 并且令 P=min(Xi,Yi),Q=max(Xi,Yi) P = m i n ( X i , Y i ) , Q = m a x ( X i , Y i ) ;可得: PQP+Q=S2N1=sum P ≤ Q , P + Q = S 2 N − 1 = s u m ,由此推出 P12sumQ12sum P ≤ 1 2 s u m , Q ≥ 1 2 s u m ,并且可以得到 P{S1,S2,,S2N11}, P ∈ { S 1 , S 2 , ⋯ , S 2 N − 1 − 1 } , Q{S2N1,,S2N1} Q ∈ { S 2 N − 1 , ⋯ , S 2 N − 1 } (即 P P 在前半段, Q 在后半段) ; (这里就不太懂,题解上说“assume”;我试了几个样例,还真的是这样!)

然后呢,就是要找一个最小的 Q Q ;还没结束,重点(黑科技)还在后面。

一般求能构成哪些和,设计一个bool型的DP状态, dp[i][j] d p [ i ] [ j ] 表示前 i i 个能否加成 j 这个数;通过 dp[i1][ja[i]]dp[i1][j] d p [ i − 1 ] [ j − a [ i ] ] 和 d p [ i − 1 ] [ j ] 转移。但是,空间和时间都是不够的,近乎达到了 O(N3) O ( N 3 ) 的复杂度(虽然能开成一维的)。然而,大S用 bitset 解决了这个问题。

bitset 的用法就不再说了。当 bitset 中第 pos p o s 位为 1 时,就代表能得到 pos p o s 这个值;假设当前 bitset 的状态为 B B ,对于一个 整数 x ,就是用或不用;如果用,可得到状态 (B<<x) ( B << x ) ;如果不用,还是原来的状态 B B . 那么,新的状态 B=B(B<<x) 。题解上说,这样复杂度就变成了 O(N2max(Ai)64) O ( N 2 m a x ( A i ) 64 ) ,bitset 里面肯定有东西。

举个例子吧!有3个数:“1 3 2”,现在到了 ‘2 ‘这个数,前面的 ‘1’, ‘3’ 能构成的和有 {0, 1, 3, 4} ; 所以 bitset 现在的状态为 “11011” (最右边是第 0 位);如果不用 ‘2’ 这个数,状态依旧是 “11011”;如果用 ‘2’ 这个数,状态为 “1101100” (即之前能得到的和全体 +2);最后将这两个状态 或( ) 起来,就得到新的状态 “11111111” (即0~6都能构成)。

哇!理解其中精妙后,真的感觉好NB啊!

AC代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <bitset>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 2005;

int n;
bitset <MaxN * MaxN> bs;

int main()
{
    while(scanf("%d", &n) != EOF)
    {
        int sum = 0;
        bs[0] = 1;
        for(int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            sum += x;
            bs = bs | (bs << x);
        }
        int ans = (sum + 1) / 2;
        while(bs[ans] == false) ans++;
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值