jzoj5537 [2014东莞市选]分组 二分图

30 篇文章 0 订阅
14 篇文章 0 订阅

Description


有n个字符串,给这些字符串分组,使得每个字符串属于且仅属于一个组。
对于一个合法的分组,至少满足以下两个条件种的一个:

  1. 所有字符串的k前缀相同(即前k个字母相同)
  2. 所有字符串的k后缀相同(即后k个字母相同)
    你需要给这些字符串分组,使得所分的组数最少。

50%的数据n<=100
100%的数据n<=5000,k<=550

Solution


这道题好劲啊,没有想法
可以想到转成图上的问题来做,把字符串看成边,前后缀看做点,哈希一波处理标号。把所有匹配边和匹配点求出来,然后对于每个连通块讨论:若该连通块没有未匹配点,则把该连通块某一侧的点全部选掉即可。否则就从任意一个未匹配点开始,搜索出一棵交替树(未匹配边->匹配边->未匹配边…),那么交替树上深度为偶数的点就都可以选掉。显然每个点对应一条匹配边,如果某条匹配边两端都是奇数点,则会出现奇环,与二分图矛盾。
但考虑到一棵交替树未必能找到一个连通块的所有点,那么每找到一棵交替树,就把树上的点删掉,再继续重复就好了。这样的话就可以保证每一步都是正确的。

Code


#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
#include <map>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fill(x,t) memset(x,t,sizeof(x))
typedef long long ll;
const int MOD=1000000007;
const int N=50005;
const int E=500005;
struct edge{int x,y,used,next;}e[E];
std:: vector<int> vec[N];
std:: vector<int>:: iterator it;
std:: map<int,int> suf,pre;
int link[N],chosen[N],vis[N];
int prt[N],left[N];
int ls[N],edCnt=1,cnt=0;
char str[N];
int read() {
    int x=0,v=1; char ch=getchar();
    for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
    for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
    return x*v;
}
void addEdge(int x,int y) {
    e[++edCnt]=(edge){x,y,0,ls[x]}; ls[x]=edCnt;
    e[++edCnt]=(edge){y,x,0,ls[y]}; ls[y]=edCnt;
}
int find(int now,int id) {
    for (int i=ls[now];i;i=e[i].next) {
        if (vis[e[i].y]==id) continue;
        vis[e[i].y]=id;
        if (!link[e[i].y]||find(link[e[i].y],id)) {
            link[e[i].y]=now;
            return 1;
        }
    }
    return 0;
}
int hungary() {
    int ret=0;
    rep(i,1,cnt) if (left[i]) ret+=find(i,i);
    return ret;
}
void print(int now) {
    int size=0;
    for (it=vec[now].begin();it!=vec[now].end();it++) {
        if (!prt[*it]) size++;
    }
    if (!size) return ;
    printf("%d ", size);
    for (it=vec[now].begin();it!=vec[now].end();it++) {
        if (!prt[*it]) {
            printf("%d ", *it);
            prt[*it]=1;
        }
    }
    puts("");
}
void get_ans(int now,int d) {
    if (d) print(now);
    vis[now]=1;
    for (int i=ls[now];i;i=e[i].next) {
        if (e[i].used==d&&!vis[e[i].y]) {
            get_ans(e[i].y,d^1);
        }
    }
}
void solve() {
    for (int i=2;i<=edCnt;i+=2) {
        if (link[e[i].x]==e[i].y||link[e[i].y]==e[i].x) {
            e[i].used=e[i^1].used=1;
            chosen[e[i].x]=chosen[e[i].y]=1;
        }
    }
    fill(vis,0);
    rep(i,1,cnt) {
        if (!vis[i]&&!chosen[i]) get_ans(i,0);
    }
    rep(i,1,cnt) {
        if (!vis[i]&&chosen[i]&&left[i]) print(i);
    }
}
void init() {
    int n=read(),k=read();
    rep(i,1,n) {
        scanf("%s",&str);
        int x=0,y=0,len=strlen(str);
        rep(j,0,k-1) {
            x=(ll)(x*27+str[j]-'A')%MOD;
            y=(ll)(y*27+str[len-j-1]-'A')%MOD;
        }
        if (!pre[x]) pre[x]=++cnt;
        if (!suf[y]) suf[y]=++cnt;
        addEdge(pre[x],suf[y]);
        vec[pre[x]].push_back(i);
        vec[suf[y]].push_back(i);
        left[pre[x]]=1;
        // printf("%d %d\n", pre[x],suf[y]);
    }
}
int main(void) {
    // freopen("data.in","r",stdin);
    // freopen("myp.out","w",stdout);
    init();
    printf("%d\n", hungary());
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值