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

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(nk26) O ( n k ∗ 26 ) ,极限数据可达到 O(71500000) 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) O ( n 3 ) 。但是我们使用SAP,加上邻接表优化、GAP优化、当前弧优化,再加上网络流的时间复杂度向来玄学地快,所以我才跑了277ms。
  时间复杂度: O() 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[N],to[N],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[N];
struct S
{
    char s[K];
    int num;
}pre[N],tov[N];
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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值