把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF590E】Birthday(AC自动机+二分图匹配)

点此看题面

  • 给定\(n\)个字符串,求最多能选出多少字符串使得它们两两不存在子串关系,并给出一组最优方案。
  • \(n\le750,\sum|s_i|\le10^7\)

\(AC\)自动机求偏序关系

我们对所有字符串建出\(AC\)自动机,接着我们枚举每一个串,想要求出它的所有子串。

众所周知子串是前缀的后缀,我们枚举这个串的每个前缀,则后缀就是\(fail\)树上从该节点一路到根的所有节点。

但是,如果直接暴力跳\(fail\)树,复杂度\(O(n\sum|s_i|)\)显然爆炸。

不过,由于这道题我们要求所有偏序关系,实际上对于每个前缀只需求出最长一个符合条件的后缀,最后再统一跑一遍\(Floyd\)即可。

而要求出最长后缀,就是\(fail\)树上最近的一个终止节点。

本来我们建\(AC\)自动机的时候就需要\(BFS\),直接借助\(BFS\)队列(或直接在\(BFS\)的同时),令每个点从父节点转移即可。

\(Dilworth\)定理+二分图匹配

现在的题面就和【洛谷4298】[CTSC2008] 祭祀完全一样了。

利用\(Dilworth\)定理转化题意,然后上二分图匹配即可。

代码:\(O(\sum|s_i|+n^4)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 750
#define SZ 10000000
using namespace std;
int n,m,bg[N+5],l[N+5],f[N+5][N+5];char s[SZ+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	I void reads(int& l,char* s) {l=0;W(isspace(oc=tc()));W(s[++l]=oc,!isspace(oc=tc())&&~oc);}
}using namespace FastIO;
namespace AC//AC自动机求偏序关系
{
	int Nt=1;struct node {int P,F,S[2];}O[SZ+5];
	I void Ins(CI id,CI l,char* s)//插入字符串
	{
		RI x=1;for(RI i=1,t;i<=l;++i) !O[x].S[t=s[i]&1]&&(O[x].S[t]=++Nt),x=O[x].S[t];O[x].P=id;
	}
	int q[SZ+5];I void Build()//建AC自动机
	{
		RI i,k,H=1,T=0;for(i=0;i<=1;++i) (O[1].S[i]?O[q[++T]=O[1].S[i]].F:O[1].S[i])=1;
		W(H<=T) for(k=q[H++],i=0;i<=1;++i) (O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
		for(i=1;i<=Nt;++i) k=q[i],!O[k].P&&(O[k].P=O[O[k].F].P);//记录每个点向上第一个终止节点
	}
	I void Work(CI id,CI l,char* s)//求每个前缀的最长后缀
	{
		for(RI i=1,x=1,t;i<=l;++i) x=O[x].S[s[i]&1],(t=i^l?O[x].P:O[O[x].F].P)&&(f[t][id]=1);//最后一个位置特判要从父节点取
	}
}
namespace H//匈牙利算法
{
	int p[N+5],s[N+5],vis[N+5];I bool Match(CI x,CI ti)//二分图匹配
	{
		for(RI i=1;i<=n;++i) if(f[x][i]&&
			!p[i]&&vis[i]^ti&&(vis[i]=ti,!s[i]||Match(s[i],ti))) return s[i]=x;
		return 0;
	}
	I int Calc()
	{
		RI i,t=0;for(i=1;i<=n;++i) s[i]=vis[i]=0;for(i=1;i<=n;++i) !p[i]&&(t+=!Match(i,i));return t;//总点数-最大二分图匹配
	}
}
int main()
{
	RI i,j,k;for(read(n),i=1;i<=n;++i) bg[i]=bg[i-1]+l[i-1],reads(l[i],s+bg[i]),AC::Ins(i,l[i],s+bg[i]);
	for(AC::Build(),i=1;i<=n;++i) AC::Work(i,l[i],s+bg[i]);//利用AC自动机初步求出偏序关系
	for(k=1;k<=n;++k) for(i=1;i<=n;++i) for(j=1;j<=n;++j) f[i][j]|=f[i][k]&f[k][j];//Floyd求出所有偏序关系
	RI t=k=H::Calc();for(printf("%d\n",k),i=1;i<=n;++i) if(!H::p[i])//已删除节点不再考虑
	{
		for(j=1;j<=n;++j) H::p[j]+=i==j||f[j][i]||f[i][j];if(H::Calc()==t-1) {printf("%d ",i),--t;continue;}//判断能否在最优构造方案中
		for(j=1;j<=n;++j) H::p[j]-=i==j||f[j][i]||f[i][j];//不能则撤销影响
	}return 0;
}
posted @ 2021-05-06 19:33  TheLostWeak  阅读(93)  评论(0编辑  收藏  举报