闲话 22.11.9

杂题

怎么字符串题单里面只有三个紫题啊
剩下的怎么全是黑的难度的啊
我是在备战 ZJOI 吗

只会贺

随做随写吧

CF1286E

给定一个字符串 \(S\) 和一个序列 \(W\),初始时它们都为空。你需要在线完成 \(n\) 次操作。每次操作在 \(S\) 后面添加一个字符 \(c\),在序列 \(W\) 后面添加一个数字 \(W_i\)。二者均加密。

定义一个子区间 \([L,R]\) 的可疑度为:
若子串 \([L,R]\) 和前缀 \([1,R-L+1]\) 相同,则其可疑度为 \(\min_{i=L}^{R} W_i\)。否则其可疑度为\(0\)

每次操作后,你都要求出当前的串的所有子区间的可疑度之和。

\(n\le 6\times 10^5,\ W< 2^{30}\)

考虑每次更新答案只会加入右端点为新加入节点的区间。问题转化为动态维护 border 的权值和。
容易发现每次加入一个节点最多只会加入一个 border,因此总 border 数为 \(O(n)\) 的。因此考虑用 \(O(n \ f(n))\) 的方式均摊。

设当前加入的节点为 \(i\)
我们需要维护 \(s[1\dots i-1]\) 的子串内所有 border,在加入元素时考虑这些 border 是否还能拓展。如果能拓展则这个 border 的权值和 \(w_i\)\(\min\),答案相应更改。反之我们需要将这个 border 删掉。
考虑如何快速找到下一个需要被删掉的 border。
对每个节点维护一个数组 \(\text{fat}[]\) 表示该节点在 fail 树上深度最大且与当前节点对应前缀的下一个字母不同的节点。话比较绕嘴,但只需要明白这是为了快速找到不能拓展的前缀就行了。
每次从当前节点的 fail 开始跳 fat,如果可以删除就一直删除,删不了了就再跳一次 fat。最后跳到空串为止。
容易发现每个字符串都只会被遍历一次,因此这部分的复杂度到了 \(O(n)\)

然后考虑能拓展的情况。我们维护一个单增栈,容易发现所有能对答案贡献的位置都在这个栈里面。
我们需要快速确定一个节点对应的答案是多少,这个可以在弹栈时用并查集将栈顶和当前节点合并。

最后是当前位置自己作为 border 和串首做贡献。
这时取栈首即可。

总时间复杂度 \(O(n\ \alpha(n))\)。实现的好的话可能是最优解?
会爆 long long,请使用 __int128_t 或者 pair<long long, long long> 模拟。没必要写高精。

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 = 6e5 + 10, mask = (1 << 30) - 1;
const long long bse = 1e18;
// const int mod = 998244353; 

int n, nxt[N], w[N], stk[N], top, fat[N];
char s[N];
__int128_t ans, now;

int fa[N], siz[N], sz[N];
int find(int u) { return u == fa[u] ? u : fa[u] = find(fa[u]); }
void merge(int & a, int & b) {
	now -= 1ll * siz[b] * w[b];
	now += 1ll * siz[b] * w[a];
	if (sz[a] < sz[b]) swap(a, b);
	fa[b] = a; sz[a] += sz[b]; 
	w[a] = min(w[a], w[b]); siz[a] += siz[b];
}

signed main() {
	cin.tie(0)->sync_with_stdio(false);
	cin >> n >> s[1] >> w[1];
	fa[1] = sz[1] = stk[top = 1] = 1;
	ans += w[1]; printf("%lld\n", (long long)ans);
	for (int i = 2, j = 0, k; i <= n; ++ i) {
		cin >> s[i] >> w[i];
		s[i] = (s[i] + ans - 'a') % 26 + 'a';
		w[i] = (w[i] ^ (ans & mask));
		stk[++top] = i, fa[i] = i, sz[i] = 1;
		while (top > 1 and w[stk[top]] <= w[stk[top - 1]]) {
			swap(stk[top], stk[top - 1]);
			merge(stk[top - 1], stk[top]);
			-- top;
		} 

		fat[i - 1] = (s[i] == s[j + 1] ? fat[j] : j); // 找到后缀树上的祖先位置
		while (j and s[j + 1] != s[i]) {
			-- siz[find(i - j)];
			now -= w[find(i - j)]; 
			j = nxt[j];
		} 

		k = fat[j];
		if (s[j + 1] == s[i]) ++ j;
		nxt[i] = j;
		while (k) {
			if (s[k + 1] == s[i]) {
				k = fat[k];
			} else {
				-- siz[find(i - k)];
				now -= w[find(i - k)];
				k = nxt[k];
			}
		} 

		if (s[1] == s[i]) {
			++ siz[find(i)];
			now += w[find(i)];
		}

		ans += now + w[find(stk[1])];
		if (ans > bse) printf("%lld%018lld\n", (long long)(ans / bse), (long long)(ans % bse));
		else printf("%lld\n", (long long)ans);
	} 
}



