闲话 22.12.22

闲话

想怎么开场呢
然后突然想到一个直切正题的引言:
”我们闲话少说……“

那就上今天的推歌吧!
Feedback/Artery by AVTechNO! & 镜音双子
有些人比较听不惯早期(?)虚拟歌姬的这种电子感
当然现在也有以 Synthesizer V AI 为例的神经网络优化歌声合成技术
但说实在的 这种机械感的确在很多情况下不可或缺

完 不会组织语言

后缀数组附题

对 来补点后缀数组的题。

前情

[SDOI2008] Sandy 的卡片

给定 \(n\) 个长度不超过 \(m\) 字符串,字符范围为 \([0, 2000]\)。需要找出这些字符串的最长的差分相同公共子串。

\(n, m \le 1000\)

差分相同,因此作差分后转化为相同子串,答案 \(+1\) 即可。

然后需要找到这些字符串的最长公共子串。

使用 SA 的话可以先把所有字符串串起来(中间插入不存在的字符),然后跑出来 \(\text{height}\) 数组。然后二分答案转化为判定。众所周知 \(\text{height}\) 数组表示一个位置队医的后缀和其前一位对应后缀的 \(\text{LCP}\) 长度,因此如果存在 \(n\) 个起点彼此不同的起点,满足 \(\text{height} \ge mid\),则 \(mid\) 是可以取到的。
二分答案即可。

使用 SAM 的话就先拿一个串建自动机,随后将其他串扔上去匹配就行。

SA code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 3e6 + 10, inf = 1e9 + 7;
int n, t1, str[N], id[N], mlc, l, r, mn, mx, siz;
vector<int> raw[N];

int sa[N], rk[N], height[N];
void get_sa(int ch[], int n, int sa[], int rk[]) {
    int m = siz;
    static int cnt[N], rk2[N], key[N], id[N];
    memset(cnt+1, 0, m << 2);
    rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
    rep(i,1,m) cnt[i] += cnt[i-1];
    pre(i,n,1) sa[cnt[rk[i]] --] = i;
    for (int w = 1; ; w <<= 1) {
        int p = 0;
        for (register int i = n; i > n - w; -- i) id[++p] = i;
        rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt + 1, 0, m << 2);
        rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]]++;
        rep(i,1,m) cnt[i] += cnt[i-1];
        pre(i,n,1) sa[cnt[key[i]] --] = id[i];
        memcpy(rk2+1, rk+1, n << 2);
        p = 0;
        rep(i,1,n) rk[sa[i]] = (
                        rk2[sa[i]] == rk2[sa[i-1]] and 
                        rk2[sa[i] + w] == rk2[sa[i-1] + w]
                     ) ? p : ++p;
        if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
        m = p;
    }
}

void get_height(int s[], int n) {
    for (int i = 1, k = 0; i <= n; i++) {
        if (rk[i] == 0) continue;
        if (k) k--;
        while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
        height[rk[i]] = k;
    }
}

bool vis[N];
vector <int> stk;
bool check(int val) {
	for (auto x : stk) vis[x] = 0; stk.clear();
	rep(i,1,mlc) {
		if (height[i] < val) { for (auto x : stk) vis[x] = 0; stk.clear(); }
		if (!vis[id[sa[i]]]) {
			vis[id[sa[i]]] = 1;
			stk.push_back(id[sa[i]]);
			if (stk.size() >= n) return true;
		}
	} return false;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n;
	l = mx = 0, r = mn = inf;
	rep(i,1,n) {
		cin >> t1; raw[i].resize(t1);
		rep(j,0,raw[i].size() - 1) cin >> raw[i][j], (j > 0) && (mx = max(mx, raw[i][j] - raw[i][j-1]), mn = min(mn, raw[i][j] - raw[i][j-1]), true);
		r = min(r, (int)raw[i].size() - 1);
	} 
	rep(i,1,n) {
		rep(j,1,raw[i].size() - 1) str[++mlc] = raw[i][j] - raw[i][j-1], id[mlc] = i;
		str[++mlc] = ++mx;
	} 
	rep(i,1,mlc) str[i] = str[i] - mn + 1, siz = max(siz, str[i]);
	get_sa(str, mlc, sa, rk); get_height(str, mlc);
	int l = 0, r = siz + 1, mid, ans;
	while (l <= r) {
		mid = l + r >> 1;
		if (check(mid)) ans = mid, l = mid + 1;
		else r = mid - 1;
	} cout << ans + 1 << endl;
}



