【2014东莞市选】分组
(File IO): input:group.in output:group.out
Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits Special Judge
Description
有n个字符串,给这些字符串分组,使得每个字符串属于且仅属于一个组。
对于一个合法的分组,至少满足以下两个条件种的一个:
1. 所有字符串的k前缀相同(即前k个字母相同)
2. 所有字符串的k后缀相同(即后k个字母相同)
你需要给这些字符串分组,使得所分的组数最少。
Input
第一行两个整数n,k(1<=n<=5000, 1<=k<=550),分别表示字符串的数量以及题述中的参数k。
接下来有n行,每行一个字符串,字符串的长度至少为k,且不会超过550。
Output
第一行一个整数m,表示最少的分组数目。
接下来m行,每行的第一个整数ti表示第i个分组的字符串数量,接下来有ti个整数,表示第i个分组中的字符串编号,编号对应字符串的输入顺序。数字之间用一个空格隔开。如果分组方案不唯一,输出任意一种即可。
Sample Input
4 1
AA
AB
BB
BA
Sample Output
2
2 1 2
2 3 4
Data Constraint
50%的数据n<=100
100%的数据n<=5000,k<=550
最开始的思路:
将串们排序,将k位的前缀与后缀编号。
贪心,若某前缀有分组,那么塞进去,否则塞进后缀的分组,再否则新建分组,然而显然是错的:
4 2
AABC
ADBC
ADXY
AAXX
这样会将1,2分组,导致不是最优
于是我考虑按照前缀的顺序加入,也是错的:
4 2
AABC
AABD
CCBD
CDBC
同理。
于是我按照后缀的顺序重做,取最小值,当然还是错的!
——只是坑到了90+分
正解:
假设单词前缀集合为 {A1..n1} ,后缀的集合为 {B1..n2} ,每个单词可以用 AiBj 表示,于是这就符合二分图的定义了。
网络流。源点连向所有 Ai ,权为1,割掉这条边表示选择这个前缀,所有 Bi 连向汇点,权为1,割掉表明选择这个后缀,每个单词为一条边,权为∞,若ST连通,则说明还有单词没被选到,答案就是最小割。
要找到组内的元素,就要知道割掉哪些边,那么怎么找割边呢?
想到残余网络中割边权为0,但权为0时一定是割边吗?不是的:
S到T有两条路径,但只能跑一个单位的流,图中有两条权为0的边,但明显仅有连向T的边是割边。
那么就换一种方式判断是不是割边:从S开跑,如果能到达的点是靠T那一边的 Bi ,在流完的情况下是不可能到汇点的,那么 Bi 一定被割掉,反之不被割掉(最小割的情况下不可能再多割一条边),如果能到达靠S那一边的 Ai ,那么 Ai 一定不被割掉,①直接连边②上图的情况,通过反向弧到达,反之若不能到达 Ai ,则一定被割掉。
这样一来问题就迎刃而解了。
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 5010
#define L 560
#define min(a,b) (a<b?a:b)
using namespace std;
int n,k,cnt[2],ex[2][N],no[N][2],top=1,fir[N*2],las[N*2],nex[4*N],to[4*N],v[4*N];
int own[2*N],head[2*N],nxt[2*N],tot,S,T,h[N*2],vh[N*2],sta[N],que[N*2],H;
bool vis[N],used[N];
struct str{int p,el[L];}pre[N],suf[N];
char c[L];
bool sm(str a,str b){
for(int i=1;i<=k;i++){
if(a.el[i]<b.el[i])return 1;
if(a.el[i]>b.el[i])return 0;
}return 0;
}
bool eq(str a,str b){
for(int i=1;i<=k;i++)if(a.el[i]!=b.el[i])return 0;return 1;
}
void link(int x,int y,int val){
to[++top]=y;nex[top]=0;v[top]=val;
if(fir[x])nex[las[x]]=top;else fir[x]=top;las[x]=top;
}
void qlink(int x,int y){
own[++tot]=y;nxt[tot]=head[x];head[x]=tot;
}
int flow(int x,int fl){
if(x==T)return fl;
int mn=T+10,i,j,y,f;
for(i=fir[x],j=las[x];i;j=i,i=nex[i])if(v[i]){
if(h[x]==h[y=to[i]]+1){
if(f=flow(y,min(fl,v[i]))){
nex[las[x]]=fir[x];nex[j]=0;fir[x]=i;las[x]=j;
v[i]-=f;v[i^1]+=f;
return f;
}if(h[S]>T)return 0;
}mn=min(mn,h[y]+1);
}
if(!--vh[h[x]])h[S]=T+10;
++vh[h[x]=mn];return 0;
}
int main(){
freopen("group.in","r",stdin);
freopen("group.out","w",stdout);
scanf("%d %d\n",&n,&k);
for(int i=1,l;i<=n;i++){
memset(c,0,sizeof(c));scanf("%s",c+1);l=strlen(c+1);
pre[i].p=suf[i].p=i;
for(int o=1;o<=k;o++)pre[i].el[o]=c[o]-'A',suf[i].el[o]=c[l-o+1]-'A';
}sort(pre+1,pre+n+1,sm);sort(suf+1,suf+n+1,sm);
for(int i=1;i<=n;i++)no[pre[i].p][0]=(i==1 || !eq(pre[i],pre[i-1]))?++cnt[0]:cnt[0];
for(int i=1;i<=n;i++)no[suf[i].p][1]=(i==1 || !eq(suf[i],suf[i-1]))?++cnt[1]:cnt[1];
S=cnt[0]+cnt[1]+1;T=S+1;
for(int i=1;i<=cnt[0];i++)link(S,i,1),link(i,S,0);
for(int i=1;i<=cnt[1];i++)link(i+cnt[0],T,1),link(T,cnt[0]+i,0);
for(int i=1;i<=n;i++){
int x=no[i][0],y=no[i][1];
link(x,y+cnt[0],n+n);link(y+cnt[0],x,0);
qlink(x,i);qlink(y+cnt[0],i);
}
int tot=0;vh[0]=T;while(h[S]<=T)tot+=flow(S,n+n);
printf("%d",tot);
for(H=0,vis[que[T=1]=S]=1;H^T;)
for(int x=que[++H],i=fir[x],y;i;i=nex[i])
if(v[i] && !vis[y=to[i]])vis[que[++T]=y]=1;
for(int i=1;i<=cnt[0];i++)if(!vis[i]){
tot=0;for(int o=head[i];o;o=nxt[o])if(!used[own[o]])used[sta[++tot]=own[o]]=1;
if(tot){printf("\n%d",tot);while(tot)printf(" %d",sta[tot--]);}
}
for(int i=cnt[0]+1;i<=cnt[0]+cnt[1];i++)if(vis[i]){
tot=0;for(int o=head[i];o;o=nxt[o])if(!used[own[o]])used[sta[++tot]=own[o]]=1;
if(tot){printf("\n%d",tot);while(tot)printf(" %d",sta[tot--]);}
}
fclose(stdin);fclose(stdout);
return 0;
}