jzoj3149 【GDKOI2013】 大山王国的城市规划 (回文树,最大独立集转最小路径覆盖转二分图)

86 篇文章 0 订阅
12 篇文章 1 订阅

题意

给一个1e5的字符串,选出尽量多的回文串(可以相交)使得他们两两互不为子串。

算法

先建出pam,然后将fail边与转移视作有向边(fail为父亲指向儿子),然后可以发现包含一个回文串x的其他回文串都可以走到x的那个点
转化为dag求最大独立集。根据dilworth定理(好像叫这个?),最大反链 = 最小链覆盖(最大独立集 = 最小路径覆盖),于是即为求最小链覆盖。

拆出入点,初始有n条路径,每有一两两匹配即合并两条路径,于是最大独立集即为点数 - 最大匹配数。

主要是存一下pam的板子,0为偶根,1为奇根,fail[偶] = 1,len[0] = -1。这种写法不用加特判,非常舒服。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5 + 100, inf = 1e9;
int t,n;
char s[N];
struct pamT{
    int len[N], c[N][26], fail[N], tot, last;
    void insert(int x) {
        int r = s[x] - 'a';
        while (s[x] != s[x - len[last] - 1]) 
            last = fail[last];
        if (!c[last][r]) {
            ++tot; len[tot] = len[last] + 2;
            int k = fail[last];
            while (s[x] != s[x - len[k] - 1]) k = fail[k];
            fail[tot] = c[k][r];

            c[last][r] = tot;
            last = tot;
        } else last = c[last][r];
    }

    void init() {
        memset(c, 0, sizeof c); //0偶, 1奇 
        tot = 1, fail[0] = 1;
        len[1] = -1;
        last = 0;
    }
} pam;

int tot,S,T;
const int E = 1e5 * 26 * 2;
int final[N * 2], to[E], nex[E], e[E], cnt;

void link0(int x,int y,int ee) {
    to[++tot] = y, nex[tot] = final[x], final[x] = tot;
    e[tot] = ee;
}

void link(int x,int y,int ee) {
    link0(x, y, ee), link0(y, x, 0);
//  cout<<x<<" "<<y<<endl;
}

void build() {
    tot = 1;
    memset(final,0,sizeof final);
    cnt = pam.tot; 

    S = cnt * 2 + 1, T = S + 1;
    for (int i = 2; i <= cnt; i++) {
        link(S, i, 1);
        link(i + cnt, T, 1);

        if (pam.fail[i] > 1) {
            link(pam.fail[i], cnt + i, 1);
        }
        for (int j = 0; j < 26; j++) if (pam.c[i][j]) {
            link(i, cnt + pam.c[i][j], 1);
        }
    }
}

int gap[2 * N], label[2 * N];
int go(int x,int wait) {
    if (x == T) return wait;
    int used = 0;
    for (int i = final[x]; i; i = nex[i]) if (e[i] && label[x] == label[to[i]] + 1) {
        int take = go(to[i], min(e[i], wait - used));
        used += take;
        e[i] -= take, e[i ^ 1] += take;
        if (used == wait) return wait;
    }
    if ((--gap[label[x]++]) == 0) label[0] = inf;
    gap[label[x]] ++;
    return used;
}

int maxflow() {
    int ret = 0;
    memset(gap, 0, sizeof gap); gap[0] = cnt * 2;
    memset(label, 0, sizeof label);
    while (label[0] != inf) 
        ret += go(S, inf);
    return ret;
}

int main() {
    freopen("pam.in","r",stdin);
    for (cin>>t; t; t--) {
        pam.init();
        scanf("%s", s + 1);
        n = strlen(s + 1);
        for (int i = 1; i <= n; i++) pam.insert(i);
        build();
        printf("%d\n",cnt - 1 - maxflow());
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值