[SCOI2012] 喵星球上的点名

题面长,不粘。

我写的是 SA + 莫队。跑得挺快?因为常数巨小冲到了最优解。

首先把母串(喵星人的名字)拼起来,中间放个彼此不同的大数字,然后跑 SA。给到一个点名串,我们就可以在 \(\text{sa}\) 数组上通过不断二分的方式定位一个区间,满足这些区间里任意元素对应的后缀和点名串的 \(\text{LCP}\) 是这个点名串本身。这个区间里所有串都能做贡献,这样我们就将问题转化成了颜色个数和颜色贡献区间数了。

颜色个数可以直接做莫队。颜色贡献的区间数可以差分。当一个颜色可以贡献时加入后缀长度,不能贡献时减去即可。

总时间复杂度 \(O(SA(n) + q(\log q + \sqrt n))\)

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int n, siz, m, q, l, r, t1, t2, t3, a[N], mlc, id[N], tmp = 1e4;

struct queries {
	int l, r, bel, id;
	bool operator < (const queries & b) const {
		if (bel == b.bel) return r < b.r;
		return l < b.l;
	}
} qy[N];

int sa[N], rk[N];
void getsa(int ch[], int n, int sa[] = sa, int rk[] = rk) {
    int m = tmp;
    static int cntmx[N], rk2[N], key[N], id[N];
    memset(cntmx+1, 0, m * sizeof(*cntmx));
    rep(i,1,n) rk[i] = ch[i], cntmx[rk[i]]++;
    rep(i,1,m) cntmx[i] += cntmx[i-1];
    pre(i,n,1) sa[cntmx[rk[i]] --] = i;
    for (int w = 1; ; w <<= 1) {
        int p = 0;
        for (register int i = n; i > n - w; -- i) id[++p] = i;
        rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cntmx + 1, 0, m * sizeof(*cntmx));
        rep(i,1,n) key[i] = rk[id[i]], cntmx[key[i]]++;
        rep(i,1,m) cntmx[i] += cntmx[i-1];
        pre(i,n,1) sa[cntmx[key[i]] --] = id[i];
        memcpy(rk2+1, rk+1, n * sizeof(*rk2));
        p = 0;
        rep(i,1,n) rk[sa[i]] = (
                        rk2[sa[i]] == rk2[sa[i-1]] and 
                        rk2[sa[i] + w] == rk2[sa[i-1] + w]
                     ) ? p : ++p;
        if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
        m = p;
    }
}

int cnt[N], ans1[N], ans2[N], ret;
#define add(x, idt) \
	++ cnt[id[x]];\
	if (cnt[id[x]] == 1) ret ++, ans2[id[x]] += q - idt + 1;
#define del(x, idt)\
	-- cnt[id[x]];\
	if (cnt[id[x]] == 0) ret --, ans2[id[x]] -= q - idt + 1;

