「题解」:毛一琛/$cow$ $subsets$

问题 A: 毛一琛/$cow$ $subsets$

时间限制: 1 Sec  内存限制: 512 MB

题面


题面谢绝公开。

题解


题名貌似是个大神??看起来像是签到题然后就死了。

首先$O(3^n)$算法显然。也显然过不去$20$的测试点。

正解是赫赫有名的$meet$ $in$ $the$ $middle$算法。数据在40以内的都能用$meet$ $in$ $the$ $middle$??

对于两半路径,可以拼起来并且构成合法答案的条件是两人获得的分数相同。

所以一个比较聪明的办法是,不去记录两个人各自得到了多少分,而是记录两个人得分的差。

至于谁减谁无所谓,相同的话把其中一个翻转就好了。不搞相反也可以避免重复。

于是我们开一个$map$,对于每一个在前一半路径得到的差值映射一个编号。并以之为下标存储前一半扫过的状态,方便拼接。

对于后一半路径扫过的差值,在$map$里查询,有就拼上,状态置1。

代码:

#include<bits/stdc++.h>
#define rint register int
using namespace std;
int n,a[23],tot,ans;
bool vis[(1<<21)+5];
map <int,int> m;
vector <int> v[10000005];
inline void dfs1(rint k,rint res,rint con)
{
    if(k==n/2+1)
    {
        if(m.find(res)==m.end())m[res]=++tot;
        int lin=m[res];v[lin].push_back(con);
        return ;
    }
    dfs1(k+1,res,con);
    dfs1(k+1,res+a[k],con|(1<<k-1));
    dfs1(k+1,res-a[k],con|(1<<k-1));
    return ;
}
inline void dfs2(rint k,rint res,rint con)
{
    if(k==n+1)
    {
        if(m.find(res)!=m.end())
        {
            int lin=m[res];
            for(rint i=0;i<v[lin].size();++i)
                vis[v[lin][i]|con]=1;
        }
        return ;
    }
    dfs2(k+1,res,con);
    dfs2(k+1,res-a[k],con|(1<<k-1));
    dfs2(k+1,res+a[k],con|(1<<k-1));
    return ;
}
int main()
{
    scanf("%d",&n);
    for(rint i=1;i<=n;++i)scanf("%d",&a[i]);
    dfs1(1,0,0);dfs2(n/2+1,0,0);
    for(rint i=1;i<=(1<<n)-1;++i)ans+=vis[i];
    printf("%d\n",ans);
    return 0;
}
View Code
posted @ 2019-10-13 16:22  hzoi_Joe  阅读(202)  评论(0编辑  收藏  举报