闲话 23.3.27

闲话

某人写了 INTERNET YAMERO 上黑板
……我觉得会被跳过
另一个人写了 Brain Power 上黑板
A 老师:这玩意有词?

美式咖啡喝凉的好还是热的好?
你说得对,但是我喝摩卡和拿铁

模拟赛

这模拟赛打的 明天是要模拟如何翻盘?

T1
签到题。考虑直接枚举前面的 BABA...BA\([1, r]\),这样的 \(r\) 可以枚举 BA 长度后 check \(O(n)\) 段得到。然后就是统计 \([r + 1, n]\) 段和 BA 的 lcp 长度,可以二分得到。
通过哈希实现的总时间复杂度 \(O(n\log n)\),注意数组是否越界。

T2
考场降智时刻。
考虑第一问。设 \(f(u)\) 表示子树内叶子情况全知道的最小花费,\(g(u)\) 表示有且只有一个叶子不知道的最小花费,转移就是 \(g(u) = \min_w\{\sum_{v \neq w} f(v) + g(w)\}, \ f(u) = \min\{\sum_v f(v), g(u) + c[u]\}\)。叶子初值 \(f(u) = c[u], g(u) = 0\)
考虑第三问。设 \(fw(u)\) 表示子树内叶子情况全知道的所有情况,\(gw(u)\) 表示有且只有一个叶子不知道的所有情况,我们只需要按上面的最小情况转移即可。也就是说,上面加法改成乘法,上面最小值改成把所有最小情况加和即可。叶子初值 \(fw(u) = gw(u) = 1\)
考虑第二问。我们按照上面的情况直接再做一遍递归即可。也就是说,我们按照最优方案 dfs,dfs 时记录当前节点子树内还有几个叶子不知道,再记忆化,保证只搜索 \(O(n)\) 次。
总时间复杂度 \(O(n)\)

T3
\(f(u, h)\) 表示到 \(u\) 点,还剩 \(h\) 点生命值时答案的期望。不难写出

\[f(u, h) = \min\left\{H - h + f(1, H), 1 + \frac{1}{deg[u]} \sum_{u\to v} f(v, h - d_v) \right\} \]

我们发现,这是有后效性的,但是所有后效性只取决于 \(f(1, H)\) 这一个点的值。考虑如何把这个点的值删去。
引入变元 \(x = f(1, H)\),我们设 \(f(u, h)(x)\) 为如上的期望对变元的函数。可以证明的是,\(\forall i, h, \ \dfrac{\text d}{\text dx} f(u, h)(x) \le 1\)
证明考虑归纳法。
我们首先观察 \(f(n, h)(x)\),这些函数总是定值 \(0\),因此 \(\dfrac{\text d}{\text dx} f(u, h)(x) = 0 \le 1\)
我们已经知道了对 \(\forall u\to v, \ f(v, h)(x)\) 全满足这条件,现在需要证明 \(f(u, h)(x)\) 也满足这条件。可以简记形态为

\[f(x) = \min\left\{x + c, \frac{1}{k}\sum_{i = 1}^k g_i(x) + c\right\} \]

其中 \(\forall g_i(x), \ \dfrac{\text d}{\text dx} g_i(x) \le 1\)。它的曲线非光滑,但由于间断点都是第一类的,我们可以只对连续的部分考虑导数,最后讨论间断点。也就是说我们只需要对 \(\min\) 的前后两部分分别归纳证明。前一部分是显然的,\dfrac{\text d}{\text dx} (x + c) = 1 \le 1$,而后一部分可以知道

\[\dfrac{\text d}{\text dx} \left(\frac{1}{k}\sum_{i = 1}^k g_i(x) + c\right) = \frac{1}{k}\sum_{i = 1}^k \dfrac{\text d}{\text dx} g_i(x) \le \frac{1}{k} \sum_{i = 1}^k 1= 1 \]

由于是取 \(\min\) 操作,因此图像连续。由于函数在光滑部分的导数 \(\le 1\),并由 \(\min\) 操作的性质,任意两个间断点总是由导数大的函数切换至导数小的函数。所以 \(f(u, h)(x)\) 的导数小于等于 \(\min\) 的两个参数的。
这也就证明了 \(f(u, h)(x)\) 也满足这条件。
考虑这是个 dag,点 \(u\) 的出边只会指向 \(> u\) 的点。因此假设我们已知了 \([l, n]\) 段都符合要求,总存在一个只连向 \([l, n]\) 段的节点使得我们可以拓展符合要求的区间,取 \(l - 1\) 即可。
证毕。
所以我们也能知道 \(\dfrac{\text d}{\text dx}f(1, H)(x) \le 1\),即 \(\dfrac{\text d}{\text dx}(f(1, H)(x) - x) \le 0\)。这也就说明了 \(f(1, H)(x) - x\) 是有单调性的。我们只需要二分得到 \(f(1, H)(x) - x = 0\)\(x\),这 \(x\) 就是答案。
总时间复杂度 \(O(nH\log V)\),其中 \(V\) 是答案的值域。