signed main() {
	cin >> n >> m;
	rep(i,1,n) rep(j,0,1) {
		cin >> t1;
		rep(k,1,t1) cin >> a[++ mlc], id[mlc] = i;
		a[++ mlc] = ++ tmp;
	}
	siz = sqrt(n);
	getsa(a, mlc);
	rep(i,1,m) {
		cin >> t1;
		int L, R, j;
		for (L = 1, j = 1, R = mlc; j <= t1; ++ j) {
			cin >> t2; 
			int l = L, r = R, mid;
			while (l <= r) {
				mid = l + r >> 1;
				if (a[sa[mid] + j - 1] < t2) l = mid + 1;
				else r = mid - 1;
			}
			t3 = l; l = L, r = R;
			while (l <= r) {
				mid = l + r >> 1;
				if (a[sa[mid] + j - 1] <= t2) l = mid + 1;
				else r = mid - 1;
			}
			L = t3, R = r;
	 	}
		if (L <= R) qy[++ q] = { L, R, (L - 1) / siz + 1, i };
	} 
	sort(qy + 1, qy + 1 + q);
	for (int i = 1, l = 1, r = 0; i <= q; ++ i) {
		while (qy[i].l < l) {
			-- l;
			add(sa[l], i);
		} 
		while (r < qy[i].r) {
			++ r;
			add(sa[r], i);
		}
		while (qy[i].l > l) {
			del(sa[l], i);
			++ l;
		} 
		while (r > qy[i].r) {
			del(sa[r], i);
			-- r;
		} ans1[qy[i].id] = ret;
	}
	rep(i,1,m) cout << ans1[i] << '\n';
	rep(i,1,n) cout << ans2[i] << ' ';
}



[AHOI2013] 差异

给定一个长度为 \(n\) 的字符串 \(S\),令 \(T_i\) 表示它从第 \(i\) 个字符开始的后缀。求

\[\sum_{1\le i<j\le n}\text{len}(T_i)+\text{len}(T_j)-2\times\text{LCP}(T_i,T_j) \]

其中,\(\text{len}(a)\) 表示字符串 \(a\) 的长度,\(\text{LCP}(a,b)\) 表示字符串 \(a\) 和字符串 \(b\) 的最长公共前缀。

\(2\le n\le 500000\),且 \(S\) 中均为小写字母。

原式其实就等于

\[\sum_{1\le i<j\le n} (i + j) -2\times\sum_{1\le i<j\le n}\text{LCP}(T_i,T_j) = \frac 12 n(n - 1)(n + 1) -2\times\sum_{1\le i<j\le n}\text{LCP}(T_i,T_j) \]

因此考虑 \(\text{LCP}\) 如何计算。首先求 SA 得到 \(\text{height}\),可以发现 \(\text{LCP}(T_i,T_j) = \min\limits_{k=i+1}^j \text{height}(k)\)
套路确定左端点后线段树维护各个右端点答案之和。然后区间取最小值区间求和就行了。

为什么你放着好写的 SAM 做法和快的单调栈做法不写反而写这一坨东西呢?
主要是想复习吉司机线段树了。真不会写了啊啊啊
而且真不用动脑子

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e6 + 10;
char ch[N];
int n, sa[N], rk[N], height[N];
void getsa(char ch[] = ch, int sa[] = sa, int rk[] = rk) {
    int m = 127;
    static int cntmx[N], rk2[N], key[N], id[N];
    memset(cntmx+1, 0, m * sizeof(*cntmx));
    rep(i,1,n) rk[i] = ch[i], cntmx[rk[i]]++;
    rep(i,1,m) cntmx[i] += cntmx[i-1];
    pre(i,n,1) sa[cntmx[rk[i]] --] = i;
    for (int w = 1; ; w <<= 1) {
        int p = 0;
        for (register int i = n; i > n - w; -- i) id[++p] = i;
        rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cntmx + 1, 0, m * sizeof(*cntmx));
        rep(i,1,n) key[i] = rk[id[i]], cntmx[key[i]]++;
        rep(i,1,m) cntmx[i] += cntmx[i-1];
        pre(i,n,1) sa[cntmx[key[i]] --] = id[i];
        memcpy(rk2+1, rk+1, n * sizeof(*rk2));
        p = 0;
        rep(i,1,n) rk[sa[i]] = (
                        rk2[sa[i]] == rk2[sa[i-1]] and 
                        rk2[sa[i] + w] == rk2[sa[i-1] + w]
                     ) ? p : ++p;
        if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
        m = p;
    }
}

void getheight() {
    for (int i = 1, k = 0; i <= n; i++) {
        if (rk[i] == 0) continue;
        if (k) k--;
        while (ch[i + k] == ch[sa[rk[i] - 1] + k]) k++;
        height[rk[i]] = k;
    }
}


