GCJ2015 还原集合 题解

GCJ2015 还原集合

提交地址找不到 /qd。

假设说我们知道一个数 x,x>0x, x > 0x,x>0 我们考虑对其进行背包,不妨假设上一个背包的数组为 fff

4IXWEq.png

发现对于一个新的数组位置 iii 会对应 i−x,xi - x, xix,x 两个位置。

然后考虑一下 x,x<0x, x < 0x,x<0 的情况,位置 iii 会对应 x,i+xx, i + xx,i+x 两个位置。

发现本质上还原的两个位置只相差了 xxx,所以我们最后还原出来的数列也只是位置相差了 xxx。这个 xxx 是由若干个数拼接而成的,也就是将一个数取反得到的。


我们考虑如何得到最终答案的一个数,如果当前集合和的最大值减去次大值肯定只有几种情况。

  • 少了一个最小的正数。
  • 多了一个最大的负数。

结合一下就是得到的数的绝对值是最小的。我们不妨将所有的数都当做正数来考虑,那么最后得到初始的 fff 数组也就是只有 f0=1f_0 = 1f0=1 。那么我们找那个唯一有值的情况,之后进行背包即可。

如果 f0≠1f_0 \ne 1f0=1 呢?肯定是有若干个 000 组成的集合,也就是 2sum−12^{sum - 1}2sum1,其中 sumsumsum000 的数量。


当我们找到这个位置 fx=1f_x = 1fx=1 而且其他位置都是 000 的时候我们考虑将其他数进行取反。对于背包 g(i,j)g(i, j)g(i,j) 表示考虑第 iii 个数,容量为 jjj 是否被拼成。如果 g(i,s)=1g(i, s) = 1g(i,s)=1 而且 g(i−1,s−v)=1g(i - 1, s - v) = 1g(i1,sv)=1 那么说明这个数是可以放的。

这里进行背包之后直接进行贪心即可。


背包的空间太大?我们发现最终能够拼成的所有不同权值的集合都已经给出了,我们对于每个权值直接进行离散化即可。

因为一开始我们的 xxx 肯定是 <0< 0<0 的,我们会考虑将其取反,那么我们的权值也需要对应取反一下。

也就是将数轴正负翻转一下即可。


代码实现的话就是直接按照上述过程进行模拟。

#include <bits/stdc++.h>
using namespace std;

//#define Fread
//#define Getmod

#ifdef Fread
char buf[1 << 21], *iS, *iT;
#define gc() (iS == iT ? (iT = (iS = buf) + fread (buf, 1, 1 << 21, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
#define getchar gc
#endif // Fread

template <typename T>
void r1(T &x) {
	x = 0;
	char c(getchar());
	int f(1);
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(; '0' <= c && c <= '9';c = getchar()) x = (x * 10) + (c ^ 48);
	x *= f;
}

template <typename T,typename... Args> inline void r1(T& t, Args&... args) {
    r1(t);  r1(args...);
}

#define int long long
const int maxn = 2e5 + 5;
const int maxm = maxn << 1;
const int inf = 1e15;
void Solve() {
    int i, j, n;
    r1(n);
    struct Node {
        int s, p;
        int operator < (const Node &z) const {
            return s < z.s;
        }
    };
    vector<Node> p(n + 1);
    vector<int> v;
    for(i = 1; i <= n; ++ i) r1(p[i].s);
    for(i = 1; i <= n; ++ i) r1(p[i].p);
    sort(p.begin() + 1, p.end());

    for(i = 1; i <= n; ++ i) v.push_back(-p[i].s);
    sort(v.begin(), v.end());
    
    function<pair<int, int>(void)> gt;
    gt = [&] () {
        int mx[2]; mx[0] = mx[1] = - inf;
        for(i = n; i >= 1; -- i) if(p[i].p) {
            if(p[i].s > mx[0]) mx[1] = mx[0], mx[0] = p[i].s;
            else mx[1] = max(mx[1], p[i].s);
        }
        return make_pair(mx[0], mx[1]);
    };// the empty : only mx[0]
    
    int ps0(0);
    vector<int> abpos;
    while(1) {
        pair<int, int> x = gt();
        if(x.second == -inf) { ps0 = x.first; break; }
        abpos.push_back(x.first - x.second);
        int delta = x.first - x.second;
        static map<int, int> mp;
        mp.clear();
        mp[p[1].s] = p[1].p;
        for(i = 2; i <= n; ++ i) {
            if(mp.count(p[i].s - delta)) p[i].p -= mp[p[i].s - delta];
            mp[p[i].s] = p[i].p;
        }
    }
    int T(0);
    for(i = 1; i <= n; ++ i) if(p[i].p > 0) { T = log2(p[i].p); break; }
    while(T --) abpos.push_back(0);

    sort(abpos.begin(), abpos.end());
    ps0 = -ps0;
    vector<vector<int> >  f(abpos.size() + 2, vector<int>(n + 2, 0));

    f[0][lower_bound(v.begin(), v.end(), 0) - v.begin()] = 1;

    for(i = 0; i < abpos.size(); ++ i) {
        f[i + 1] = f[i];
        for(j = v.size() - 1; j >= 0 && v[j] - abpos[i] >= 0; -- j) {
            int id = lower_bound(v.begin(), v.end(), v[j] - abpos[i]) - v.begin();
            if(v[id] == v[j] - abpos[i])
                f[i + 1][j] |= f[i][id];
        }
    }
    for(int i = abpos.size(), ns = lower_bound(v.begin(), v.end(), ps0) - v.begin(); i > 0; -- i) {
        int x = v[ns] - abpos[i - 1];
        if(x < 0) continue;
        int y = lower_bound(v.begin(), v.end(), x) - v.begin();
        if(v[y] == x && f[i - 1][y] == 1) abpos[i - 1] = - abpos[i - 1], ns = y, ps0 += abpos[i - 1];
        if(x == 0) break;
    }
    sort(abpos.begin(), abpos.end());
    for(int v : abpos) printf(" %lld", v);
    puts("");
}

signed main() {
    freopen("recoverset.in", "r", stdin);
    freopen("recoverset.out", "w", stdout);
    int i, j, T;
    r1(T);
    for(i = 1; i <= T; ++ i) {
        printf("Case #%lld:", i);
        Solve();
    }
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值