CF1105E Helping Hiasat

cf

luogu

先将问题转换.由于一个网友要一直和他同名答案才能+1,所以对于一个改名的间隔,如果要选这个网友就不能选其他网友,所以对于两个1操作之间的所有网友分别相互连边.最后我们得到了一张图,现在问题是无向图最大独立集

\(n\le 40\),那就\(meet\ in\ the\ middle\),点集分为两半,然后分别暴力枚举集合,再枚举左边的某个集合,右边的集合能选当且仅当不存在和右边集合有连边的点,所以可以预处理出\(g_i\)表示右边超集为\(i\)的独立集大小最大值,可以高维前缀和实现.再预处理\(h_i\)表示左边独立集\(i\)在右边点的连边情况,同样可以高维前缀和.然后每次查一下就好了

upd:上面两个信息可以不用高维前缀和,因为子集max计算是可以算重复贡献的,所以一个\(i\)可以由\(i-lowbit(i)\)\(i-hignbit(i)\)转移过来

#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
#define db double

using namespace std;
const int N=(1<<20)+10,M=45;
int rd()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
map<string,int> id;
bool v[M],mp[M][M];
int q,n,nn,stk[M],tp,zt[N],f[N],g[N],l2[N];
char cc[M];

int main()
{
    //
    q=rd(),n=rd();
    while(q--)
    {
        int op=rd();
        if(op==1)
        {
            for(int i=1;i<=tp;++i)
                for(int j=i+1;j<=tp;++j)
                    mp[stk[i]][stk[j]]=mp[stk[j]][stk[i]]=1;
            while(tp) v[stk[tp]]=0,--tp;
        }
        else
        {
            scanf("%s",cc);
            if(!id[cc]) id[cc]=++nn;
            if(!v[id[cc]]) v[id[cc]]=1,stk[++tp]=id[cc];
        }
    }
    for(int i=1;i<=tp;++i)
        for(int j=i+1;j<=tp;++j)
            mp[stk[i]][stk[j]]=mp[stk[j]][stk[i]]=1;
    while(tp) v[stk[tp]]=0,--tp;
    for(int i=1;i<=n/2;++i)
    {
        for(int j=1;j<=n-n/2;++j)
            zt[1<<(i-1)]|=mp[i][n/2+j]<<(j-1);
    }
    for(int i=1;i<=1<<20;++i) l2[i]=l2[i>>1]+1;
    memset(f,-0x3f3f3f,sizeof(f));
    f[0]=0;
    for(int i=1;i<1<<(n/2);++i)
    {
        int j=i^(i&(-i));
        bool ok=1;
        for(int k=1;ok&&k<=n/2;++k)
            ok=!(i>>(k-1)&1)||!mp[l2[i^j]][k];
        if(ok) f[i]=f[j]+1;
    }
    memset(g,-0x3f3f3f,sizeof(g));
    g[0]=0;
    for(int i=1;i<1<<(n-n/2);++i)
    {
        int j=i^(i&(-i));
        bool ok=1;
        for(int k=1;ok&&k<=n-n/2;++k)
            ok=!(i>>(k-1)&1)||!mp[l2[i^j]+n/2][k+n/2];
        if(ok) g[i]=g[j]+1;
    }
    for(int j=1;j<1<<(n/2);j<<=1)
        for(int i=0;i<1<<(n/2);++i)
            if((i&j)==j) zt[i]|=zt[i^j];
    for(int j=1;j<1<<(n-n/2);j<<=1)
        for(int i=0;i<1<<(n-n/2);++i)
            if((i&j)==j) g[i]=max(g[i],g[i^j]);
    int ans=0,u=(1<<(n-n/2))-1;
    for(int i=0;i<1<<(n/2);++i)
        ans=max(ans,f[i]+g[u^zt[i]]);
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/smyjr/p/11594953.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值