杂题

CF1747E

给定 \(n\)\(m\),请问能构造满足以下条件的数组对 \((a,b)\) 中,\(a\) 长度之和是多少:

  1. 数组长度相等且长度大于 \(2\),设数组长度都为 \(k\)
  2. \(a_1=b_1=0\)\(a_k=n\)\(b_k=m\)
  3. 每个数组都单调不下降且对于所有 \(1<i\le k\) 都满足 \(a_i+b_i\ne a_{i-1}+b_{i-1}\)

答案对 \(10^9+7\) 取模。

\(1\le n, m\le 5\times 10^6\)

好像有高妙格路计数,不是很懂。

考虑数组单调不降,所以 \(a_i+b_i\ge a_{i-1}+b_{i-1}\),等号当且仅当 \(a_i = a_{i - 1}, b_i = b_{i - 1}\)。所以 3. 条件说的很明白了:两个数组不升的位置不能有重合。
这启发我们枚举不升的位置计数。我们枚举长度是 \(k + 1\)\(a,b\) 序列升的位置分别为 \(i, j\) 个。首先有 \((k + 1)\) 的贡献,并且分配 \(n/m\) 的值给 \(i/j\) 个升的位置等价于 \(n/m\) 个球放在 \(i/j\) 个有标号盒子里且不能有空盒子,方案数就是 \(\binom{n - 1}{i - 1} / \binom{m - 1}{j - 1}\)。最后是分配不升的位置,首先分配 \(a\) 序列的方案数是 \(\binom{k}{i}\) 个,而 \(b\) 序列可以考虑 \(k - j\) 个不升的位置必须要和 \(i\)\(a\) 中升的位置重合,方案数是 \(\binom{j}{k - i}\)。所以有答案就是

\[\begin{aligned} &\sum_{k\ge 1} \sum_{i} \sum_{j} (k + 1) \binom{n - 1}{i - 1} \binom{m - 1}{j - 1} \binom{k}{i} \binom{i}{k - j} \\ = \ & \sum_{k\ge 1} \sum_{i} (k + 1) \binom{n - 1}{i - 1}\binom{k}{i}\sum_{j} \binom{m - 1}{j - 1} \binom{i}{k - j} \\ = \ & \sum_{k\ge 1} \sum_{i} (k + 1) \binom{n - 1}{i - 1} \binom{k}{i} \binom{m + i - 1}{k - 1} \\ = \ & \sum_{k\ge 1} \sum_{i} (k + 1) \binom{n - 1}{i - 1} \frac{k}{i} \binom{k - 1}{i - 1} \binom{m + i - 1}{k - 1} \\ = \ & \sum_{k\ge 1} \sum_{i} \frac{1}{i} k(k + 1) \binom{n - 1}{i - 1}\binom{m + i - 1}{k - 1} \binom{k - 1}{i - 1} \\ = \ & \sum_{k\ge 1} \sum_{i} \frac{1}{i} k(k + 1) \binom{n - 1}{i - 1}\binom{m + i - 1}{i - 1} \binom{m}{k - i} \\ = \ & \sum_{i\ge 0} \frac{1}{i} \binom{n - 1}{i - 1}\binom{m + i - 1}{i - 1} \sum_{k\ge 1} k(k + 1) \binom{m}{k - i} \\ = \ & \sum_{i\ge 0} \frac{1}{i} \binom{n - 1}{i - 1}\binom{m + i - 1}{i - 1} \sum_{k\ge 0} (k + i)(k + i + 1) \binom{m}{k} \end{aligned}\]

接下来我们要解决的是 \(\sum_{k\ge 0} (k + i)(k + i + 1) \dbinom{m}{k}\) 的计算。我们不妨考虑更广泛的情况,即已知一个 \(d\) 次多项式 \(F(k)\) 的系数,计算 \(\sum_{k\ge 0} F(k)\dbinom{m}{k}\)
由于这题 \(d\) 较小,我们可以考虑直接计算每个 \(\sum_{k\ge 0} k^v\dbinom{m}{k}\)你定睛一看——这不是《载谭 Binomial Sum》吗?所以可以做到 O(k + log n)
由于这题 \(d = 2\),我们可以直接考虑计算每个 \(v = 0/1/2\)
\(v = 0\) 时是经典结论 \(2^m\)
\(v = 1\) 时可以知道答案就是

\[\frac{\sum_{k\ge 0} k\dbinom{m}{k} + \sum_{k\ge 0} (m - k)\dbinom{m}{k}}{2} = \frac{m}{2} \sum_{k\ge 0} \binom{m}{k} = m2^{m - 1} \]

\(v = 2\) 时可以拆成下降幂形式,自然得到。

所以我们可以 \(O(n)\) 解决原问题。



P1117

如果一个字符串可以被拆分为 \(\text{AABB}\) 的形式,其中 \(\text{A}\)\(\text{B}\) 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。现在给出一个长度为 \(n\) 的字符串 \(S\),我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

\(1\le n\le 10^5\)

好久之前就想学这个套路了但是一直没写(

假设从 \(i\) 点开始的形如 AA 的串数是 \(a(i)\),从 \(i\) 点结束的形如 AA 的串数是 \(b(i)\),则 \(\sum_{i = 1}^{n - 1} a(i)\times b(i + 1)\) 就是答案。考虑如何计算这两个值。

考虑枚举长度 \(\text{len}\),我们每次统计长度为 \(\text{len}\) 的串会对哪些点的 \(a, b\) 产生贡献。
我们在 \(\text{len}, 2\text{len}, \dots\) 的位置撒关键点,这样每个形如 AA 的串都会经过且仅经过相邻的 2 个点。由于总点数是 \(O(n\log n)\) 的,我们可以枚举相邻点算贡献。假设当前的两个点是 \(l = k\)\(r = k + \text{len}\)

假设我们求出了前缀 \([1, l]\)\([1, r]\) 的最长公共后缀 \(S\),以及后缀 \([l + 1, n]\)\([r + 1, n]\) 的最长公共前缀 \(T\)
考虑当 \(|S| + |T| < \text{len}\) 时不可能有解,我们总能取 \([l + |T|, r - |S|]\) 中一个字符作为两个串中不等的字符的例子。
而当 \(|S| + |T| \ge \text{len}\) 时,我们总能从 \(l - |S| + 1\) 位置开始向后取一段,这一段内都可以作为 AA 串的起点,直到最右侧达到 \(r + |T| - 1\)。终点类似。

这样我们就可以对 \(a, b\) 做区间加了。由于我们是先加再求,考虑差分维护即可。最长公共前后缀可以采用 SA + st表的方式求解。
总时间复杂度 \(O(n\log n)\)

类似题:UVA10829

code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#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)
#define eb emplace_back
#define pb pop_back
const int N = 30000 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, a[N], b[N];
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 << 3], *_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() {
		n = strlen(begin());

		memset(tr, 0, sizeof tr);
		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; _t = bse;
		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 lcp(int l, int r) {
		if (l <= 0 or l > n or r <= 0 or r > n) return 0;
		l = rk[l], r = rk[r];
		if(l > r) swap(l, r); l++; 
		int k = lgv[r - l + 1]; 
		return min(st[l][k], st[r - (1 << k) + 1][k]); 
	}
}; SA<> sa[2];

signed main() {
	multi {
		cin >> sa[0].begin();
		sa[0].build(); n = sa[0].n;
		rep(i,1,n) sa[1][i] = sa[0][n - i + 1]; sa[1][n + 1] = 0;
		sa[1].build();
		rep(i,1,n) a[i] = b[i] = 0;
		for (int len = 1; len <= n / 2; ++ len) {
			for (int i = len; i <= n; i += len) {
				int l1 = i, r1 = i + len, l2 = n - (r1 - 1) + 1, r2 = n - (l1 - 1) + 1;
				int lcp = min(len, sa[0].lcp(l1, r1)); 
				int lcs = min(len - 1, sa[1].lcp(l2, r2)); 
				if (lcp + lcs >= len) {
					b[i - lcs] ++, b[i - lcs + (lcp + lcs - len + 1)] --;
					a[r1 + lcp - (lcp + lcs - len + 1)] ++, a[r1 + lcp] --;
				}
			}
		} rep(i,1,n) a[i] += a[i - 1], b[i] += b[i - 1];
		ll ans = 0; 
		rep(i,1,n - 1) ans += 1ll * a[i] * b[i + 1];
		cout << ans << '\n';
	}
} 
posted @ 2023-03-27 15:47  joke3579  阅读(89)  评论(5编辑  收藏  举报