CF618F Double Knapsack 构造、抽屉原理

传送门


首先,选取子集的限制太宽了,子集似乎只能枚举,不是很好做。考虑加强限制条件:将“选取子集”的限制变为“选取子序列”的限制。在接下来的讨论中我们将会知道:将限制控制得更紧,问题也一定会有解。

现在我们需要求\(A,B\)的两个子序列,满足两者的和相等。显然可以前缀和,然后就不会做了qwq

考虑下面的算法:假定\(\sum\limits_{a \in A} a < \sum\limits_{b \in B} b\)(如果相等直接全选),设序列\(A\)前缀和为\(sumA_i\),序列\(B\)前缀和为\(sumB_i\)

对于\(n+1\)\(sumA_i\),在\(sumB\)中找到最小的大于等于它的元素\(sumB_j\),那么一定有\(sumB_j - sumA_i \in [0,n)\),可能的\(sumB - sumA\)\(n\)种,但是有\(n+1\)\(i,j\)

根据抽屉原理,一定会存在两组\((i_1,j_1)(i_2,j_2)(i_1 > i_2)\)满足\(sumB_{j_1} - sumA_{i_1} = sum_B{j_2} - sumA_{i_2}\),即\(sumB_{j_1} - sumB_{j_2} = sumA_{i_1} - sumA_{i_2}\)。这样我们就找到了一组可行解:在\(A\)中选择\(A_x , x \in (i_2 , i_1]\),在\(B\)中选择\(B_y , y \in (j_2 , j_1]\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return f ? -a : a;
}

#define PII pair < int , int >
#define st first
#define nd second
#define ll long long
const int MAXN = 1e6 + 7;
ll A[MAXN] , B[MAXN];
PII pos[MAXN];

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    int N = read();
    for(int i = 1 ; i <= N ; ++i)
        A[i] = A[i - 1] + read();
    for(int i = 1 ; i <= N ; ++i)
        B[i] = B[i - 1] + read();
    bool f = A[N] > B[N];
    if(f) swap(A , B);
    fill(pos , pos + N + 1 , PII(-1 , -1));
    int p = 0;
    for(int i = 0 ; i <= N ; ++i){
        while(B[p] < A[i]) ++p;
        if(pos[B[p] - A[i]] != PII(-1 , -1)){
            PII t = pos[B[p] - A[i]];
            if(!f){
                printf("%d\n" , i - t.first);
                for(int j = t.first + 1 ; j <= i ; ++j)
                    printf("%d " , j);
                printf("\n%d\n" , p - t.second);
                for(int j = t.second + 1 ; j <= p ; ++j)
                    printf("%d " , j);
            }
            else{
                printf("%d\n" , p - t.second);
                for(int j = t.second + 1 ; j <= p ; ++j)
                    printf("%d " , j);
                printf("\n%d\n" , i - t.first);
                for(int j = t.first + 1 ; j <= i ; ++j)
                    printf("%d " , j);
            }
            return 0;
        }
        else pos[B[p] - A[i]] = PII(i , p);
    }
    puts("-1");
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10585514.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值