• [codeforces 618 F] Double Knapsack (抽屉原理)


    题目链接:http://codeforces.com/contest/618/problem/F

    题目:

    题目大意:

    有两个大小为 N 的可重集 A, B, 每个元素都在 1 到 N 之间. 分别找出 A 和 B 的一个子集, 使得这两个子集元素之和相等.

    分别输出集合A和集合B的子集的个数以及子集中元素在原集合中的位置

    N ≤ $10^6$

    题解:

    首先我们证明一个结论,存在一组方案,满足两个子集在A中和在B中都是连续的一段

    把两个集合看成两个数组,分别计算出前缀和sa,sb

    对于每个i(0<=i<=n),我们j为满足0<=sa[i]-sb[j]<=n-1的最大的j,设d[i]=sa[i]-sb[j],可以发现j的单调递增的

    我们发现d数组一共有n+1个元素(包括i=0,sa[i]=0的情况),并且我们又发现d[i]的取值只有n个。那么根据抽屉原理,至少有两个d数组中的元素是相等的

    于是我们有sa[i']-sb[j']=sa[i]-sb[j](i'>i)

    移项之后得到sa[i']-sa[i]=sb[j']-sb[j]

    这个时候我们就知道在A数组中i+1~i'这一段元素之和与B数组中j+1~j'这一段元素之和相等

    证毕

    其实上面的证明过程就是我们本题的做法,我们用two pointers处理出d数组,然后判断当前的d值在之前是否出现过,这个时候我们就得到了答案

    值得注意的是,我们需要sa[n]<=sb[n],因为若是sa[n]>sb[n]可能出现对于i=n找不到一个j满足上述条件

    代码:

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    
    const int N=1e6+15;
    int n,cnt1,cnt2,st1,st2,ed1,ed2;
    int sa[N],sb[N],vis[N],p[N];
    inline int read()
    {
        char ch=getchar();
        int s=0,f=1;
        while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
        while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
        return s*f;
    }
    void solve(int a[],int b[])
    {
        memset(vis,-1,sizeof(vis));
        int j=0;
        for (int i=0;i<=n;i++)
        {
            while (a[i]-b[j]>=n) j++;
            int d=a[i]-b[j];
            if (vis[d]!=-1)
            {
                cnt1=i-vis[d];
                st1=vis[d]+1;ed1=i;
                cnt2=j-p[vis[d]];
                st2=p[vis[d]]+1;ed2=j;
                break;
            }
            vis[d]=i;p[i]=j;
        }
    }
    int main()
    {
        n=read();
        for (int i=1;i<=n;i++) sa[i]=sa[i-1]+read();
        for (int i=1;i<=n;i++) sb[i]=sb[i-1]+read();
        if (sa[n]<=sb[n])
        {
            solve(sa,sb);
        }
        else 
        {
            solve(sb,sa);swap(cnt1,cnt2);swap(st1,st2);swap(ed1,ed2);    
        }
        printf("%d
    ",cnt1);
        for (int i=st1;i<=ed1;i++) printf("%d ",i);
        printf("
    ");
        printf("%d
    ",cnt2);
        for (int i=st2;i<=ed2;i++) printf("%d ",i);
        printf("
    ");
        return 0;
    }
  • 相关阅读:
    类class解读
    函数重载
    for循环之省略{}
    《c++程序设计现代方法》笔记2
    【程序】c++雇员工资管理系统
    贪婪法
    乘法口诀程序
    百钱买百鸡问题程序
    【转】牛人十个月自学C++ 现在做C#开发工作
    c++基本规则习惯
  • 原文地址:https://www.cnblogs.com/xxzh/p/9535463.html
Copyright © 2020-2023  润新知