题解:
依照题意找最长反链即可。
不过要输出方案,我们可以这么做:
1.传递闭包,二分图匹配。
2.两边都在二分图最大独立集中的点即为最长反链。
证明:
1.这个方案肯定是反链。
2.设最大匹配为
,独立集大小为
,最小链覆盖为
,依据抽屉原题,反链大小
,而“只存在于独立集一边”的点
,所以这个方案求出来的点
, 所以这是最长反链。
实际上, Dilworth定理也可以这么证明。
怎么找最大独立集呢,首先不在最大匹配中的点在独立集中。我们从一侧未匹配点开始dfs,另一侧要求只能走匹配路径。 我们可以发现这样之后,这一侧的访问到的点和另一侧未访问的点形成最大独立集。
论文哥给的证明:
显然不存在 这一侧访问 另一侧未访问 的边,所以是个独立集。这一侧未访问的点一定是匹配了的(因为是从所有未匹配点开始dfs),剩下的匹配边这一侧被访问了,所以另一侧也应该是被访问的,而且另一侧被访问的点一定是匹配了的(否则就找到了增广路),所以这一侧未访问的点和另一侧访问的点数=最大匹配数(实际上就是最小点覆盖)。那么这个独立集大小就是点数-匹配数,显然不可能更大。
#include <bits/stdc++.h>
using namespace std;
const int N=755, M=2e7+5;
int n;
string s[N];
int son[M][2],fail[M],id[M],tot=1;
int que[M],hd,tl,MM,vis[N*2],vs;
int G[N][N],mate[N*2];
vector <int> edge[N*2];
inline bool dfs(int x) {
vis[x]=vs;
for(auto i:edge[x])
if(!mate[i] || (vis[mate[i]]!=vs && dfs(mate[i])))
return mate[i]=x, mate[x]=i, true;
return false;
}
inline void dfs2(int x) {
vis[x]=vs;
for(auto i:edge[x])
if(mate[i] && vis[i]!=vs) vis[i]=vs, dfs2(mate[i]);
}
int main() {
cin>>n; MM=n; son[0][1]=son[0][0]=1;
for(int i=1;i<=n;i++) {
cin>>s[i]; int p=1;
for(int j=0;j<s[i].length();++j) {
if(!son[p][s[i][j]-'a']) son[p][s[i][j]-'a']=++tot;
p=son[p][s[i][j]-'a'];
} id[p]=i;
}
que[hd=tl=1]=1;
while(hd<=tl) {
int u=que[hd++];
for(int j=0;j<2;++j) {
int v=fail[u];
while(!son[v][j]) v=fail[v];
if(son[u][j]) fail[son[u][j]]=son[v][j], que[++tl]=son[u][j];
else son[u][j]=son[v][j];
}
}
for(int i=1;i<=tl;i++) {
int u=que[i];
if(!id[u]) id[u]=id[fail[u]];
}
for(int i=1;i<=n;i++) {
int p=1;
for(int j=0;j<s[i].length();++j) {
p=son[p][s[i][j]-'a'];
if(j==s[i].length()-1) G[i][id[fail[p]]]=1;
else G[i][id[p]]=1;
}
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
G[i][j]|=(G[i][k]&G[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(G[i][j]) edge[i].push_back(j+n), edge[j+n].push_back(i);
for(int i=1;i<=n;i++)
if(!mate[i]) if(++vs,dfs(i)) --MM;
cout<<MM<<'\n'; ++vs;
for(int i=1;i<=n;i++)
if(!mate[i]) dfs2(i);
for(int i=1;i<=n;i++)
if(vis[i]==vs && vis[i+n]!=vs) cout<<i<<' ';
}