codeforces 618F Double Knapsack

题意:给你两个长度为n的序列,在两个序列中分别找出一个子集,使这两个子集的总和相等。输出方案。
思路:大概,这个题很多条件都是用来搞事的。比如,输出-1就很有迷惑性,事实上这题并不可能无解。而且,如果子序列改为子段的话,会更好想一些。
总之,这道题的思维性很强。我们可以维护两个前缀和数组 s a [ n ] , s b [ n ] sa[n],sb[n] sa[n],sb[n],分别代表两个序列的前缀和。
序列a中子段的和可以表示为 s a [ i ] − s a [ j ] ( n ≥ i ≥ j ≥ 0 ) sa[i]-sa[j](n\geq i\geq j\geq 0) sa[i]sa[j](nij0)
判断是否存在两个子段,保证
s a [ i ] − s a [ j ] = s b [ i ′ ] − s b [ j ′ ] ( n ≥ i ≥ j ≥ 0 , n ≥ i ′ ≥ j ′ ≥ 0 ) sa[i]-sa[j]=sb[i']-sb[j'](n\geq i\geq j\geq 0,n\geq i'\geq j'\geq 0) sa[i]sa[j]=sb[i]sb[j](nij0,nij0)
移项后可以得到 s a [ i ] − s b [ i ′ ] = s a [ j ] − s b [ j ′ ] sa[i]-sb[i']=sa[j]-sb[j'] sa[i]sb[i]=sa[j]sb[j]
而对于每一个 s a [ i ] sa[i] sa[i],总能找到一个 s b [ i ′ ] sb[i'] sb[i],使得 s a [ i ] − s b [ j ′ ] sa[i]-sb[j'] sa[i]sb[j]的值在区间 [ 0 , n − 1 ] [0,n-1] [0,n1]范围内。
由鸽巢原理可以得到,对于 [ 0 , n ] [0,n] [0,n]范围内的所有 i i i,必然存在两个 s a [ i ] − s b [ i ′ ] sa[i]-sb[i'] sa[i]sb[i]的值相同。
代码:

#include <bits/stdc++.h>
 
using namespace std;
 
#define ll long long
#define ld long double
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
 
const int maxn = 1e6 + 10;
const ll mod = 998244353;
const ld PI = acos(-1.0);
 
int n, p[maxn];
ll s1[maxn], s2[maxn], a[maxn], b[maxn];
set<ll> s;
 
int main() {
    __;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        s1[i] = s1[i - 1] + a[i];
    }
    for (int i = 1; i <= n; ++i) {
        cin >> b[i];
        s2[i] = s2[i - 1] + b[i];
    }
    if (s1[n] < s2[n]) {
        int pos = 0;
        for (int i = 0; i <= n; ++i) {
            while (s2[pos] < s1[i])++pos;
            p[i] = pos;
            if (s.count(s1[i] - s2[p[i]]) == 0)
                s.insert(s1[i] - s2[p[i]]);
            else {
                for (int j = i - 1; j >= 0; --j) {
                    if (s1[i] - s2[p[i]] == s1[j] - s2[p[j]]) {
                        cout << i - j << endl;
                        for (int k = j + 1; k <= i; ++k)cout << k << ' ';
                        cout << endl;
                        cout << p[i] - p[j] << endl;
                        for (int k = p[j] + 1; k <= p[i]; ++k)cout << k << ' ';
                        cout << endl;
                        return 0;
                    }
                }
            }
        }
    } else {
        int pos = 0;
        for (int i = 0; i <= n; ++i) {
            while (s1[pos] < s2[i])++pos;
            p[i] = pos;
            if (s.count(s2[i] - s1[p[i]]) == 0)
                s.insert(s2[i] - s1[p[i]]);
            else {
                for (int j = i - 1; j >= 0; --j) {
                    if (s2[i] - s1[p[i]] == s2[j] - s1[p[j]]) {
                        cout << p[i] - p[j] << endl;
                        for (int k = p[j] + 1; k <= p[i]; ++k)cout << k << ' ';
                        cout << endl;
                        cout << i - j << endl;
                        for (int k = j + 1; k <= i; ++k)cout << k << ' ';
                        cout << endl;
                        return 0;
                    }
                }
            }
        }
    }
    cout << -1 << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值