const int inf = 1e8;
class SegmentBeats {
    #define ls (p << 1)
    #define rs (p << 1 | 1)
    #define lzy(p) seg[p].lzy
    #define sum(p) seg[p].sum
    #define firmx(p) seg[p].firmx
    #define secmx(p) seg[p].secmx
    #define cntmx(p) seg[p].cntmx

  private :

    struct node {
        int firmx, secmx, cntmx, lzy, sum;
    } seg[N << 2];

    void ps_p(int p) {
        sum(p) = sum(ls) + sum(rs);
        if (firmx(ls) == firmx(rs)) {
            firmx(p) = firmx(ls);
            secmx(p) = max(secmx(ls), secmx(rs));
            cntmx(p) = cntmx(ls) + cntmx(rs);
        } else if (firmx(ls) > firmx(rs)) {
            firmx(p) = firmx(ls);
            secmx(p) = max(secmx(ls), firmx(rs));
            cntmx(p) = cntmx(ls);
        } else {
            firmx(p) = firmx(rs);
            secmx(p) = max(firmx(ls), secmx(rs));
            cntmx(p) = cntmx(rs);
        }
    }

    void ps_t(int p, int v) {
        if (firmx(p) <= v) return;
        sum(p) -= (firmx(p) - v) * cntmx(p);
        firmx(p) = lzy(p) = v;
    }

    void ps_d(int p) {
        if (lzy(p) == -inf) return;
        ps_t(ls, lzy(p)), ps_t(rs, lzy(p));
        lzy(p) = -inf;
    }

    void upd(int p, int l, int r, int L, int R, int v) {
        if (firmx(p) <= v) return;
        if (L <= l and r <= R and secmx(p) < v) return ps_t(p, v), void();
        ps_d(p); int mid = l + r >> 1;
        if (L <= mid) upd(ls, l, mid, L, R, v);
        if (mid < R) upd(rs, mid + 1, r, L, R, v);
        ps_p(p);
    }

    int qry(int p, int l, int r, int L, int R) {
        if (L <= l and r <= R) return sum(p);
        ps_d(p); int mid = l + r >> 1, ret = 0;
        if (L <= mid) ret += qry(ls, l, mid, L, R);
        if (mid < R) ret += qry(rs, mid + 1, r, L, R);
        ps_p(p);
        return ret; 
    }

  public :
    
    void build(int p = 1, int l = 1, int r = n) {
        lzy(p) = -inf;
        if (l == r) {
            sum(p) = firmx(p) = inf, secmx(p) = -inf, cntmx(p) = 1;
            return;
        } int mid = l + r >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);
        ps_p(p);
    }

    void upd(int l, int r, int v) { upd(1, 1, n, l, r, v); }
    int qry(int l, int r) { return qry(1, 1, n, l, r); }
} Tr;

signed main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> ch + 1; n = strlen(ch + 1);
    getsa(); getheight();
    int ans = 0;
    Tr.build();
    pre(i,n,1) {
        if (i < n) ans += Tr.qry(i + 1, n);
        Tr.upd(i, n, height[i]); 
    }
    cout << 1ll * n * (n - 1) * (n + 1) / 2 - 2 * ans << endl; 
}



相似子串

给一个字符串 \(S\)。有 \(q\) 次询问,每次询问给定两个排名 \(rk_1, rk_2\)。你需要回答将 \(S\) 的所有本质不同字符串排序后排名分别为 \(rk_1, rk_2\) 的两个字符串 \(s_1, s_2\)\(|最长公共前缀|^2 + |最长公共后缀|^2\) 的值。

\(N≤100000, Q≤100000\),字符串 \(S\) 只由小写字母组成。

首先我们需要定位这个串是哪个前缀的子串。
做 SA 求出需要的信息,然后就能定位了。我们按照 \(\text{rk}\) 一个一个地确定每个后缀能贡献的子串数。显然贡献就是 \(后缀长度 - \text{LCP } 长度\)。也就是 \((n - \text{sa} + 1) - \text{height}\)。求出这个后作前缀和,我们就能通过二分找到确定排名的子串串首所在位置以及长度了。记作 \(id\)\(len\)

