您的位置:首页 > 理论基础 > 计算机网络

【JZOJ5537】【2014东莞市选】分组(网络流)

2018-01-22 21:56 211 查看

Problem

  有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个分组中的字符串编号,编号对应字符串的输入顺序。数字之间用一个空格隔开。如果分组方案不唯一,输出任意一种即可。

Solution

  这道题我刚看完后觉得不太可做。

  首先,考虑将所有字符串的前缀、后缀离散化。首先想到trie,但是空间复杂度为O(nk∗26),极限数据可达到O(71500000),显然会炸。然后想到快排离散。至于哈希,虽然时间复杂度可以去掉一个log,但却有匹配错误的概率。

  然后考虑转换模型。可以发现,如果将前、后缀视作节点,将每个字符串视作连接其前、后缀的边,那便是一个二分图。我们可以从超级源(S)向每个前缀各建1条流量为1的边,从每个字符串的前缀向后缀建1条流量为无穷大的边,从每个后缀向超级汇(T)均建1条流量为1的边。这样建边保证我们后面求最小割时不会割到字符串。

  那么我们从超级源流一遍最大流。我们知道流完一遍后,某些边会变成反向弧,这样就使得原图变成了两个分别包括S、T但互不相连的点集。我们设其为点集{S}和点集{T}。

  这样的话,如何求出最小割割集呢?我想了很久,最后问了lyl并得到启发:我们在流完之后,从S做一遍flood fill,标记出所有可从超级源到达的节点,则这些点全部属于{S}。若某个前缀可从S到达,那么说明此前缀属于{S},因此S到其的边定然不是割边;若某个后缀可从S到达,那么说明此后缀不属于{T},因此其到T的边定然是割边。

  那么,我们必须选择那些被割的点,因为我们求出的是最小割,所以可以保证m最小。而对于每个字符串,我们随便选择它连接的一个被割的点即可。

  而此图的点数、边数最坏情况是2n,所以此题时间复杂度的理论上限是O(n3)。但是我们使用SAP,加上邻接表优化、GAP优化、当前弧优化,再加上网络流的时间复杂度向来玄学地快,所以我才跑了277ms。

  时间复杂度:O(玄)。

Code

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define N 5010
#define K 560
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fs(i,x) for(it=x;it;it=ne[it])
int i,j,l,n,k,len,cnt,pr
,to
,ps,dis[2*N],vh[2*N],m,sum,tot,ty[100*N],tl[100*N],ne[100*N],la[2*N],di[2*N],il;
char s[K];
bool p[2*N],bz
;
struct S
{
char s[K];
int num;
}pre
,tov
;
struct X
{
int it,x;
}last[2*N];
bool operator<(const S&a,const S&b)
{
int i;
fo(i,0,k-1)
if(a.s[i]<b.s[i])return 1;
else
if(a.s[i]>b.s[i])return 0;
return 0;
}
void insert(int x,int y,int len)
{
ty[++tot]=y;
tl[tot]=len;
ne[tot]=la[x];
la[x]=tot;
}
void sap()
{
int i,j,aug,mi,it,x;
vh[0]=cnt+2;
fo(i,0,cnt)di[i]=la[i];
i=0;
bool flag;
aug=1<<30;
while(dis[0]<cnt+2)
{
flag=0;
fs(i,di[i])
if(dis[j=ty[it]]+1==dis[i]&&(x=tl[it]))
{
flag=1;
di[i]=it;
aug=min(aug,x);
last[j]=(X){it,i};
i=j;
if(i==2*N-1)
{
while(i)
{
j=last[i].x;
tl[last[i].it]-=aug;
insert(i,j,aug);
i=j;
}
aug=1<<30;
}
break;
}
if(flag)continue;
mi=cnt+1;
fs(i,la[i])
if(dis[ty[it]]<mi&&tl[it])
il=it,mi=dis[ty[it]];
di[i]=il;
if(!--vh[dis[i]])break;
dis[i]=mi+1;
vh[dis[i]]++;
if(i)i=last[i].x;
}
}
void dfs(int x)
{
p[x]=1;
int y,it;
fs(x,la[x])
if(!p[y=ty[it]]&&tl[it])
dfs(y);
}
int main()
{
freopen("group.in","r",stdin);
freopen("group.out","w",stdout);
scanf("%d%d",&n,&k);
fo(i,1,n)
{
scanf("%s",&s);
len=strlen(s);
fo(j,0,k-1)
{
pre[i].s[j]=s[j];
tov[i].s[j]=s[len-k+j];
}
pre[i].num=tov[i].num=i;
}
sort(pre+1,pre+n+1);
fo(i,1,n)
{
insert(0,++cnt,1);
fo(j,i,n)
{
pr[pre[j].num]=cnt;
if(j==n||pre[i]<pre[j+1])break;
}
i=j;
}
ps=cnt;

sort(tov+1,tov+n+1);
fo(i,1,n)
{
insert(++cnt,2*N-1,1);
fo(j,i,n)
{
to[tov[j].num]=cnt;
if(j==n||tov[i]<tov[j+1])break;
}
i=j;
}
fo(i,1,n)insert(pr[i],to[i],1<<30);
sap();
dfs(0);

fo(i,1,ps)m+=!p[i];
fo(i,i,cnt)m+=p[i];
printf("%d\n",m);

cnt=0;
fo(i,1,n)
{
cnt++;
sum=0;
fo(j,i,n)
{
if(!p[cnt])sum++;
if(j==n||pre[i]<pre[j+1])break;
}
if(!p[cnt])printf("%d ",sum);
fo(j,i,n)
{
if(!p[cnt])printf("%d ",pre[j].num),bz[pre[j].num]=1;
if(j==n||pre[i]<pre[j+1])break;
}
if(!p[cnt])printf("\n");
i=j;
}

fo(i,1,n)
{
cnt++;
sum=0;
fo(j,i,n)
{
if(p[cnt]&&!bz[tov[j].num])sum++;
if(j==n||tov[i]<tov[j+1])break;
}
if(p[cnt])printf("%d ",sum);
fo(j,i,n)
{
if(p[cnt]&&!bz[tov[j].num])printf("%d ",tov[j].num);
if(j==n||tov[i]<tov[j+1])break;
}
if(p[cnt])printf("\n");
i=j;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: