JZOJ4841 平衡的子集

题意:有一堆数字,从中任意选一些,使得这些数字能够分成两组,两组和相等。
n<=20,ai<=10^9;
这题看起来简单,实际上比较难,暴力的话拿不了多少分。
正解的脑洞超大,我觉得我好弱啊。
首先我们要知道每个数都有3个系数,-1,0,1。
因此我们考虑折半搜索(别问我怎么考虑到的),因为只有3个系数,我们可以分别对前一半和后一半分别搜索,时间复杂度是3^(n/2),搜索出所有数字的选择情况。
然后对于后一半的状态,按照和排序,然后前一半的状态O(2^(N/2))枚举,在用双指针匹配前一半和后一半的状态统计答案。
ps:这题用pascal打有毒啊!!!!我本地秒过交上去就是RE0RE0,mdzz。改成c++240ms就过了,食屎啦你!( ・∀・)つ=≡≡ξ)Д`)。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)

using namespace std;
typedef long long ll;
const int N=1e7;
ll m,tot,tot1,ans,n,midn,k;
bool vis[2*N];
int a[20],head[N],go[N],next[N],len[N];
struct node
{
    ll x1,x2;
}edge[200000];
bool cmp1(node a,node b)
{
    return a.x1<b.x1;
}
bool cmp2(int a,int b)
{
    return a<b;
}
inline void dfs1(int x,int x1,int x2)
{
    if (x==midn)
    {
        go[++tot]=x1;
        next[tot]=head[x2];
        head[x2]=tot;
        return;
    }
    dfs1(x+1,x1,x2);
    dfs1(x+1,x1+a[x],x2|(1<<x));
    dfs1(x+1,x1-a[x],x2|(1<<x));
}
inline void dfs2(int x,int x1,int x2)
{
    if (x==n)
    {
        edge[tot1].x1=x1;
        edge[tot1++].x2=x2;
        return;
    }
    dfs2(x+1,x1,x2);
    dfs2(x+1,x1+a[x],x2|(1<<x));
    dfs2(x+1,x1-a[x],x2|(1<<x));
}
int main()
{
    freopen("subset.in","r",stdin);
    freopen("subset.out","w",stdout);
    scanf("%d",&n);
    midn=(n+1)/2;
    fo(i,0,n-1) scanf("%d",&a[i]);
    dfs1(0,0,0);
    dfs2(midn,0,0);
    sort(edge+1,edge+1+tot1,cmp1);
    fo(i,0,1<<midn)
    {
        m=0;
        for(int j=head[i];j;j=next[j])
        {
            len[m++]=go[j];
        }
        sort(len,len+m,cmp2);
        k=0;
        fo(j,0,tot1-1)
        {
            while (k<m&&len[k]<edge[j].x1) k++;
            if (k==m) break;
            if (len[k]==edge[j].x1)
            vis[i|edge[j].x2]=true;
        }

    }
    fo(i,1,(1<<n)-1)
    if (vis[i])ans++;
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值