随后考虑如何计算答案。
首先最长公共前缀是简单的,我们直接求 \([id_1 + 1, id_2]\) 段内的 \(\text{height}\) 最小值即可。
最长后缀呢?知道 \(id\)\(len\) 后不难得到其在翻转串上的串首位置。因此我们对反串求出 SA,对应到反串上求得 \(\text{height}\) 最小值。

两个最小值分别和 \(\min(len_1, len_2)\)\(\min\) 即可。

因为得对反串求解 SA,这里封装了一下。然后想着封装都封装了,\(\text{build}()\) 就是个接口,为什么不打一下 SA-IS 呢?
然后就打了一下。很难相信的是 bzoj 上没人实现 SA-IS,混了个最优解。

code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 10;
int n, q;
ll t1, t2;

template <typename _Tp = char, const int siz = 127>
struct SA {
	_Tp ch[N];
	int n = -1, sa[N], rk[N], heg[N];
	int cnt[N], rk2[N], key[N], id[N];

	_Tp* begin() { return ch + 1; }
	const _Tp operator [] (const int & p) const { return ch[p]; }
	_Tp & operator [] (const int & p) { return ch[p]; }

	void getsa() {
		int m = siz;
		memset(cnt + 1, 0, sizeof(*cnt) * m);
		rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
		rep(i,1,m) cnt[i] += cnt[i - 1];
		pre(i,n,1) sa[cnt[rk[i]] --] = i;
		for (int w = 1, p; ; w <<= 1) {
			p = 0;
			pre(i,n,n-w+1) id[++ p] = i;
			rep(i,1,n) if (sa[i] > w) id[++ p] = sa[i] - w;
			memset(cnt + 1, 0, sizeof(*cnt) * m);
			rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]] ++;
			rep(i,1,m) cnt[i] += cnt[i - 1];
			pre(i,n,1) sa[cnt[key[i]] --] = id[i];
			memcpy(rk2 + 1, rk + 1, sizeof(*rk2) * n);
			p = 0;
			rep(i,1,n) rk[sa[i]] = (
					rk2[sa[i]] == rk2[sa[i - 1]] and 
					rk2[sa[i] + w] == rk2[sa[i - 1] + w]
					) ? p : ++ p;
			if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
			m = p;
		}
	}

	void getheg() {
		for (int i = 1, k = 0; i <= n; ++ i) {
			if (rk[i] == 0) continue;
			if (k) -- k;
			while (ch[i + k] == ch[sa[rk[i] - 1] + k]) ++ k;
			heg[rk[i]] = k;
		}
	}

	int st[N][20], lgv[N];
	void build() {
		if (n <= 0) n = strlen(begin());
		getsa(); getheg();
		rep(i,1,n) st[i][0] = heg[i];
		rep(i,2,n) lgv[i] = lgv[i >> 1] + 1;
		rep(i,1,lgv[n]) for (int j = 1; j + (1 << i) - 1 <= n; ++ j) 
			st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
	}

	int query(int l, int r) {
		if (l == r) return n + 1;
		if (l > r) swap(l, r); ++ l;
		int d = lgv[r - l + 1];
		return min(st[l][d], st[r - (1 << d) + 1][d]);
	}
}; 
SA<char, 127> str[2];

ll lv[N], rv[N];
int get(ll L) {
	int l = 1, r = n, mid;
	while (l <= r) {
		mid = l + r >> 1;
		if (lv[mid] <= L and L <= rv[mid]) return mid;
		else if (lv[mid] > L) r = mid - 1;
		else l = mid + 1;
	} assert(0);
}

signed main() {
	ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
	cin >> str[0].n >> q >> str[0].begin(); 
	str[1].n = n = str[0].n;
	rep(i,1,str[1].n) str[1][i] = str[0][n - i + 1]; str[1][str[1].n + 1] = 0;
	str[0].build(); str[1].build();
	rep(i,1,n) lv[i] = rv[i - 1] + 1, rv[i] = lv[i] + n - str[0].sa[i] - str[0].heg[i];
	while (q --) {
		cin >> t1 >> t2;
		if (t1 > rv[n] or t2 > rv[n]) {
			cout << "-1\n";
			continue;
		} 
		int id1 = get(t1), id2 = get(t2);
		int len1 = str[0].heg[id1] + t1 - lv[id1] + 1, 
			len2 = str[0].heg[id2] + t2 - lv[id2] + 1;
		int ans1 = min( { len1, len2, str[0].query(id1, id2) } ),
			ans2 = min( { len1, len2, str[1].query(str[1].rk[ n - (str[0].sa[id1] + len1 - 1) + 1 ], str[1].rk[ n - (str[0].sa[id2] + len2 - 1) + 1] ) } );
		cout << 1ll * ans1 * ans1 + 1ll * ans2 * ans2 << '\n';
	}
}
struct SA with SA-IS
template <typename _Tp = char, const int siz = 127>
struct SA {
	_Tp ch[N];
	int n = -1, sa[N], rk[N], heg[N];
	int a[N], tr[siz + 5], cur[N];
	int sum[N], bse[N << 1], *_t = bse;

	_Tp* begin() { return ch + 1; }
	const _Tp operator [] (const int & p) const { return ch[p]; }
	_Tp & operator [] (const int & p) { return ch[p]; }

	#define _MemAlloc(pool, size, tag) (tag = pool, pool += size)
	#define pushs(x) (sa[cur[a[x]] --] = x)
	#define pushi(x) (sa[cur[a[x]] ++] = x)
	#define inds(lms)  		\
		rep(i,1,n) sa[i] = -1, sum[i] = 0; \
		rep(i,1,n) sum[a[i]] ++; \
		rep(i,1,n) sum[i] += sum[i - 1]; \
		rep(i,1,n) cur[i] = sum[i]; \
		pre(i,m,1) pushs(lms[i]); \
		rep(i,1,n) cur[i] = sum[i - 1] + 1; \
		rep(i,1,n) if (sa[i] > 1 and !tp[sa[i] - 1]) pushi(sa[i] - 1); \
		rep(i,1,n) cur[i] = sum[i]; \
		pre(i,n,1) if (sa[i] > 1 and tp[sa[i] - 1]) pushs(sa[i] - 1);

	inline void SA_IS(int n, int* a) {
		int* tp; _MemAlloc(_t, n + 1, tp); tp[n] = 1;
		int* p; _MemAlloc(_t, n + 2, p);
		pre(i,n-1,1) tp[i] = (a[i] == a[i + 1]) ? tp[i + 1] : (a[i] < a[i + 1]);
		int m = 0, tot = 0;
		rep(i,1,n) rk[i] = (tp[i] and !tp[i - 1]) ? (p[++ m] = i, m) : -1;
		inds(p);
		int* a1; _MemAlloc(_t, m + 1, a1);
		p[m + 1] = n;
		for (int i = 1, x, y; i <= n; ++ i) if ((x = rk[sa[i]]) != -1) {
			if (tot == 0 or p[x + 1] - p[x] != p[y + 1] - p[y]) ++ tot;
			else for (int p1 = p[x], p2 = p[y]; p2 <= p[y + 1]; ++ p1, ++ p2)
				if ((a[p1] << 1 | tp[p1]) != (a[p2] << 1 | tp[p2])) { ++ tot; break; }
			a1[y = x] = tot;
		}
		if (tot == m) rep(i,1,m) sa[a1[i]] = i;
		else SA_IS(m, a1);
		rep(i,1,m) a1[i] = p[sa[i]];
		inds(a1);
	}

	int st[N][20], lgv[N];
	void build() {
		if (n <= 0) n = strlen(begin());

		rep(i,1,n) tr[ch[i]] = 1;
		rep(i,1,siz + 1) tr[i] += tr[i - 1];
		rep(i,1,n) a[i] = tr[ch[i]] + 1;
		a[n + 1] = 1;
		SA_IS(n + 1, a); 
		rep(i,1,n) sa[i] = sa[i + 1];
		rep(i,1,n) rk[sa[i]] = i;
		
		for (int i = 1, k = 0; i <= n; ++ i) {
			if (rk[i] == 0) continue;
			if (k) -- k;
			while (ch[i + k] == ch[sa[rk[i] - 1] + k]) ++ k;
			heg[rk[i]] = k;
		}

		rep(i,1,n) st[i][0] = heg[i];
		rep(i,2,n) lgv[i] = lgv[i >> 1] + 1;
		rep(i,1,lgv[n]) for (int j = 1; j + (1 << i) - 1 <= n; ++ j) 
			st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
	}

	int query(int l, int r) {
		if (l == r) return n + 1;
		if (l > r) swap(l, r); ++ l;
		int d = lgv[r - l + 1];
		return min(st[l][d], st[r - (1 << d) + 1][d]);
	}
}; 



[NOI2015] 品酒大会

长长长,不粘。

SA 的话就写 SA 的做法吧。
SAM tomorrow(?) !

一杯酒可以看作一个后缀,两杯酒是 \(r\) 相似的,当且仅当从这两杯酒所在位置开始的后缀的 \(\text{LCP} \ge r\)
我们发现,两个串是 \(r \ge 0\) 相似的,那必定是 \(r - 1\) 相似的。因此可以先求出恰好 \(r\) 相似的信息,再作后缀和得到最终答案。

考虑两个后缀 \(p < q\)\(\min\limits_{i = p+1}^q \text{height}(i)\) 处统计答案。这里不妨 \(p = p + 1\)。然后我们发现一段信息会在最小值处统计,这启发我们按由大到小的顺序插入 \(\text{height}\) 值。插入 \(\text{height} = k\) 时考虑是否能合并左右两段。每段记录长度,合并时可以选出的酒数量是两段长度之积,这里需要记录一个历史数量和。然后最大值考虑有负数,是两段的最大值之积和最小值之积中的较大值。
这可以很简单地使用并查集维护。

取后缀和/后缀 \(\max\) 可以得到答案。

code
#include <bits/stdc++.h>
using namespace std; using ll = long long; 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 5e6 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, a[N];
ll ans1[N], ans2[N], val1, val2 = -infll;
char ch[N];
vi buk[N];

int sa[N], rk[N], height[N];
void getsa(char ch[] = ch, int sa[] = sa, int rk[] = rk) {
    int m = 127;
    static int cnt[N], rk2[N], key[N], id[N];
    memset(cnt+1, 0, m << 2);
    rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
    rep(i,1,m) cnt[i] += cnt[i-1];
    pre(i,n,1) sa[cnt[rk[i]] --] = i;
    for (int w = 1; ; w <<= 1) {
        int p = 0;
        for (register int i = n; i > n - w; -- i) id[++p] = i;
        rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt + 1, 0, m << 2);
        rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]]++;
        rep(i,1,m) cnt[i] += cnt[i-1];
        pre(i,n,1) sa[cnt[key[i]] --] = id[i];
        memcpy(rk2+1, rk+1, n << 2);
        p = 0;
        rep(i,1,n) rk[sa[i]] = (
                        rk2[sa[i]] == rk2[sa[i-1]] and 
                        rk2[sa[i] + w] == rk2[sa[i-1] + w]
                     ) ? p : ++p;
        if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
        m = p;
    }
}

void getheight(char s[] = ch) {
    for (int i = 1, k = 0; i <= n; i++) {
        if (rk[i] == 0) continue;
        if (k) k--;
        while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
        height[rk[i]] = k;
    }
}

int fa[N], mx[N], mn[N], sum[N];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
	x = find(x), y = find(y);
	if (x == y) return;
	val1 += 1ll * sum[x] * sum[y], val2 = max( { val2, 1ll * mx[x] * mx[y], 1ll * mn[x] * mn[y] } );
	fa[x] = y, sum[y] += sum[x], mx[y] = max(mx[x], mx[y]), mn[y] = min(mn[x], mn[y]);
}

