【2014东莞市选】分组

Description:

有n个字符串,给这些字符串分组,使得每个字符串属于且仅属于一个组。
对于一个合法的分组,至少满足以下两个条件种的一个:
1. 所有字符串的k前缀相同(即前k个字母相同)
2. 所有字符串的k后缀相同(即后k个字母相同)
3.你需要给这些字符串分组,使得所分的组数最少。
n<=5000,k<=550

题解:

先离散一下前缀和后缀。

用双哈希或排序。

对于一个串,它的前缀和后缀离散后的点至少有一个要选,这是经典模型。

这是个二分图。

建立超级源S、超级汇T。

S到离散后的前缀连流量为1的边。

离散后的后缀到T连流量为1的边。

如果x,y至少有一个要选,x到y连正无穷的边。

跑最大流=最小割就是答案,证明显然。

最后从S出发,找到属于S集的点就可以判断出哪些边被割了来确定分组。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 100005, M = 555, E = 5e6 + 5, INF = 1 << 30;

const ll p1 = 26455189, mo1 = 43820603;
const ll p2 = 9781909, mo2 = 38577701;

int n, m, k, td, tf; char str[M];

struct node {
    ll x, y, i;
} a[N], b[N];

int c[N], d[N];

bool cmp(node a, node b) {
    if(a.x < b.x) return 1;
    if(a.x > b.x) return 0;
    return a.y < b.y;
}

int final[N], to[E], next[E], r[E], tot = 1;
int S, T, co[N], dd[N], cur[N], ans;

void link(int x, int y, int z) {
    next[++ tot] = final[x], to[tot] = y, r[tot] = z, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, r[tot] = 0, final[y] = tot;
}

int dg(int x, int flow) {
    if(x == T) return flow;
    int use = 0;
    for(int i = cur[x]; i; i = next[i], cur[x] = i) {
        int y = to[i];
        if(dd[y] + 1 == dd[x] && r[i]) {
            int tmp = dg(y, min(flow - use, r[i]));
            use += tmp; r[i] -= tmp; r[i ^ 1] += tmp;
            if(use == flow) return use;
        }
    }
    cur[x] = final[x];
    if(!(-- co[dd[x]])) dd[T] = T;
    ++ co[++ dd[x]];
    return use;
}

int bz[N];

void dfs(int x) {
    if(bz[x]) return;
    bz[x] = 1;
    for(int i = final[x]; i; i = next[i])
        if(r[i]) dfs(to[i]);
}

struct edge {
    int final[N], to[N], next[N], tot;
    void link(int x, int y) {next[++ tot] = final[x], to[tot] = y, final[x] = tot;}
} e;

int bx[N];

int main() {
    freopen("group.in", "r", stdin);
    freopen("group.out", "w", stdout);
    scanf("%d %d", &n, &k);
    fo(i, 1, n) {
        scanf("%s", str + 1); m = strlen(str + 1);
        fo(j, 1, k) {
            a[i].x = (a[i].x * p1 + str[j] - 'A') % mo1;
            a[i].y = (a[i].y * p2 + str[j] - 'A') % mo2;
        }
        fd(j, m, m - k + 1) {
            b[i].x = (b[i].x * p1 + str[j] - 'A') % mo1;
            b[i].y = (b[i].y * p2 + str[j] - 'A') % mo2;
        }
        a[i].i = b[i].i = i;
    }
    sort(a + 1, a + n + 1, cmp), sort(b + 1, b + n + 1, cmp);
    fo(i, 1, n) {
        if(i == 1 || a[i].x != a[i - 1].x || a[i].y != a[i - 1].y)
            td ++;
        c[a[i].i] = td;
    }
    tf = td;
    fo(i, 1, n) {
        if(i == 1 || b[i].x != b[i - 1].x || b[i].y != b[i - 1].y)
            td ++;
        d[b[i].i] = td;
    }
    fo(i, 1, n) e.link(c[i], i), e.link(d[i], i);
    S = td + 1; T = S + 1;
    fo(i, 1, tf) link(S, i, 1);
    fo(i, tf + 1, td) link(i, T, 1);
    fo(i, 1, n) link(c[i], d[i], INF);
    co[0] = T;
    for(; dd[T] < T;) ans += dg(S, INF);
    dfs(S);
    fo(i, 1, tf) bz[i] = !bz[i];
    printf("%d\n", ans);
    fo(i, 1, td) if(bz[i]) {
        int ss = 0;
        for(int j = e.final[i]; j; j = e.next[j])
            if(!bx[e.to[j]]) ss ++;
        printf("%d", ss);
        for(int j = e.final[i]; j; j = e.next[j])
            if(!bx[e.to[j]]) bx[e.to[j]] = 1, printf(" %d", e.to[j]);
        printf("\n");
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值