[POI2011]OKR-Periodicity

给定一个字符串 \(S\),其长度为 \(N\)

定义 \(\textrm{Per}(S)\)\(S\) 的所有周期的集合。

多组数据,每次给一个 \(S\),要求你构造一个 \(01\) 串使得其 \(\textrm{Per}\) 集合与 \(S\) 相同。如果有多种答案,输出字典序最小的一种。

\(T \leq 20\)\(|S| \leq 2 \times 10^5\)

切了,但不是很理解为什么
为啥我把得到的 \(qq'\) 再求一次 period 就会炸 删了就切了啊
似乎是子串 period 不是很一样 但是原串拼合后就一样了

考虑递归构造。
我们设 \(\text{solve}(l,r)\) 为字典序最小且对 \(s[l\dots r]\) 子串满足条件的 01 序列。然后可以分类讨论 \(\text{solve}(l,r)\) 是什么:

  1. \(s[l\dots r]\) 子串的 period 为1,则为 \(r-l+1\)\(0\)
  2. \(s[l\dots r]\) 子串的 border 为1,则为 \(r-l\)\(0\)\(1\)\(1\)
  3. \(s[l\dots r]\) 子串的 period \(p\) 满足 \(2p \le r - l + 1\),则 \(s[l\dots r]\) 子串定可以表示为 \(s\dots ss'\) 的形式,其中 \(s'\)\(s\) 的一个可空前缀。递归解决 \(ss'\)。设得到的答案为 \(tt'\),则 \(\text{solve}(l,r)\) 定可以表为 \(t\dots tt'\) 的形式,其中 \(t'\)\(t\) 的一个可空前缀。
  4. \(s[l\dots r]\) 子串的 period \(p\) 满足 \(2p > r - l + 1\),则 \(s[l\dots r]\) 子串定可以表示为 \(sas'\) 的形式,\(a\) 不可空。递归解决 \(s\)。设得到的答案为 \(t\),则 \(\text{solve}(l,r)\) 定可以表为 \(tbt\) 的形式,\(b\) 不可空。
    \(b\) 为全 \(0\) 时满足条件则为全 \(0\) 即可,反之置 \(b\) 的最后一位为 \(1\) 定满足条件且最优。

到这里程序实现就没有问题了。难点在如何证明。

1.2. 显然,不证。

3.的情况需要一个引理。

\(\text{Weak Periodicity Lemma (WPL)}\)

对于一个字符串 \(s\),若其有长度为 \(p\) 和长度为 \(q\) 的周期,且 \(p+q \leq |s|\),则 \(s\) 有长度为 \(\gcd(p,q)\) 的周期。

证明:
不妨设 \(p < q\)。由于 \(p + q \le |s|\),则对于任意 \(i\)\(i - p\ge 0\)\(i + q < s\) 定成立其中一个。因此可以向前跳 \(p\) 并向后跳 \(q\),二者在某种顺序下定成立。设 \(d = q - p\),则我们有 \(s[i + d] = s[i]\)。则 \(q - p\) 也是 \(s\) 的一个周期。
根据辗转相减法,原设成立。

然后可以有推论:

对于一个字符串 \(s\),若其最短周期为 \(l\)\(2l \le |s|\),则任意长度大于 \(l + (|s| \bmod l)\) 的周期只能在 \(l\) 的周期首处取得。

证明考虑根据 \(\text{WPL}\) 进行反证法。

根据推论,我们的构造法保证了当 \(s\) 满足最短周期 \(l \nmid |s|\) 时的正确性。
\(s\) 的最短周期 \(l \mid |s|\) 时考虑 \(s\) 不会在这时成为原串的一个划分单元。
因此 3. 情况正确。

4.的情况一眼看上去挺显然的。那就不证了。

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 + 10;
// const int mod = 998244353; 
int T, n;
char ch[N], tmp[N];

int nxt[N];
int period(int l, int r) {
	int len = 0;
	rep(i,l,r) tmp[++ len] = ch[i];
	for (int i(2), j(0); i <= len; ++ i) {
		while (j and tmp[j + 1] != tmp[i]) j = nxt[j];
		if (tmp[j + 1] == tmp[i]) ++ j;
		nxt[i] = j;
	} return nxt[len];
}

int period(basic_string<char> ch) {
	int len = 0;
	rep(i,0,ch.length() - 1) tmp[++ len] = ch[i];
	for (int i(2), j(0); i <= len; ++ i) {
		while (j and tmp[j + 1] != tmp[i]) j = nxt[j];
		if (tmp[j + 1] == tmp[i]) ++ j;
		nxt[i] = j;
	} return nxt[len];
}

basic_string<char> solve(int l, int r) {
	int prd = r - l + 1 - period(l, r);
	basic_string<char> now;
	if (prd == 1) {
		rep(i,l,r) now += '0';
	} else if (prd == r - l + 1) {
		rep(i,l,r - 1) now += '0';
		now += '1';
	} else if (prd * 2 <= (r - l + 1)) {
		basic_string<char> qq_ = solve(r - prd - (r - l + 1) % prd + 1, r);
		basic_string<char> tmp = qq_.substr(0, prd);
		rep(i,1,floor(1. * (r - l + 1) / prd) - 1) now += tmp;
		now += qq_;
	} else {
		basic_string<char> q = solve(l, r - prd);
		now += q;
		rep(i,1,2*prd - (r-l+1)) now += '0';
		now += q;
		if (r - l + 1 - period(now) != prd) {
			now.clear();
			now += q;
			rep(i,2,2*prd - (r-l+1)) now += '0';
			now += '1';
			now += q;
		}
 	}
	return now;
}

signed main() {
	cin.tie(0)->sync_with_stdio(false);
	cin >> T;
	while (T --) {
		cin >> ch + 1;
		cout << solve(1, strlen(ch + 1)) << endl;
	}
}



[WC2016]论战捆竹竿

给定长度为 \(n\) 的字符串 \(S\),现有一个空串 \(T\),每次可将 \(S\) 去掉一个 \(\text{border}\) 后接在 \(T\) 上。问 \(T\) 的长度可以是 \([n,w]\) 中的多少个数。

\(1\le n\le 5\times 10^5,1\le w\le 10^{18}\)

我不是很理解我贺了个什么东西
他只跑了一次最短路,但是它把所有等差数列缩在了一个点里面

考虑正常做法(
我们发现,接在 \(T\) 上的 \(S\) 子串只能是 \(S\) 的 period。设 \(S\) 共有 \(k\) 个period,\(a_i\) 代表 \(S\) 的第 \(i\) 个 period,则我们需要的是 \(\sum_{i=1}^k a_ix_i\) 的取值个数。这启发我们使用同余最短路算法解决问题。
暴力跑 dij,点数 \(O(n)\) 边数 \(O(n^2)\) 因此有复杂度 \(O(n^2)\)谁在想带 log 的复杂度呢?

考虑优化。
有性质:所有长度 \(\ge\) 原串一半的 border 构成一个等差数列。

证明:
考虑 \(S\) 的最长 border \(A\),以及另一个 border \(B\),满足 \(|B| \ge |S|/2\)
\(p = |S| -|A|\)\(q = |S| - |B|\)。由定义,\(p,q\) 为两个 period。由 \(\text{WPL}\) 可知 \(\gcd(p,q)\) 也是一个 period。因此 \(|S| - \gcd(p,q)\) 为一个 border。又因为 \(A\) 极长,因此这个 border 就是 \(A\)。因此 \(\gcd(p,q) = p\) 。因此 \(p \mid q\)。因为 \(\forall i\times q\) 也是 \(S\) 的周期,因此得证。

因此我们可以将原串的所有 border 分成两部分,一部分为等差数列,另一部分的最长长度小于 \(|S| / 2\)。递归可得最终 border 构成了 \(O(\log |S|)\) 个等差数列。
然后我们把每个等差数列分开处理。假设当前的等差数列形如 \(kx +b\),则我们取同余 \(b\) 跑最短路。然后连边考虑 \(\forall 0 \le y < x\)\(y\)\((y + k) \bmod b\) 连边。这样形成了 \(\gcd(k, b)\) 个环。
\(b=0\) 的等差数列开始更新,每次选择当前所有点中 \(dis\) 最小点使用单调队列更新就可以做到转移。
然后是换模数。
假设原来的 \(dis\)\(f\),当前的 \(dis\)\(g\),模数为 \(m\),则我们有 \(g_i = \min_{f_j \bmod m= i} f_j\)

然后转移即可。总时间复杂度 \(O(n\log n)\)

↓ 我也不知道这份代码在写什么 晚上好好研究一下

code
#include <bits/stdc++.h>
#include <bits/extc++.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)
using ll = long long; using ull = unsigned long long;
const int N = 2e6 + 10;
// const int mod = 998244353; 
int T, n, nxt[N], beg[N], val[N], num[N], top;
ll m, ans, dis[N];
bool vis[N];
char s[N];

__gnu_pbds :: priority_queue <pair<ll, int> > que;

signed main() {
	cin.tie(0)->sync_with_stdio(false); cout.tie(0);
	cin >> T;
	nxt[0] = -1;
	while (T --) {
		cin >> n >> m >> s + 1; m -= n;
		top = ans = 0;
		for (int i(2), j(0); i <= n; ++ i) {
			while (j and s[j + 1] != s[i]) j = nxt[j];
			if (s[j + 1] == s[i]) ++ j;
			nxt[i] = j;
		}
		for (int i = nxt[n]; i >= 0; i = nxt[i]) {
			if (top and (num[top] == 1 or n - i == beg[top] + val[top] * num[top]))
				val[top] = (n - i - beg[top]) / (num[top] ++);
			else beg[++ top] = n - i, num[top] = 1;
		}
		rep(i,0,beg[1] - 1) dis[i] = 1e18 + 11e8, vis[i] = false;
		que.push( {dis[0] = 0, 0} ); 
		while (que.size()) {
			int u = que.top().second; que.pop();
			if (vis[u]) continue;
			vis[u] = 1;
			rep(i,1,top) for (int j(beg[i]), k(0), d(val[i]), r(beg[i] + val[i] * (num[i] - 1)); j <= r; j += val[i], d += val[i]) {
				k = (u + j) % beg[1];
				if (dis[k] > dis[u] + j)
					dis[k] = dis[u] + j, que.push( {-dis[k], k} );
				if (dis[(u + d) % beg[1]] <= dis[u] + d) break;
			}
 		} 
		rep(i,0,beg[1] - 1) if (dis[i] <= m) ans += (m - dis[i]) / beg[1] + 1;
		cout << ans << endl;
	}
}



loj6681

给一棵树,每条边上有一个字符,求有多少对 \((x,y)(x<y)\),满足 \(x\)\(y\) 路径上的边上的字符按顺序组成的字符串为回文串。

点数 \(\le 5\times 10^4\),字符 \(\in \{0,1\}\)

点分。

首先以当前点分中心为根,将根到当前联通块内每个节点路径上的 \(01\) 串插入自动机。
考虑答案一定形如 \(\text{ST|S}\),其中 \(\text{T}\) 是一个回文串,\(|\) 是重心所在的位置。设串首的节点为 \(x\),串尾节点为 \(y\)
首先在自动机的 fail 树上进行 dfs,把每个点作为 \(y\) 的贡献求出来。

然后 \(T\) 部分是好判的,哈希即可。
然后由于最终的回文子串构成了 \(O(\log |S|)\) 个等差数列,根号分治一下就能暴力了。
总时间复杂度 \(O(n\sqrt n)\)

根号平衡的 \(B = 10\) 时跑得飞快,已经是 loj 上最优解了

code
#include <bits/stdc++.h>
using namespace std; 
using ll = long long; using ull = unsigned long long;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#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 = 5e4 + 10;
const int B = 10;

// const int mod = 469762049; 
// const int mod = 998244353; 
const int mod = 1004535809; 
// const int mod = 1e9 + 7; 
// const int mod = 1e9 + 9; 
// const int mod = 1e9 + 3579, bse = 131;
template <typename T1, typename T2> T1 add(T1 a, T2 b) { return (a += b) >= mod ? a - mod : a; }
template <typename T1, typename ...Args> T1 add(T1 a, Args ... b) { return add(a, add(b...)); }

int n, pw[N], t1, t2, t3;
ll ans, res;
vector<pair<int,int> > e[N];
bool vis[N];

int ch[N][2], s[N], fail[N], dep[N], mlc;
vector<int> g[N];
vector<tuple<int,int,int> > q[N];
int New() {
	ch[mlc][0] = ch[mlc][1] = s[mlc] = fail[mlc] = 0;
	q[mlc].clear(), g[mlc].clear();
	return mlc ++;
}

void build(int u, int fa, int & id) {
	if (!id and fa) id = New();
	++ s[id];
	for (auto [v, w] : e[u]) if (!vis[v] and fa != v) 
		build(v, u, ch[id][w]);
}

queue <int> que;
void build() {
	rep(i,0,1) if (ch[0][i]) que.push(ch[0][i]);
	while (que.size()) {
		int u = que.front(); que.pop();
		g[fail[u]].emplace_back(u);
		rep(i,0,1)  {
			if (!ch[u][i]) ch[u][i] = ch[fail[u]][i];
			else fail[ch[u][i]] = ch[fail[u]][i], que.push(ch[u][i]);	
		}
	}
}

vector <pair<int,int> > b[N];
void dfs1(int u, int hshl, int hshr, int ls) { 
	if (u and hshl == hshr) {
		if (b[u].size() and dep[u] - ls == b[u].back().first) ++ b[u].back().second;
		else b[u].emplace_back(dep[u] - ls, 1);
		ls = dep[u];
	} rep(i,0,1) if (ch[u][i]) {
		b[ch[u][i]] = b[u];
		dep[ch[u][i]] = dep[u] + 1;
		dfs1(ch[u][i], add(hshl, hshl, i), add(hshr, i * pw[dep[u]]), ls);
	}
}

int id[N], stk[N], top, cnt[N], c[B + 5][B + 5];
void dfs2(int u) {
	id[top] = u, stk[top] = dep[u];
	++ top;
	cnt[dep[u]] = s[u];
	res += 1ll * s[u] * (s[u] - 1) >> 1;
	int len = 0;
	for (auto [l, t] : b[u]) {
		if (l >= B) {
			while (t --) res += 1ll * s[u] * cnt[dep[u] - (len += l)];
		} else {
			int lp = dep[u] - len - l * t, rp = dep[u] - len - l;
			if (stk[0] < lp) q[id[lower_bound(stk, stk + top, lp) - stk - 1]].emplace_back(-s[u], l, rp % l);
			q[id[upper_bound(stk, stk + top, rp) - stk - 1]].emplace_back(s[u], l, rp % l);
			len += l * t;
		}
	} 
	for (int i = 1; i < B; ++ i) c[i][dep[u] % i] += s[u];
	for (auto v : g[u]) dfs2(v);
	for (auto [cont, i, j] : q[u]) res += 1ll * cont * c[i][j];
	for (int i = 1; i < B; ++ i) c[i][dep[u] % i] -= s[u];
	-- top; cnt[dep[u]] = 0;
}

ll calc(int u, int w) {
	res = mlc = 0; int tmp = New();
	if (w == -1) build(u, 0, tmp);
	else ch[tmp][w] = New(), build(u, 0, ch[tmp][w]);
	dfs1(0, 0, 0, 0);
	build();
	dfs2(0);
	return res;
}

int sum, rt, siz[N], f[N];
void get_rt(int u, int fa) {
	siz[u] = 1; f[u] = 0;
	for (auto [v, w] : e[u]) if (!vis[v] and v != fa) {
		get_rt(v, u);
		siz[u] += siz[v];
		f[u] = max(f[u], siz[v]);
	} f[u] = max(f[u], sum - siz[u]);
	if (f[rt] > f[u]) rt = u;
}

void solve(int u) {
	rt = 0; get_rt(u, 0); vis[rt] = 1;
	ans += calc(rt, -1);
	for (auto [v, w] : e[rt]) if (!vis[v]) {
		ans -= calc(v, w);
		sum = siz[v];
		solve(v);
	}
}

signed main() {
	get(n); pw[0] = 1;
	rep(i,1,n) pw[i] = add(pw[i-1], pw[i-1]);
	rep(i,2,n) get(t1, t2, t3), e[t1].emplace_back(t2, t3), e[t2].emplace_back(t1, t3);
	sum = n, f[0] = 1e9;
	solve(1);
	cout << ans << endl;
}
posted @ 2022-11-09 10:37  joke3579  阅读(102)  评论(3编辑  收藏  举报