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
#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 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;
}