ICPC Shanghai 网络赛 Stone Game

题意

给定一个可重集\(S\),请求出满足下面条件的子集\(S'\)个数:

  • \(S'\geq S-S'\)
  • \(S'-a_i \leq S-S'\),其中\(a_i \in S'\)

\(|S|\leq 300, a_i \leq 500\)

解法

\(\tt {hin}\)有味的计数题,虽然\(A\)的人很多,但是我没想出来

我们令满足条件的集合为集合\(A\),其补集为\(B\)

首先要发现一个性质,如果集合\(A\)中的元素最小值为\(a\),那么如果\(A\)为满足条件的子集,\(A-a\leq B\)

那么我们考虑枚举\(a\)

为了保证单调,我们把所有元素从小到大排序,倒序枚举\(a\)

我们假设当前枚举到的\(a\)\(a_i\),那么\(a_1\)\(a_{i-1}\)一定都属于\(B\)集合

这是因为我们枚举的\(a\)是集合中的元素最小值,所以小于\(a\)的元素一定不在\(A\)集合中

此时合法的\(A\)集合中包含的元素一定有\(a_i\),并且剩下的元素都来自\(a_{i+1}\)\(a_n\)

我们先处理出一个\(DP\)数组\(f[i][j]\)代表考虑\(i\)\(n\)中的元素,选择的\(A\)集合与\(B\)集合权值之差为\(j\)的方案数

转移方程还是比较好想的,即
\[ f[i][j]=f[i+1][j+a_i]+f[i+1][j-a_i] \]
至于下标为负的情况,强制转正即可

由于\(a_1\)\(a_{i-1}\)一定属于\(B\)集合,我们统计一下前缀和,那么\(B\)集合的权值至少为\(sum_{i-1}\)

为了满足上面的条件即$A-a\leq B $

我们把\(A\)分为两个部分\(A=a+A'\),其中\(A'\)\(A\)集合在\(i\)右侧的部分,同样的,把\(B\)也分为\(i\)左侧的部分\(B''\)(很明显\(B''\)的权值大小即\(sum_{i-1}\))与右侧的部分\(B'\)
\[ \because A-a \leq B \\ \therefore a+A'-a \leq B'+B'' \\ \therefore A'-B' \leq sum_{i-1} \]
又要满足限制\(1\),即$A\geq B $
\[ \because A\geq B \\ \therefore a+A' \geq B'+B''\\ \therefore A'-B' \geq sum_{j-1}-a \]
这样我们就确定了上下界,枚举差值转移即可

代码

蒯了\(\tt {JR}\)的代码(考场上就\(A\)了,是真的强)

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月15日 星期日 12时55分49秒
*******************************/
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 305;
const int maxm = 300005;
const int mid = 150000;
const int mod = 1000000007;
//{{{cin
struct IO{
    template<typename T>
    IO & operator>>(T&res){
        res=0;
        bool flag=false;
        char ch;
        while((ch=getchar())>'9'||ch<'0')   flag|=ch=='-';
        while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
        if (flag)   res=~res+1;
        return *this;
    }
}cin;
//}}}
int T,n,mi,mx,ans;
int a[maxn],sum[maxn];
int f[2][maxm];
bool now,pre;
void add (int &x,int y){    x=((x+y)%mod+mod)%mod;}
//{{{solve
int solve (int x)
{
    //x左边所有的数都不可以被选
    int c=sum[x-1]+mid;
    int res=0;
    for (int i=0;i<=a[x];++i)   add(res,f[pre][c-i]);
    return res;
}
//}}}
int main()
{
    cin>>T;
    while (T--){
        cin>>n;
        now=pre=false;
        ans=mx=0;
        memset(f,0,sizeof(f));
        f[0][mid]=1;
        for (int i=1;i<=n;++i)  cin>>a[i],mx+=a[i];
        mi=mid-mx,mx+=mid;
        sort(a+1,a+n+1);
        for (int i=1;i<=n;++i)  sum[i]=sum[i-1]+a[i];

        for (int i=n;i>=1;--i){
            now=!now,pre=!now;
            for (int j=mi;j<=mx;++j)
                f[now][j]=(f[pre][j-a[i]]+f[pre][j+a[i]])%mod;
            add(ans,solve(i));
        }

        printf("%d\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/VeniVidiVici/p/11523815.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值