signed main() {
	cin >> n >> ch + 1;
	rep(i,1,n) cin >> a[i];
	getsa(), getheight();
	rep(i,1,n) fa[i] = i, sum[i] = 1, mx[i] = mn[i] = a[sa[i]], buk[height[i]].eb(i);
	pre(i,n-1,0) {
		for (auto x : buk[i]) merge(x, x - 1);
		if (val1) ans1[i] = val1, ans2[i] = val2;
	}
	rep(i,0,n-1) cout << ans1[i] << ' ' << ans2[i] << '\n';
}



[ZJOI2009] 对称的正方形

给定一个 \(n\times m\) 的矩形,求上下对称且左右对称的正方形子矩形个数。

\(1\le n, m \le 10^3\)

发现数据范围支持 \(O(nm \log n)\),因此考虑枚举回文中心后二分判断。判断时需要 \(O(1)\) 检查两个子矩形是否相同。这不得不令人想到哈希。

于是出现了二维哈希这种东西。具体地,我们需要选择两个底数(不要两个模数),横着作一遍哈希,竖着作一遍哈希。查询时类似二维前缀和。

然后没了。具体看代码。

当然你也可以二维 manacher。luogu 题解

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e3 + 5, bse1 = 131, bse2 = 13131;
int n, m, l, r, mid;
long long ans, pw1[N], pw2[N];

struct hash_2d {
	int v[N][N]; 
	long long hsh[N][N];
	
	int* operator[] (const int & p) { return v[p]; }
	const int* operator[] (const int & p) const { return v[p]; }

	void init() {
		rep(i,1,n) rep(j,1,m) hsh[i][j] = hsh[i][j - 1] * bse1 + v[i][j];
		rep(i,1,n) rep(j,1,m) hsh[i][j] = hsh[i - 1][j] * bse2 + hsh[i][j];
	}

	long long get(int x1, int y1, int x2, int y2) {
		return hsh[x2][y2] - hsh[x1 - 1][y2] * pw2[x2 - x1 + 1] - hsh[x2][y1 - 1] * pw1[y2 - y1 + 1] 
			+ hsh[x1 - 1][y1 - 1] * pw1[x2 - x1 + 1] * pw2[y2 - y1 + 1];
	}
} a, b, c;

bool check(int x, int y, int len) { // (x - len + 1, y - len + 1) -> (x, y)
	if (x > n or y > m or x < len or y < len) return 0;
	long long tmp1 = a.get(x - len + 1, y - len + 1, x, y);
	int tx = n - (x - len);
	long long tmp2 = b.get(tx - len + 1, y - len + 1, tx, y);
	int ty = m - (y - len);
	long long tmp3 = c.get(x - len + 1, ty - len + 1, x, ty);
	return (tmp1 == tmp2) and (tmp2 == tmp3);
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	pw1[0] = pw2[0] = 1;
	rep(i,1,1000) pw1[i] = pw1[i - 1] * bse1;
	rep(i,1,1000) pw2[i] = pw2[i - 1] * bse2;
	cin >> n >> m;
	rep(i,1,n) rep(j,1,m) cin >> a[i][j];
	rep(i,1,n) rep(j,1,m) b[i][j] = a[n - i + 1][j], c[i][j] = a[i][m - j + 1];
	a.init(); b.init(); c.init();
	rep(i,1,n) rep(j,1,m) {
		l = 1, r = max(n, m);
		while (l <= r) {
			mid = l + r >> 1;
			if (check(i + mid, j + mid, mid << 1)) l = mid + 1;
			else r = mid - 1;
		} ans += l - 1;
	}
	rep(i,1,n) rep(j,1,m) {
		l = 1, r = max(n, m);
		while (l <= r) {
			mid = l + r >> 1;
			if (check(i + mid, j + mid, mid << 1 | 1)) l = mid + 1;
			else r = mid - 1;
		} ans += l - 1;
	}
	cout << ans + m * n << '\n';
}
posted @ 2022-12-22 18:49  joke3579  阅读(55)  评论(0编辑  收藏  举报