有n个字符串,给这些字符串分组,使得每个字符串属于且仅属于一个组。
对于一个合法的分组,至少满足以下两个条件种的一个:
1. 所有字符串的k前缀相同(即前k个字母相同)
2. 所有字符串的k后缀相同(即后k个字母相同)
你需要给这些字符串分组,使得所分的组数最少。
100%的数据n<=5000,k<=550
首先不难看出这题其实就是:有若干可选覆盖,要完整覆盖某一对象的模型,字符串只是唬人的。
这种题往网络流、dp想。
其实是经典的最小割模型:
前缀点在T集中为选,在S集中为不选
后缀点在S集中为选,在T集中为不选。
然后跑一次最小割就是答案;
接下来就是求方案。一种经典的思路是:
以构造的思想去看残量网络,设s可以走到(只走流量非0边)的点集为割中的S集.
剩下的是T集.
显然S->T的任意一条边都是满流的。而且其流量之和=最大流。
将这些边割掉就得到了一个S-T最小割。(因为可证明最小割=最大流,现在就构出了一个)
如果想看具体一点的证明可以去看
这题并不需要知道具体割边,只需要求随意一组最小割的S集合即可。于是S遍历一次就可以得出每一个前缀是否选了。
下面是附加内容:
如何判断必然割边(有些地方也叫关键割边)呢?从S,T分别出发(T是用反向边判断当前边是否能走,因为要求的实际上是能走到T的点),可以获得一个S集与T集,可能会有一些两边都没有走到的点。如果一条边连接S,T,那么这是一条必割边。因为只要这条边改变那么最大流也会改变,最小割也会改变,也就是说这条边在最小割上。
判断可能割边也是类似的想法。假如残量网络中存在其他u->v路径,那么将此边删去后再跑最大流流量不会正好减少那么多,也就是说这条边一定不在割中(如果在,那么删掉之后割集会正好减少那么多)。刨除上述两种情况后剩下的就是可能是割边的情况了。 (充分必要只证一边,感性++)
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 5050,mo=1e9+7,SZ='Z'+1,INF=1e9;
int n,k;
char s[N][560];
int len[N],pre[N],suf[N],cnt,S,T;
pair<int,int> pp[N],ss[N];
map<int,int> pm,sm;
int ans,fz[N][N];
int final[N*2],nex[N*6],to[N*6],R[N*6],tot;
int gap[N*2],d[N*2];
int make(char *s,int l,int r) {
long long ret=0;
for (int i=l; i<=r; i++) ret=(ret*SZ+s[i])%mo;
return ret;
}
void link(int x,int y ,int r) {
R[++tot]=r,to[tot]=y,nex[tot]=final[x],final[x]=tot;
}
int go(int x,int r) {
if (x==T) return r;
int used=0;
for (int i=final[x]; i; i=nex[i]) if (R[i] && d[x]==d[to[i]]+1) {
int e=go(to[i],min(r,R[i]));
R[i]-=e,R[i^1]+=e;
used+=e; if (used==r) return used;
}
if (--gap[d[x]++]==0) d[0]=INF;
gap[d[x]]++;
return used;
}
int q[2*N],bz[2*N];
void bfs() {
int l=0,r=0; q[++r]=S;
while (lint x=q[++l];
for (int i=final[x]; i; i=nex[i]) if (R[i] && !bz[to[i]]) {
bz[to[i]]=1;
q[++r]=to[i];
}
}
}
int cx[2*N];
void add(int g,int v) {
if (!cx[g]) cx[g]=++ans;
fz[cx[g]][++fz[cx[g]][0]]=v;
}
int main() {
freopen("group.in","r",stdin);
// freopen("group.out","w",stdout);
cin>>n>>k; for (int i=1; i<=n; i++) {
scanf("%s",s[i]+1);
len[i]=strlen(s[i]+1);
pp[i]=make_pair(pre[i]=make(s[i],1,k),i);
ss[i]=make_pair(suf[i]=make(s[i],len[i]-k+1,len[i]),i);
}
sort(ss+1,ss+1+n),sort(pp+1,pp+1+n);
S=1,T=cnt=2,tot=1;
for (int i=1; i<=n; i++)
if (pp[i].first != pp[i-1].first) pm[pp[i].first]=++cnt,link(S,cnt,1),link(cnt,S,0);
for (int i=1; i<=n; i++)
if (ss[i].first != ss[i-1].first) sm[ss[i].first]=++cnt,link(cnt,T,1),link(T,cnt,0);
for (int i=1; i<=n; i++) {
link(pm[pre[i]],sm[suf[i]],INF);
link(sm[suf[i]],pm[pre[i]],0);
}
gap[0]=cnt;
int sum=0;
while (d[0]!=INF) sum+=go(1,INF);
bfs();
for (int i=1; i<=n; i++) {
int e=pm[pre[i]];
if (bz[e]) add(sm[suf[i]],i); else add(e,i);
}
cout<for (int i=1; i<=ans; i++) {
for (int j=0; j<=fz[i][0]; j++) printf("%d ",fz[i][j]);
printf("\n");
}
}