冲刺国赛题目乱写

因为本人太菜,一车题没改,所以把部分题题解放到这里

自测9 A.字符串

没有继续观察性质。或者说应该反方向考虑?

考虑一个串一定是前面和前缀相同,后面和后缀相同

于是想到 \(boder\) ,那么从每个点向其 \(boder\) 连边,每次相当于查询两个点子树内相同点的数量

对应原串上相邻的两个子串(你重命名一下)

那么考虑 \(dfn\) 序的话就是二维数点。

\(boder\) 换成\(SAM\) 也行。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 2e5 + 55;
int n, q, nxt[maxn]; char s[maxn];
vector<int>t1[maxn], t2[maxn];
struct BIT{
	int t[maxn];
	int lowbit(int x){return x & -x;}
	void add(int x){while(x <= n){++t[x]; x += lowbit(x);}}
	int query(int x){int ans = 0; while(x){ans += t[x]; x -= lowbit(x);} return ans;}
	int query(int l, int r){return query(r) - query(l - 1);}
	void clear(){for(int i = 1; i <= n; ++i)t[i] = 0;}
}T;
int dfn[maxn], dfnr[maxn], tim;
void dfs(int x){
	dfn[x] = ++tim; 
	for(int v : t2[x])dfs(v);
	dfnr[x] = tim;
}
int ans[maxn];
vector<pii>rem[maxn];
void solve(int x){
	for(pii v : rem[x])ans[v.second] -= T.query(dfn[v.first], dfnr[v.first]);
	if(x && x != n)T.add(dfn[x + 1]);
	for(int v : t1[x])solve(v);
	for(pii v : rem[x])ans[v.second] += T.query(dfn[v.first], dfnr[v.first]);
}
void solve(){
	n = read(), q = read(); scanf("%s",s + 1);
	nxt[1] = 0; t1[0].push_back(1);
	for(int i = 2, j = 0; i <= n; ++i){
		while(j && s[j + 1] != s[i])j = nxt[j];
		if(s[j + 1] == s[i])++j;
		nxt[i] = j; t1[nxt[i]].push_back(i);
	}
	nxt[n] = n + 1; t2[n + 1].push_back(n);
	for(int i = n - 1, j = n + 1; i >= 1; --i){
		while(j != n + 1 && s[j - 1] != s[i])j = nxt[j];
		if(s[j - 1] == s[i])--j;
		nxt[i] = j; t2[nxt[i]].push_back(i);
	}
	tim = -1; dfs(n + 1);
	for(int i = 1; i <= q; ++i){
		int x = read(), y = read();
		if(x + y <= n)rem[x].push_back(pii(n - y + 1, i));
	}
	solve(0);
	for(int i = 1; i <= q; ++i)printf("%d\n",ans[i]);
	for(int i = 1; i <= q; ++i)ans[i] = 0;
	for(int i = 0; i <= n + 1; ++i)t1[i].clear(), t2[i].clear();
	for(int i = 0; i <= n + 1; ++i)rem[i].clear();
	T.clear();
}
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	int T = read(); for(int i = 1; i <= T; ++i)solve();
	return 0;
}

自测9 B. 计树

问题在树上,所以肯定要树上 \(DP\),考虑如何去计算答案

一种做法是考虑计算出每棵子树的答案,合并子树时也合并贡献

那么我们就需要知道子树内每个点在子树内的排名,于是可以这样设计\(DP\) 状态

\(f_{x, a, b}\) 表示在以 \(x\) 为根的子树内,节点 \(a\) 在子树内排名为 \(b\) 的方案数

\(g_{x} = f_{x, x, 1}\) 表示 \(x\) 子树内的总方案数

使用 \(vector\) 记录子树内所有点

考虑计算新的答案,原先子树内的答案乘上另一棵子树的方案数,和分配排名的组合数(注意答案钦定了某两个点相邻,只要给他们分配一个排名)

对于两棵子树之间的贡献,枚举点和其排名,强制枚举到的点在新排名中相邻,前后用组合数计算

考虑转移 \(f_{x, a, b}\) 枚举 \(a, b\) 枚举另一棵子树有多少点排在 \(a\) 前面,进行转移。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 205, mod = 1e9 + 7;
int n, rt, si[maxn], g[maxn], ans[maxn], tmp[maxn], f[maxn][maxn][maxn], c[maxn][maxn];
vector<int>e[maxn], ver[maxn];
void solve(int x, int fa){
	si[x] = g[x] = 1;
	for(int v : e[x])if(v != fa)solve(v, x);
	for(int v : e[x])if(v != fa){
		int sum = 1ll * g[x] * f[v][v][1] % mod * c[si[x] + si[v] - 2][si[v] - 1] % mod * abs(x - v) % mod;
		for(int p : ver[x]){
			for(int i = 1; i <= si[v]; ++i){
				int tmp = 0;
				for(int q : ver[v])tmp = (tmp + 1ll * abs(p - q) * f[v][q][i]) % mod;
				if(tmp){
					int val = 0;
					for(int j = 1; j <= si[x]; ++j)if(f[x][p][j])
						val = (val + 1ll * f[x][p][j] * c[i - 1 + j - 2][i - 1] % mod * c[si[x] + si[v] - i - j][si[x] - j]) % mod;
					sum = (sum + 2ll * val * tmp) % mod;
				}
			}
		}
		for(int p : ver[x]){
			for(int i = 2; i <= si[x]; ++i)if(f[x][p][i])
				for(int j = 0; j <= si[v]; ++j)
					tmp[i + j] = (tmp[i + j] + 1ll * f[x][p][i] * c[i - 2 + j][j] % mod * c[si[x] + si[v] - i - j][si[x] - i]) % mod;
			for(int i = 1; i <= si[x] + si[v]; ++i)f[x][p][i] = 1ll * g[v] * tmp[i] % mod, tmp[i] = 0;
		}
		for(int p : ver[v]){
			for(int i = 1; i <= si[v]; ++i)if(f[v][p][i])
				for(int j = 1; j <= si[x]; ++j)
					tmp[i + j] = (tmp[i + j] + 1ll * f[v][p][i] * c[i - 2 + j][i - 1] % mod * c[si[x] + si[v] - i - j][si[x] - j]) % mod;
			for(int i = 1; i <= si[x] + si[v]; ++i)f[x][p][i] = 1ll * g[x] * tmp[i] % mod, tmp[i] = 0;
		}
		for(int p : ver[v])ver[x].push_back(p); ver[v].clear();
		si[x] += si[v];
		ans[x] = (1ll * ans[x] * g[v] % mod * c[si[x] - 2][si[v]] + 1ll * ans[v] * g[x] % mod * c[si[x] - 2][si[v] - 1] + sum) % mod;
		g[x] = 1ll * g[x] * g[v] % mod * c[si[x] - 1][si[v]] % mod;
	}
	f[x][x][1] = g[x]; ver[x].push_back(x);
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	for(int i = 0; i <= 200; ++i){
		c[i][0] = 1;
		for(int j = 1; j <= i; ++j)c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	}
	n = read(), rt = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		e[u].push_back(v); e[v].push_back(u);
	}
	solve(rt, 0);
	printf("%d\n",ans[rt]);
	return 0;
}

2023冲刺国赛模拟32 A. 树

考虑容斥进行计算,枚举 \(1\) 所在的连通块进行容斥, \(f_{s, i}\) 表示当前与 \(1\) 联通的集合为 \(s\), 内部连了 \(i\) 条边的方案数。

\(cnt_s\) 表示 \(s\) 集合内的边的数量。

\[ f_{s, i} = \binom{cnt_s}{i} - \sum_{t\subset s \and 1 \in t}\sum_{j = 0}^{min(i, cnt_t)}\binom{cnt_{s \oplus t}}{i - j}f_{t, j} \]

看成多项式

\[ F_{s} = (1 + x)^{cnt_s} - \sum_{t\subset s \and 1 \in t}(1 + x)^{cnt_{s \oplus t}}F_{t} \]

\(y = 1 + x\)

那么转移是简单的,最后带入即可。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int mod = 1e9 + 7, maxn = 16;
int n, m, cz, mp[maxn][maxn], cnt[1 << 15];
int f[1 << 15][205], c[205][205], ans[205];
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= m; ++i){
		int u = read(), v = read();
		++cnt[(1 << u) | (1 << v)];
	}
	for(int i = 0; i <= m; ++i){
		c[i][0] = 1;
		for(int j = 1; j <= i; ++j)c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	}
	for(int hl = 1, l = 2; l <= (1 << n); l <<= 1, hl <<= 1)
		for(int i = 0; i < (1 << n); i += l)
			for(int j = i; j < i + hl; ++j)
				cnt[j + hl] += cnt[j];
	for(int s = 1; s < (1 << n); s += 2){
		f[s][cnt[s]] = 1;
		for(int t = (s - 1) & s; t; t = (t - 1) & s)if(t & 1)
			for(int j = 0; j <= cnt[t]; ++j)
				(f[s][j + cnt[s ^ t]] -= f[t][j]) %= mod;
		for(int i = 0; i <= cnt[s]; ++i)if(f[s][i] < 0)f[s][i] += mod;
	}

	int S = (1 << n) - 1;
	for(int i = n - 1; i <= m; ++i)
		for(int j = i; j <= m; ++j)
			ans[i] = (ans[i] + 1ll * c[j][i] * f[S][j]) % mod;
	for(int i = n - 1; i <= m; ++i)printf("%d ",ans[i]); printf("\n");
	return 0;
}

2023冲刺国赛模拟32 B. 差

考虑设 \(c_i = a_{i + 1} - a{i}\)

那么 \(b_{i} = max(| c_{i} |,| c_{i + 1}|,|c_{i} +c_{i +1}|)\)

\(f_{i, x}\) 表示考虑完前 \(i\) 项, \(c_{i} = x\) 是否合法

转移时保留 \(0 \leq x \leq b_i\)

然后三种转移

\(x \to b_i - x\) 对应 \(max\) 取到第三项

\(b_i \to [0, b_i]\) 取到第一项

\(x \to b_i\) 取到第二项

发现需要维护一个区间和若干单点,支持删除前后缀,翻转,平移,区间只有一个可以直接维护,单点使用双端队列维护

记录每次删去的部分,构造方案时反过来倒推。

代码没写。

2023冲刺国赛自测10

暴力老哥的阶段性胜利

A. 硬币序列

\(f_{i, j, 0 / 1}\) 表示前 \(i\) 个位置,已经用了 \(j\)\(0\) 最后一段是 \(0 / 1\) 的最短长度

二分答案可以做到 \(n^2log\)

既然已经二分答案了,那么记录最短长度看起来就比较多余?

变成枚举这次填的连续段长度,

然后变成\(f_{i, 1 / 0}\) 表示考虑前 \(i\) 个位置,最后一段是 \(1 / 0\) 填的 \(0\) 的个数的合法范围,大胆猜测是一个区间,于是变成维护最大最小值

可以使用单调队列进行优化。

构造方案考虑倒着扫,找到一个当前合法的段就填。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 1e5 + 55, inf = 0x3f3f3f3f;
char s[maxn];
int T, type, n, a, b, sum[maxn];
void ckmi(int &x, int y){if(x > y)x = y;}
int f[maxn][2];
deque<int>q0, q1;
void trans(int len){
	for(int i = 1; i <= n; ++i)f[i][0] = f[i][1] = inf;
	q0.push_back(0); q1.push_back(0);
	for(int i = 1; i <= n; ++i){
		while(q0.size() && i - q0.front() > len)q0.pop_front();
		while(q1.size() && i - q1.front() > len)q1.pop_front();
		if(s[i] != '0' && q0.size())f[i][1] = f[q0.front()][0];
		if(s[i] != '1' && q1.size())f[i][0] = f[q1.front()][1] + sum[i] - sum[q1.front()];
		if(s[i] == '0')q0.clear(), f[i][1] = inf; if(s[i] == '1')q1.clear(), f[i][0] = inf;
		if(f[i][0] != inf){
			while(q0.size() && f[q0.back()][0] > f[i][0])q0.pop_back();
			q0.push_back(i);
		}
		if(f[i][1] != inf){
			while(q1.size() && f[q1.back()][1] - sum[q1.back()] > f[i][1] - sum[i])q1.pop_back();
			q1.push_back(i);
		}
	}
	q0.clear(); q1.clear(); 
}
void rev(){for(int i = 1; i <= n; ++i)if(s[i] != '?')s[i] = '0' + '1' - s[i];}
bool check(int len){
	trans(len); if(f[n][0] > a && f[n][1] > a)return false;
	rev(); trans(len); rev();
	if(f[n][0] > b && f[n][1] > b)return false;
	return true;
}
int mi[maxn][2], mx[maxn][2];
void print(int len){
	trans(len); 
	for(int i = 1; i <= n; ++i)mi[i][0] = f[i][0], mi[i][1] = f[i][1];
	rev(); trans(len); rev();
	for(int i = 1; i <= n; ++i)mx[i][0] = sum[i] - f[i][1], mx[i][1] = sum[i] - f[i][0];
	int r = n, v = 1, res = a;
	if(mi[n][0] <= a && mx[n][0] >= a)v = 0;
	while(r){
		if(v){
			for(int l = r - 1; l >= 0; --l)if(mi[l][0] <= res && mx[l][0] >= res){
				for(int i = l + 1; i <= r; ++i)s[i] = '1';
				r = l; break;
			}
		}else{
			for(int l = r - 1; l >= 0; --l)if(mi[l][1] <= res - sum[r] + sum[l] && mx[l][1] >= res - sum[r] + sum[l]){
				for(int i = l + 1; i <= r; ++i)s[i] = '0';
				res = res - sum[r] + sum[l]; r = l; break;
			}
		}
		v ^= 1;
	}
	for(int i = 1; i <= n; ++i)printf("%c",s[i]); printf("\n");
}
void solve(){
	n = read(), a = read(), b = read();
	scanf("%s",s + 1);
	for(int i = 1; i <= n; ++i)sum[i] = sum[i - 1] + (s[i] == '?');
	int len = 0, l = 0, c0 = 0;
	for(int i = 1; i <= n; ++i)
		if(s[i] == '?')len = 0;
		else{
			if(s[i] == s[i - 1])++len;
			else len = 1;
			l = max(l, len);
		}
	int r = max(c0, n - c0), ans = r;
	while(l <= r){
		int mid = (l + r) >> 1;
		if(check(mid))r = mid - 1, ans = mid;
		else l = mid + 1;
	}
	printf("%d\n",ans);
	if(type)print(ans);
}
int main(){
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
	T = read(), type = read();
	for(int i = 1; i <= T; ++i)solve();
	return 0;
}

B. 划分线段

线段的关系是一个树形结构,考虑进行树形 \(DP\)

\(f_{x, y, 0 / 1, 0 / 1}\) 表示考虑 \(x\) 子树,需要其祖先在里面选 \(y\) 个区间,左端/右端是否需要祖先选择的贡献

贡献预先统计好,

考虑到一棵树最多填 \(2size - 1\) 个区间,所以 \(y\) 只枚举到 \(size_x\) 树形背包是 \(n^2\)

一种好写的做法是,离散化值域,对线段按照长度排序,每次处理完一个线段在左端点位置记录线段编号

这样一个线段的儿子就是从左往右扫区间,扫到一个线段记为儿子,然后跳到右端点继续。

转移先把每个儿子左侧的段给儿子,然后背包拼接,最后把最右侧的段加上。

需要注意第一个儿子的值应该直接赋给当前点

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 5005;
int n, l[maxn], r[maxn], tmp[maxn + maxn], m, p[maxn], id[maxn + maxn];
bool cmp(const int &x, const int &y){return r[x] - l[x] < r[y] - l[y];}
int f[maxn][maxn][2][2], tl[maxn], si[maxn], g[maxn][2][2];
void ckmx(int &x, int y){if(y > x)x = y;}
void calc(int now){
	int las = tmp[l[now]];
	vector<int>rem;
	for(int i = l[now]; i < r[now]; ++i)if(id[i]){
		tl[id[i]] = tmp[l[id[i]]] - las; las = tmp[r[id[i]]];
		rem.push_back(id[i]); i = r[id[i]];
	}
	si[now] = 1;
	if(rem.empty())f[now][0][0][0] = tmp[r[now]] - tmp[l[now]];
	else{
		bool flag = true;
		for(int v : rem){
			for(int i = si[v] - 1; i >= 0; --i){
				f[v][i][1][0] += tl[v];
				f[v][i][1][1] += tl[v];
				ckmx(f[v][i + 1][1][0], f[v][i][0][0] + tl[v]);
				ckmx(f[v][i + 1][1][1], f[v][i][0][1] + tl[v]);
			}
			if(flag){
				for(int j = 0; j <= si[v]; ++j)for(int a = 0; a <= 1; ++a)for(int b = 0; b <= 1; ++b)if(a + b <= j)f[now][j][a][b] = f[v][j][a][b];
				si[now] += si[v]; flag = false;
			}else{
				for(int i = 0; i < si[now]; ++i)for(int p = 0; p <= 1; ++p)for(int q = 0; q <= 1; ++q)if(p + q <= i)
					for(int j = 0; j <= si[v]; ++j)for(int a = 0; a <= 1; ++a)for(int b = 0; b <= 1; ++b)if(a + b <= j)
						ckmx(g[i + j - (q && a)][p][b], f[now][i][p][q] + f[v][j][a][b]);
				si[now] += si[v];
				for(int i = 0; i < si[now]; ++i)for(int p = 0; p <= 1; ++p)for(int q = 0; q <= 1; ++q)f[now][i][p][q] = g[i][p][q], g[i][p][q] = 0;
			}
		}
		int tr = tmp[r[now]] - las;
		for(int i = si[now] - 1; i >= 0; --i)for(int p = 0; p <= 1; ++p)for(int q = 0; q <= 1; ++q)
			if(q)f[now][i][p][q] += tr; else ckmx(f[now][i + 1][p][1], f[now][i][p][q] + tr);
		for(int i = 0; i < si[now]; ++i)for(int p = 0; p <= 1; ++p)for(int q = 0; q <= 1; ++q){
			ckmx(f[now][i][p][q], f[now][i + 1][p][q]);
			if(!p)ckmx(f[now][i][p][q], f[now][i + 1][1][q]);
			if(!q)ckmx(f[now][i][p][q], f[now][i + 1][p][1]);
			if(p + q > i)f[now][i][p][q] = 0;
		}
		f[now][si[now]][0][0] = f[now][si[now]][1][0] = f[now][si[now]][0][1] = f[now][si[now]][1][1] = 0;
	}
	id[l[now]] = now;
}
int get_ans(){
	int ans = 0;
	for(int i = 1; i <= m; ++i)if(id[i]){ans += f[id[i]][0][0][0]; i = r[id[i]];}
	return ans;
}
int main(){
	freopen("segment.in","r",stdin);
	freopen("segment.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i)l[i] = read(), r[i] = read();
	if(n == 1){printf("%d\n",r[1] - l[1]); return 0;}
	for(int i = 1; i <= n; ++i)tmp[++m] = l[i], tmp[++m] = r[i];
	sort(tmp + 1, tmp + m + 1); m = unique(tmp + 1, tmp + m + 1) - tmp - 1;
	for(int i = 1; i <= n; ++i)l[i] = lower_bound(tmp + 1, tmp + m + 1, l[i]) - tmp, r[i] = lower_bound(tmp + 1, tmp + m + 1, r[i]) - tmp;
	for(int i = 1; i <= n; ++i)p[i] = i;
	sort(p + 1, p + n + 1, cmp);
	for(int i = 1; i <= n; ++i)calc(p[i]);
	printf("%d\n",get_ans());
	return 0;
}

2023冲刺国赛模拟33

A. 染色

可以操作分块根号分治,有人点分树

我写的树剖,考虑快速求 \(\sum dep_{lca}\)

对黑点到根的路径链加,查询点到根的路径上第一次遇到一个点则加上当前点的深度,实际就是对子树内黑点数量进行差分,对应乘上。

其实把 \(dep_{lca}\) 分到每条边上求也行,那是真的链加链查。。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 1e5 + 55;
int n, m, fa[maxn], d[maxn];
vector<int>e[maxn];
int si[maxn], son[maxn];
ll dep[maxn];
void dfs1(int x){
	si[x] = 1;
	for(int v : e[x]){
		dep[v] = dep[x] + d[v];
		dfs1(v); si[x] += si[v];
		if(si[v] > si[son[x]])son[x] = v;
	}
}
int tim, dfn[maxn], top[maxn], id[maxn];
void dfs2(int x, int tp){
	dfn[x] = ++tim; id[tim] = x; top[x] = tp;
	if(son[x])dfs2(son[x], tp);
	for(int v : e[x])if(v != son[x])dfs2(v, v);
}
struct data{
	ll val; int vr, cntl;
	friend data operator + (const data &x, const data &y){
		return data{x.val + y.val - 1ll * x.vr * y.cntl, y.vr, x.cntl};
	}
};
struct seg{
	struct node{
		data val; int tag;
	}t[maxn << 2 | 1];

	void build(int x, int l, int r){
		if(l == r){
			t[x].val.vr = dep[id[l]];
			return;
		}
		int mid = (l + r) >> 1;
		build(x << 1, l, mid);
		build(x << 1 | 1, mid + 1, r);
		t[x].val.vr = t[x << 1 | 1].val.vr;
	}
	void upd(int x, int val){
		t[x].tag += val;
		t[x].val.cntl += val;
		t[x].val.val += 1ll * t[x].val.vr * val;
	}
	void push_down(int x){
		if(t[x].tag){
			upd(x << 1, t[x].tag);
			upd(x << 1 | 1, t[x].tag);
			t[x].tag = 0;
		}
	}
	void modify(int x, int l, int r, int L, int R){
		if(L <= l && r <= R)return upd(x, 1);
		push_down(x); int mid = (l + r) >> 1;
		if(L <= mid)modify(x << 1, l, mid, L, R);
		if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R);
		t[x].val = t[x << 1].val + t[x << 1 | 1].val;
	}
	data query(int x, int l, int r, int L, int R){
		if(L <= l && r <= R)return t[x].val;
		push_down(x); int mid = (l + r) >> 1;
		if(R <= mid)return query(x << 1, l, mid, L, R);
		if(L > mid)return query(x << 1 | 1, mid + 1, r, L, R);
		return query(x << 1, l, mid, L, R) + query(x << 1 | 1, mid + 1, r, L, R);
	}
}T;
bool col[maxn]; ll sd; int cnt;
void paint(int x){
	if(col[x])return;
	col[x] = true;
	sd += dep[x]; ++cnt;
	while(x){
		T.modify(1, 1, n, dfn[top[x]], dfn[x]);
		x = fa[top[x]];
	}
}
ll query(int x){
	data tmp; tmp = {0, 0, 0};
	ll ans = 1ll * cnt * dep[x] + sd;
	while(x){
		tmp = T.query(1, 1, n, dfn[top[x]], dfn[x]) + tmp;
		x = fa[top[x]];
	}
	return ans - 2ll * tmp.val;
}
int main(){
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	n = read(), m = read();
	for(int i = 2; i <= n; ++i)e[fa[i] = read() + 1].push_back(i);
	for(int i = 2; i <= n; ++i)d[i] = read();
	dfs1(1); dfs2(1, 1); T.build(1, 1, n);
	for(int i = 1; i <= m; ++i){
		int op = read(), x = read() + 1;
		if(op == 1)paint(x);
		else printf("%lld\n",query(x));
	}
	return 0;
}

B. 寻宝游戏

对障碍和宝物定一个方向的射线,装压穿过路径次数的奇偶性

\(f_{x}{y}{s}\) 进行 \(BFS\)转移

射线可以定无限靠近某条横纵坐标的

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; bool f = false; char c = getchar();
	while(!isdigit(c))f = c == '-', c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 25;
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
char s[maxn][maxn];
int n, m, sx, sy, cnt, rx[maxn], ry[maxn], v[maxn], f[maxn][maxn][1 << 8], lg[1 << 8], sum[1 << 8];
struct node{int u, v, st;};
queue<node>q;
int get(int x, int y, int nx, int ny, int i){
	if(x == nx)return 0;
	if(nx < x && rx[i] == x && ry[i] < y)return 1;
	if(nx > x && rx[i] == nx && ry[i] < y)return 1;
	return 0;
}
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)scanf("%s",s[i] + 1);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)if(s[i][j] >= '0' &&	s[i][j] <= '9')
			rx[s[i][j] - '0'] = i, ry[s[i][j] - '0'] = j, ++cnt;
	for(int i = 1; i <= cnt; ++i)v[i] = read();
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)if(s[i][j] == 'B'){
			++cnt; rx[cnt] = i; ry[cnt] = j; v[cnt] = -0x3f3f3f3f;
		}else if(s[i][j] == 'S')sx = i, sy = j;
	memset(f, 0x3f, sizeof(f));
	f[sx][sy][0] = 0; q.push({sx, sy, 0});
	while(!q.empty()){
		int x = q.front().u, y = q.front().v, st = q.front().st; q.pop();
		for(int i = 0; i < 4; ++i){
			int nx = dx[i] + x, ny = dy[i] + y;
			if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && s[nx][ny] != '#' && s[nx][ny] != 'B' && (s[nx][ny] < '0' || s[nx][ny] > '9')){
				int ns = st;
				for(int j = 1; j <= cnt; ++j)ns ^= get(x, y, nx, ny, j) << (j - 1);
				if(f[nx][ny][ns] == f[0][0][0])f[nx][ny][ns] = f[x][y][st] + 1, q.push({nx, ny, ns});
			}
		}
	}
	int ans = -0x3f3f3f3f;
	for(int i = 0; i < cnt; ++i)lg[1 << i] = i + 1;
	for(int i = 1; i < (1 << cnt); ++i)sum[i] = max(sum[i ^ (i & -i)] + v[lg[i & -i]], -0x3f3f3f3f);
	for(int i = 0; i < (1 << cnt); ++i)ans = max(ans, sum[i] - f[sx][sy][i]);
	printf("%d\n",ans);
	return 0;
}

C. 点分治

转化为求不同的点分树个数

\(f_{x, i}\)表示考虑 \(x\) 子树, \(x\) 在点分树上深度为 \(j\) 的方案数。

转移考虑加上 \(x, v\) 之间的边对点分树合并的影响,考虑删掉 \(u / v\) 之后两棵树互不干扰,删掉某个点后与 \(u / v\) 不在同一连通块的不互相干扰,反应在点分树上就是按照任意顺序归并两条根到\(u, v\) 的路径,其他不变。

于是对\(f_v\) 做后缀和,然后\(f_{x, i}f_{v, j}\binom{i + j - 1}{j} - > f_{x, i + j}\)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int mod = 1e9 + 7, maxn = 5005;
vector<int>e[maxn];
int n, si[maxn], tmp[maxn], c[maxn][maxn], f[maxn][maxn];
void solve(int x, int fa){
	f[x][1] = si[x] = 1;
	for(int v : e[x])if(v != fa){
		solve(v, x);
		for(int j = 1; j <= si[x]; ++j)
			for(int k = 0; k <= si[v]; ++k)
				tmp[j + k] = (tmp[j + k] + 1ll * f[x][j] * f[v][k] % mod * c[j + k - 1][k]) % mod;
		si[x] += si[v];
		for(int j = 0; j <= si[x]; ++j)f[x][j] = tmp[j], tmp[j] = 0;
	}
	for(int i = si[x]; i >= 0; --i)f[x][i] = (f[x][i] + f[x][i + 1]) % mod;
}
int main(){
	freopen("dianfen.in","r",stdin);
	freopen("dianfen.out","w",stdout);
	n = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		e[u].push_back(v); e[v].push_back(u);
	}
	for(int i = 0; i <= n; ++i){
		c[i][0] = 1;
		for(int j = 1; j <= n; ++j)c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	}
	solve(1, 0);
	printf("%d\n",f[1][0]);
	return 0;
}

2023冲刺国赛模拟35

A. 树上游戏

感觉不是很可做,但是赛后看题解还是可以的。别被自己吓到。

初始都是\(G\) 中的点,考虑一个一个选到 \(P\)

每次选择的变化是个常数

image

唯一的问题是权值相同并且是祖先关系的时候,考虑对一个点有影响的是子树内和祖先链,对于深度较深的点能选择的子树范围较小,而且祖先链虽然长,但是存在深度的贡献,导致其一定不优于深度浅的

所以一定会先选祖先再选后代,所以计算仍然是个常数。

image

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 5e5 + 55;
vector<int>e[maxn];
int n, w[maxn], v[maxn], dep[maxn], tub[maxn];
struct BIT{
	int t[maxn];
	void add(int x, int val){while(x <= n){t[x] += val; x += (x & -x);}}
	int query(int x){int ans = 0; while(x){ans += t[x]; x -= (x & -x);} return ans;}
}T1, T2;
vector<int>res;
ll ans[maxn];
void solve(int x, int fa){
	ll tmp = dep[x] - tub[w[x]];
	++tub[w[x]];
	T1.add(n - w[x] + 1, 1);
	tmp += T1.query(n - w[x]);
	tmp -= T2.query(w[x] - 1);
	ans[n] += T2.query(w[x] - 1);
	T2.add(w[x], 1);
	for(int v : e[x])if(v != fa){
		dep[v] = dep[x] + 1; solve(v, x);
	}
	tmp -= T1.query(n - w[x]);
	T2.add(w[x], -1);
	--tub[w[x]]; res.push_back(tmp);
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i)w[i] = v[i] = read();
	sort(v + 1, v + n + 1);
	for(int i = 1; i <= n; ++i)w[i] = lower_bound(v + 1, v + n + 1, w[i]) - v;
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		e[u].push_back(v); e[v].push_back(u);
	}
	solve(1, 0); 
	sort(res.begin(), res.end());
	for(int i = 0; i < n; ++i)ans[n - i - 1] = ans[n - i] + i + res[i];
	for(int i = 0; i <= n; ++i)printf("%lld ",ans[i]); printf("\n");
	return 0;
}

C. 算法考试

考虑一个经典套路,枚举中位数 \(x\), 小于其的看成 \(-1\) , 等于的看成 \(0\) 大于的看成 \(+1\),那么中位数是好求的

而题目给出的求中位数的方法可以用树形\(DP\) 解决

\(f_{x, a, b, -1 / 0 / 1}\) 表示 \(x\) 子树内, 未确定的数填 \(-1 / 1\) 的有 \(a / b\) 个,计算出的中位数是 \(-1 / 0 / 1\) 的方案数

出来以后乘上 \(x^a(m - x)^b\) 贡献到答案

考虑优化,对于出现过的数把值域离散化,每一段的方案数是一样的,可以一起处理,那么需要快速求

\(\sum_{x = l}^{r}x^a(m - x)^b\)

考虑 \(\sum_{i = 0}^{n}i^a(m - i)^b\) 是个多项式,那么使用拉格朗日插值可以快速求解

再考虑 \(-1\) 的个数不多,可能成为中位数的数一定是已经确定的中位数前后的 \(k\) 段,可以减少枚举范围。

code
// ubsan: undefined
// accoders
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; bool f = false; char c = getchar();
	while(!isdigit(c))f = c == '-', c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 1e5 + 55, mod = 998244353;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
vector<vector<array<int, 3>>>f[maxn], tmp;
int n, m, k, a[maxn], b[maxn];
pii d[maxn]; int son[maxn][3]; int cnt, si[maxn];
int build(int l, int r){
	int now = ++cnt;
	if(r - l <= 1){
		d[now] = pii(l, r); 
		for(int i = l; i <= r; ++i)si[now] += (a[i] == -1);
	}else{
		int n = r - l + 1, left = (n + 2) / 3, right = n - (n / 3);
		son[now][0] = build(l, l + left - 1);
		son[now][1] = build(l + left, l + right - 1);
		son[now][2] = build(l + right, r);
		for(int v : son[now])si[now] += si[v];
	}
	f[now].resize(si[now] + 1);
	for(int i = 0; i <= si[now]; ++i)f[now][i].resize(si[now] - i + 1);
	return now;
}
pii v[maxn * 2]; int cv;
int mid(int a, int b, int c){
	if(a == b || a == c)return a;
	if(b == c)return b; return 1;
}
void merge(const int &tar, const int &x, const int &y, const int &z, const int &m1, const int &m2, const int &m3){
	for(int s1 = 0; s1 < 3; ++s1)
		for(int s2 = 0; s2 < 3; ++s2)
			for(int s3 = 0; s3 < 3; ++s3){
				int t = mid(s1, s2, s3);
				for(int a1 = 0; a1 <= m1; ++a1)
					for(int b1 = 0; b1 + a1 <= m1; ++b1)if(f[x][a1][b1][s1])
						for(int a2 = 0; a2 <= m2; ++a2)
							for(int b2 = 0; b2 + a2 <= m2; ++b2)if(f[y][a2][b2][s2])
								tmp[a1 + a2][b1 + b2][t] = (tmp[a1 + a2][b1 + b2][t] + 1ll * f[x][a1][b1][s1] * f[y][a2][b2][s2]) % mod;
				for(int a1 = 0; a1 <= m1 + m2; ++a1)
					for(int b1 = 0; a1 + b1 <= m1 + m2; ++b1)if(tmp[a1][b1][t]){
						for(int a3 = 0; a3 <= m3; ++a3)
							for(int b3 = 0; b3 + a3 <= m3; ++b3)if(f[z][a3][b3][s3])
								f[tar][a1 + a3][b1 + b3][t] = (f[tar][a1 + a3][b1 + b3][t] + 1ll * tmp[a1][b1][t] * f[z][a3][b3][s3]) % mod;
						tmp[a1][b1][t] = 0;
					}		

			}
}
int h[maxn], fac[maxn], ifac[maxn], pre[maxn], suf[maxn];
int lag(int n, int k){
	if(k <= n)return h[k];
	int ans = 0;
	pre[0] = 1; suf[n + 1] = 1;
	for(int i = 1; i <= n; ++i)pre[i] = 1ll * pre[i - 1] * (k - i) % mod;
	for(int i = n; i >= 1; --i)suf[i] = 1ll * suf[i + 1] * (k - i) % mod;
	for(int i = 1; i <= n; ++i){
		int tmp = 1ll * pre[i - 1] * suf[i + 1] % mod * ifac[i - 1] % mod * ifac[n - i] % mod * h[i] % mod;
		if((n - i) & 1)ans = (ans + mod - tmp) % mod;
		else ans = (ans + tmp) % mod;
	}
	return ans;
}
int calc(int l, int r, int a, int b){
	for(int i = 0; i <= a + b + 2; ++i)h[i] = 1ll * qpow(i, a) * qpow(m - i, b) % mod;
	for(int i = 1; i <= a + b + 2; ++i)h[i] = (h[i] + h[i - 1]) % mod;
	if(l == 0)return lag(a + b + 2, r);
	return (lag(a + b + 2, r) - lag(a + b + 2, l - 1) + mod) % mod;
}
int cs(int a, int b){
	if(a >= (n + 1) / 2)return 0;
	if(a + b >= (n + 1) / 2)return 1;
	return 2;
}
int ans;
void calc(int l, int r){
	for(int x = cnt; x >= 1; --x){
		for(int i = 0; i <= si[x]; ++i)
			for(int j = 0; i + j <= si[x]; ++j)
				for(int s = 0; s < 3; ++s)
					f[x][i][j][s] = 0;
		if(son[x][0])merge(x, son[x][0], son[x][1], son[x][2], si[son[x][0]], si[son[x][1]], si[son[x][2]]);
		else{
			if(d[x].second == d[x].first){
				if(a[d[x].first] == -1){
					f[x][0][0][1] = 1;
					f[x][1][0][0] = 1;
					f[x][0][1][2] = 1;
				}
				else if(a[d[x].first] < l)f[x][0][0][0] = 1;
				else if(a[d[x].first] > l)f[x][0][0][2] = 1;
				else f[x][0][0][1] = 1;
			}else{
				if(si[x] == 0){
					if(a[d[x].first] < l || a[d[x].second] < l)f[x][0][0][0] = 1;
					else if(a[d[x].first] == l || a[d[x].second] == l)f[x][0][0][1] = 1;
					else f[x][0][0][2] = 1;
				}else if(si[x] == 1){
					int v = a[d[x].first] == -1 ? a[d[x].second] : a[d[x].first];
					f[x][1][0][0] = 1;
					if(v < l)f[x][0][0][0] = f[x][0][1][0] = 1;
					else if(v == l)f[x][0][0][1] = f[x][0][1][1] = 1;
					else f[x][0][0][1] = f[x][0][1][2] = 1;
				}else{
					f[x][2][0][0] = f[x][0][0][1] = f[x][0][2][2] = 1;
					f[x][1][0][0] = f[x][1][1][0] = f[x][0][1][1] = 2;
				}
			}
		}
	}	
	int up = 0, down = 0, eq = 0;
	for(int i = 1; i <= n; ++i)if(a[i] != -1){
		if(a[i] > l)++up;
		else if(a[i] == l)++eq;
		else ++down;
	}
	for(int i = 0; i <= si[1]; ++i)
		for(int j = 0; i + j <= si[1]; ++j)
			if(cs(i + down, si[1] - i - j + eq) == 1 && f[1][i][j][1])
				ans = (ans + 1ll * calc(l, r, i, j) * f[1][i][j][1]) % mod;
}
int main(){
	// freopen("algorithm.in","r",stdin);
	// freopen("algorithm.out","w",stdout);
	n = read(), m = read(); 
	for(int i = 1; i <= n; ++i)a[i] = read(); 
	build(1, n); int p = 0; tmp.resize(si[1] + 1); for(int i = 0; i <= si[1]; ++i)tmp[i].resize(si[1] - i + 1);
	for(int i = 1; i <= n; ++i)if(a[i] == -1)++k; else b[++p] = a[i];
	sort(b + 1, b + p + 1); int md = b[(p + 1) / 2];
	b[++p] = 0; b[++p] = m; sort(b + 1, b + p + 1); 
	p = unique(b + 1, b + p + 1) - b - 1;
	int las = 0;
	for(int i = 1; i <= p; ++i){
		if(las < b[i])v[++cv] = pii(las, b[i] - 1);
		v[++cv] = pii(b[i], b[i]);
		las = b[i] + 1;
	}
	fac[0] = ifac[0] = 1; for(int i = 1; i <= k + 5; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	ifac[k + 5] = qpow(fac[k + 5], mod - 2); for(int i = k + 5 - 1; i >= 1; --i)ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
	int pos = 0;
	for(int i = 1; i <= cv; ++i)if(v[i].first == md){pos = i; break;}
	for (int i = max(1, pos - k - k); i <= min(cv, pos + k + k); ++i)calc(v[i].first, v[i].second);
	ans = 1ll * ans * qpow(qpow(m + 1, k), mod - 2) % mod;
	printf("%d\n",ans);
	return 0;
}

2023冲刺国赛模拟36

A. 染色题

观察性质可以得到奇数位置和偶数位置分别为两种颜色,这样就能满足相邻的条件

于是 \(DP\) 位置 \(i\) 满足 \(col_i != col_{i +2}\)

区间的限制相当于奇数/偶数\(col_i != col_{i +2}\)的位置不能同时出现在区间里, \(f_i\) 表示考虑完前 \(i\) 个数, \(col_i != col_{i + 2}\) 的方案数,前缀和优化即可快速转移

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int mod = 998244353, maxn = 1e6 + 5;
int n, m, ans, f[maxn], s[2][maxn], mx[maxn];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
int main(){
	freopen("colour.in","r",stdin);
	freopen("colour.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)mx[i] = i;
	for(int i = 1; i <= m; ++i){
		int l = read(), r = read();
		if(r >= 2)mx[r - 2] = min(mx[r - 2], l - 1);
	}
	for(int i = n - 1; i >= 1; --i)mx[i] = min(mx[i + 1], mx[i]);
	f[0] = s[0][0] = 1; ans = 1;
	for(int i = 1; i <= n - 2; ++i){
		s[0][i] = s[0][i - 1]; s[1][i] = s[1][i - 1];
		f[i] = (s[i & 1][i - 1] + s[(i & 1) ^ 1][mx[i]]) % mod;
		add(s[i & 1][i], f[i]); add(ans, f[i]);
	}
	ans = 8ll * ans % mod;
	printf("%d\n",ans);
	return 0;
}

B. 石头剪刀布

维护三个变量分别表示三种人胜出的概率,合并转移是容易的

预处理 \(f_{i, j}\) 表示 \([i, i + 2^j - 1]\) 的结果

\(g_{i, j}\) 表示 \([i, i +2^j - 2]\) 轮空一人的结果,对区间内所有合法轮空的人求和

那么对询问进行分治合并是简单的

部分分给的很好,但是没有想分治。。。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int mod = 998244353, maxn = 3e5 + 5;
int qpow(int x, int y){
	int ans = 1; 
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n, m, inv[maxn], iv3, iv32;
struct node{
	ll r, p, s;
	node(){r = p = s = 0;}
	node(ll _r, ll _p, ll _s){r = _r, p = _p, s = _s;}
	friend node operator * (const node &x, const node &y){
		node ans;
		ans.r = (x.r * y.r % mod + (x.r * y.p + x.p * y.r) % mod * iv3 % mod + (x.r * y.s + x.s * y.r) % mod * iv32 % mod) % mod;
		ans.p = (x.p * y.p % mod + (x.r * y.p + x.p * y.r) % mod * iv32 % mod + (x.p * y.s + x.s * y.p) % mod * iv3 % mod) % mod;
		ans.s = (x.s * y.s % mod + (x.s * y.p + x.p * y.s) % mod * iv32 % mod + (x.r * y.s + x.s * y.r) % mod * iv3 % mod) % mod;
		return ans;
	}
	friend node operator * (const node &x, const int &y){return node{1ll * x.r * y % mod, 1ll * x.p * y % mod, 1ll * x.s * y % mod};}
	friend node operator + (const node &x, const node &y){
		node ans; 
		ans.r = (x.r + y.r) % mod; 
		ans.s = (x.s + y.s) % mod; 
		ans.p = (x.p + y.p) % mod;
		return ans;
	}
};
char s[maxn]; 
node d[maxn], f[maxn][19], g[maxn][19];
node solve(int L, int R, int l, int r){
	if(l <= L && R <= r)return g[L][__lg(R - L + 2)];
	int mid = (L + R) >> 1;
	if(r < mid)return solve(L, mid - 1, l, r) * f[mid][__lg(R - mid + 1)];
	if(l > mid)return f[L][__lg(mid - L + 1)] * solve(mid + 1, R, l, r);
	return (solve(L, mid - 1, l, r) * f[mid][__lg(R - mid + 1)]) + (f[L][__lg(mid - L + 1)] * solve(mid + 1, R, l, r));
}
int main(){
	freopen("rps.in","r",stdin);
	freopen("rps.out","w",stdout);
	n = read(), m = read();
	inv[1] = 1; for(int i = 2; i <= n; ++i)inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
	iv3 = qpow(3, mod - 2); iv32 = 2ll * iv3 % mod;
	scanf("%s",s + 1);
	for(int i = 1; i <= n; ++i)
		if(s[i] == 'R')d[i].r = 1; 
		else if(s[i] == 'P')d[i].p = 1;
		else if(s[i] == 'S')d[i].s = 1;
	for(int i = 1; i <= n; ++i)f[i][0] = d[i];
	for(int i = 1; (1 << i) <= n; ++i)
		for(int j = 1; j + (1 << i) - 1 <= n; ++j)
			f[j][i] = f[j][i - 1] * f[j + (1 << (i - 1))][i - 1];
	for(int i = 1; i <= n; ++i)g[i][1] = d[i];
	for(int i = 2; (1 << i) - 1 <= n; ++i)
		for(int j = 1; j + (1 << i) - 2 <= n; ++j)
			g[j][i] = (g[j][i - 1] * f[j + (1 << (i - 1)) - 1][i - 1]) + (f[j][i - 1] * g[j + (1 << (i - 1))][i - 1]);
	for(int i = 1; i <= m; ++i){
		int L = read(), R = read(), l = read(), r = read();
		int p = (r - L) / 2 - (l - L - 1) / 2 + (l == L);
		if(R - L == 0)printf("%d\n",(int)d[L].r);
		else printf("%d\n", (int)(1ll * solve(L, R, l, r).r * inv[p] % mod));
	}
	return 0;
}

C. 树状数组

预处理 \(f_{u, i, 1 / 0}\) 表示\([0, u - 1]\) 位为 \(0\), \(u\)\(1 / 0\)\(i\) 开始,下一次满足 \([0, u]\) 全为 \(0\) 的位置。

处理 \(ans_i\) 表示 \(l = i x = 0\) 的答案

对询问枚举最大的 \(u\) 然后向后跳,每次跳不会影响高位,高位可以直接前缀异或求解,低位直接取全 \(0\),没有 \(u\) 直接用 \(ans\)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
	int x = 0; bool f = false; char c = getchar();
	while(!isdigit(c))f = c == '-', c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 5e5 + 5, inf = 0x3f3f3f3f;
int n, m, k, A, B, a[maxn], lim;
int f[maxn][30][2], sx[maxn], ans[maxn];

int main(){
	freopen("fenwick.in","r",stdin);
	freopen("fenwick.out","w",stdout);
	n = read(), m = read(), k = read(), A = read(), B = read(); lim = 1 << k;
	for(int i = 1; i <= n; ++i)a[i] = read();
	for(int i = 1; i <= n + 1; ++i)sx[i] = sx[i - 1] ^ (a[i] == -1 ? 0 : a[i]);
	f[n + 1][0][0] = n + 2; f[n + 1][0][1] = inf;
	for(int i = n; i >= 1; --i)
		if(a[i] == -1)f[i][0][0] = f[i][0][1] = i + 1;
		else if(a[i] & 1){
			f[i][0][0] = f[i + 1][0][1];
			f[i][0][1] = i + 1;
		}else{
			f[i][0][0] = i + 1;
			f[i][0][1] = f[i + 1][0][1];
		}
	for(int u = 1; u < k; ++u){
		f[n + 1][u][0] = n + 1; f[n + 1][u][1] = inf;
		for(int i = n; i >= 1; --i)
			if(a[i] == -1)f[i][u][0] = f[i][u][1] = i + 1;
			else{
				f[i][u][0] = f[i][u][1] = inf;
				if(f[i][u - 1][0] != inf){
					if(((sx[i - 1] ^ sx[f[i][u - 1][0] - 1]) >> u) & 1){
						f[i][u][0] = f[f[i][u - 1][0]][u][1];
						f[i][u][1] = f[i][u - 1][0];
					}else{
						f[i][u][0] = f[i][u - 1][0];
						f[i][u][1] = f[f[i][u - 1][0]][u][1];
					} 
				}
			}
	}
	for(int i = n; i >= 1; --i){
		int u = -1;
		for(int j = k - 1; j >= 0; --j)if(f[i][j][0] != inf){u = j; break;}
		if(u == -1)ans[i] = sx[i - 1] ^ sx[n];
		else ans[i] = (((sx[i - 1] ^ sx[n]) >> (u + 1)) << (u + 1)) | (ans[f[i][u][0]] & ((1 << (u + 1)) - 1));
	}
	int las = 0;
	for(int i = 1; i <= m; ++i){
		int l = read() ^ ((1ll * A * las + B) % n);
		int x = read() ^ ((1ll * A * las + B) % lim);
		for(int j = 0; j <= k - 1; ++j)if((x >> j) & 1){
			if(f[l][j][1] == inf){
				las = (x ^ (((sx[l - 1] ^ sx[n]) >> j) << j)) | (ans[l] & ((1 << j) - 1));
				break;
			}
			x ^= ((sx[f[l][j][1] - 1] ^ sx[l - 1]) >> (j + 1)) << (j + 1);
			x ^= (1 << j); l = f[l][j][1];
		}
		if(!x)las = ans[l];
		printf("%d\n",las);
	}
	return 0;
}

2023冲刺国赛模拟37 A. 数叶子

单独考虑一个点作为叶子的方案数,发现是有且仅有一条与之相邻的边在区间内。

于是枚举区间的长度,可以得到一个度数为 \(deg\) 的点,其贡献为

\[ deg \times A_{m - deg}^{n - 1 - deg} \sum_{len = 1}^{m}len(m - len + 1)A_{m - len}^{deg - 1} \]

选择一条边\(deg\) 给其他边分配权值\(A\) 该边有 \(len\) 种取值, 区间有 \(m - len + 1\),与该点相邻的其他边分配边权

后面的 \(\sum\) 是一个关于 \(len\) 的多项式,可以拉格朗日插值

注意关于 \(m\) 的下降幂暴力算复杂度是错的,正确做法是转成上升幂预处理

2023冲刺国赛模拟38

A. 智力游戏

直接搜索,用\(bfs\) 可过,我写的 \(dfs\) \(T\) 飞了,可以限定递归层数,或者直接迭代加深?反正很不优。

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<char, int> pci;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
typedef array<array<short, 6>, 6>  arr;
arr a; map<arr, short>mp; int lim;
struct node{int id; char type; int dis;};
vector<node>res, tmp; int ans = 0x3f3f3f3f;
void dfs(int step){
	if(step >= ans || step > lim)return;
	if(a[2][4] == 1 && a[2][5] == 1){ans = step; res = tmp; return;}
	if(mp.count(a) && mp[a] <= step)return;
	mp[a] = step;

	for(int i = 0; i < 6; ++i)
		for(int j = 0; j < 6; ++j)if(a[i][j]){
			if(i && a[i - 1][j] == a[i][j])continue;
			if(j && a[i][j - 1] == a[i][j])continue;
			int id = a[i][j];
			if(id == a[i][j + 1]){
				int l = j, r = j + 1; while(r < 5 && a[i][r + 1] == id)++r;
				int p = 0; while(l - p > 0 && !a[i][l - p - 1])++p;
				for(int k = p; k >= 1; --k){
					tmp.push_back({id, 'L', k});
					for(int b = l; b <= r; ++b)a[i][b] = 0;
					for(int b = l - k; b <= r - k; ++b)a[i][b] = id;
					dfs(step + 1);
					for(int b = l - k; b <= r - k; ++b)a[i][b] = 0;
					for(int b = l; b <= r; ++b)a[i][b] = id;
					tmp.pop_back();
				}
				p = 0; while(r + p < 5 && !a[i][r + p + 1])++p;
				for(int k = p; k >= 1; --k){
					tmp.push_back({id, 'R', k});
					for(int b = l; b <= r; ++b)a[i][b] = 0;
					for(int b = l + k; b <= r + k; ++b)a[i][b] = id;
					dfs(step + 1);
					for(int b = l + k; b <= r + k; ++b)a[i][b] = 0;
					for(int b = l; b <= r; ++b)a[i][b] = id;
					tmp.pop_back();
				}
			}else{
				int l = i, r = i + 1; while(r < 5 && a[r + 1][j] == id)++r;
				int p = 0; while(l - p > 0 && !a[l - p - 1][j])++p;
				for(int k = p; k >= 1; --k){
					tmp.push_back({id, 'U', k});
					for(int b = l; b <= r; ++b)a[b][j] = 0;
					for(int b = l - k; b <= r - k; ++b)a[b][j] = id;
					dfs(step + 1);
					for(int b = l - k; b <= r - k; ++b)a[b][j] = 0;
					for(int b = l; b <= r; ++b)a[b][j] = id;
					tmp.pop_back();
				}
				p = 0; while(r + p < 5 && !a[r + p + 1][j])++p;
				for(int k = p; k >= 1; --k){
					tmp.push_back({id, 'D', k});
					for(int b = l; b <= r; ++b)a[b][j] = 0;
					for(int b = l + k; b <= r + k; ++b)a[b][j] = id;
					dfs(step + 1);
					for(int b = l + k; b <= r + k; ++b)a[b][j] = 0;
					for(int b = l; b <= r; ++b)a[b][j] = id;
					tmp.pop_back();
				}
			}
		}
}
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	for(int i = 0; i < 6; ++i)
		for(int j = 0; j < 6; ++j)a[i][j] = read();
	lim = 10;
	while(ans == 0x3f3f3f3f){
		mp.clear();
		dfs(0);
		lim += 3;
	}
	printf("%d\n",ans);
	for(node v : res)printf("%d %c %d\n",v.id, v.type, v.dis);
	return 0;
}

B. 区域划分

性质/结论题,感觉不如**?

image

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 1e5 + 55;
int n, a[maxn], c0, f1, f2, b[maxn], top, tmp[maxn];
int main(){
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	bool flag = true;
	for(int i = 1; i < n; ++i)flag &= (bool)(a[i] ^ a[i + 1]); flag &= (bool)(a[n] ^ a[1]);
	for(int i = 1; i <= n; ++i)if(a[i] == 0)++c0; else if(a[i] == 1)f1 = true; else f2 = true;
	flag &= (bool)f1 & (bool)f2 & (bool)c0;
	if(!flag){printf("No\n"); return 0;}
	printf("Yes\n");
	for(int i = 1; i <= n; ++i)b[i] = i;
	if(c0 > 1){
		for(int i = 1; i <= n; ++i){
			b[++top] = i;
			if(top >= 3 && c0 > 1 && a[b[top]] != a[b[top - 1]] && a[b[top]] != a[b[top - 2]] && a[b[top - 1]] != a[b[top - 2]] && a[b[top - 1]] == 0){
				printf("%d %d\n",b[top - 2], b[top]); b[top - 1] = b[top]; --top; --c0; 
			}
		}
		n = top;
		if(c0 > 1 && a[b[1]] == 0 && a[b[2]] != a[b[n]] && a[b[2]] && a[b[n]]){
			printf("%d %d\n",b[2], b[n]); 
			for(int i = 2; i <= n; ++i)b[i - 1] = b[i]; --n;
		}
		if(c0 > 1 && a[b[n]] == 0 && a[b[1]] != a[b[n - 1]] && a[b[1]] && a[b[n - 1]]){
			printf("%d %d\n",b[1], b[n - 1]); --n;
		}
		if(c0 > 1){
			int pos = 0;
			for(int i = 1; i <= n; ++i)if(a[b[i]] == 0)if(a[b[(i + 1) % n + 1]]){pos = i; break;}
			for(int i = 1; i <= n; ++i)tmp[i] = b[i]; top = 0;
			for(int i = pos; i <= n; ++i)b[++top] = tmp[i];
			for(int i = 1; i < pos; ++i)b[++top] = tmp[i];
			top = 3;
			for(int i = 4; i <= n; ++i)if(a[b[i]] == 0){
				printf("%d %d\n",b[i], b[top - 1]);
				printf("%d %d\n",b[i + 1], b[top - 1]);
				--top; --c0;	
			}else b[++top] = b[i];
			n = top;
		}
	}
	int pos = 0;
	for(int i = 1; i <= n; ++i)if(a[b[i]] == 0){pos = i; break;}
	for(int i = pos + 2; i <= n - (pos == 1); ++i)printf("%d %d\n",b[pos], b[i]);
	for(int i = pos - 2; i >= 1 + (pos == n); --i)printf("%d %d\n",b[pos], b[i]);
	return 0;
}

C. 基因识别

操作分块,建立广义 \(SAM\), 每 \(B\) 次扫一遍得到新的权值,过程可以用 \(unordered\_map\) 启发式合并(好像可以用 \(bitset\)

对在同一块内的我们要快速判断某个串是否包含当前串,其实就是在 \(SAM\) 上存在某个点在当前串结尾位置的子树内,预先把每个串出现的 \(dfn\) 序拿出来排序,每次可以二分快速判断

复杂度是\(nlogn\sqrt n\)级别的(默认 $n ,m $ 同阶) 但是跑的贼慢。

题解直接莫队。?

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; bool f = false; char c = getchar();
	while(!isdigit(c))f = c == '-', c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 6e5 + 5;
char s[maxn]; 
int n, m, dfn[maxn], tim, id[maxn], dfnr[maxn];
struct node{int ch[26], fa, len;}d[maxn];
ll sval[maxn], dtv[maxn], val[maxn];
vector<int>pos[maxn], son[maxn];
vector<pii>rem;
unordered_map<int, bool>mp[maxn];
struct SAM{
	int cnt = 1, las = 1;
	int insert(int c){
		if(d[las].ch[c]){
			int fa = las, x = d[las].ch[c];
			if(d[fa].len + 1 == d[x].len)las = x;
			else{
				int now = las = ++cnt;
				d[now] = d[x]; d[now].len = d[fa].len + 1;
				for(; fa && d[fa].ch[c] == x; fa = d[fa].fa)d[fa].ch[c] = now;
				d[x].fa = now;
			}
		}else{
			int fa = las, now = las = ++cnt;
			d[now].len = d[fa].len + 1;
			for(; fa && !d[fa].ch[c]; fa = d[fa].fa)d[fa].ch[c] = now;
			if(!fa)d[now].fa = 1;
			else{
				int x = d[fa].ch[c];
				if(d[x].len == d[fa].len + 1)d[now].fa = x;
				else{
					int clone = ++cnt;
					d[clone] = d[x]; 
					d[clone].len = d[fa].len + 1;
					d[x].fa = d[now].fa = clone;
					for(; fa && d[fa].ch[c] == x; fa = d[fa].fa)d[fa].ch[c] = clone;
				}
			}
		}
		return las;
	}
	void dfs(int x){
		id[dfn[x] = ++tim] = x;
		for(int v : son[x])dfs(v);
		dfnr[x] = tim;
	}
	void build(){
		for(int i = 2; i <= cnt; ++i)son[d[i].fa].push_back(i);
		dfs(1);		
	}
	int match(){
		int len = strlen(s + 1), now = 1;
		for(int i = 1; i <= len; ++i){
			if(!d[now].ch[s[i] - 'a'])return -1;
			now = d[now].ch[s[i] - 'a'];
		}
		return now;
	}
}S;
void merge(int x, int y){
	if(mp[x].size() < mp[y].size())swap(mp[x], mp[y]), sval[x] = sval[y];
	for(auto it : mp[y])if(it.second){
		if(!mp[x][it.first]){
			sval[x] += val[it.first];
			mp[x][it.first] = true;
		}
	}
	mp[y].clear();
}
void mergedt(int x, int y){
	if(mp[x].size() < mp[y].size())swap(mp[x], mp[y]), dtv[x] = dtv[y];
	for(auto it : mp[y])if(it.second){
		if(!mp[x][it.first]){
			dtv[x] += val[it.first];
			mp[x][it.first] = true;
		}
	}
	mp[y].clear();
}
bool check(int i, int l, int r){
	int p = lower_bound(pos[i].begin(), pos[i].end(), l) - pos[i].begin();
	if(p < pos[i].size() && pos[i][p] <= r)return true;
	return false;
}
void clear(){
	for(pii v : rem)val[v.first] += v.second;
	for(int i = 1; i <= n; ++i)if(val[i]){
		for(int v : pos[i]){
			if(!mp[id[v]][i]){
				mp[id[v]][i] = 1;
				dtv[id[v]] += val[i];	
			}
		}
	}
	for(int i = S.cnt; i > 1; --i){int x = id[i]; mergedt(d[x].fa, x);}
	for(int i = 1; i <= S.cnt; ++i)sval[i] += dtv[i], dtv[i] = 0;
	for(pii v : rem)val[v.first] = 0;
	mp[1].clear(); rem.clear();
}
// int rr;
// double las;
// void print(int v, double t){cerr << v << " " << t - las << endl; las = t;}
int main(){
	freopen("dna.in","r",stdin);
	freopen("dna.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= n; ++i){
		val[i] = read(); S.las = 1;
		scanf("%s",s + 1); 
		int len = strlen(s + 1);
		for(int j = 1; j <= len; ++j){
			S.insert(s[j] - 'a');
			pos[i].push_back(s[j] - 'a');
		}
	}
	S.build();
	for(int i = 1; i <= n; ++i){
		int now = 1;
		for(int j = 0; j < pos[i].size(); ++j){
			now = d[now].ch[pos[i][j]];
			pos[i][j] = dfn[now];
			if(!mp[now].count(i)){
				mp[now][i] = true;
				sval[now] += val[i];
			}
		}
		sort(pos[i].begin(), pos[i].end());
	}
	for(int i = S.cnt; i > 1; --i){int x = id[i]; merge(d[x].fa, x);}
	mp[1].clear();
	for(int i = 1; i <= n; ++i)val[i] = 0;
	int B = sqrt(S.cnt * 1000);
	for(int i = 1; i <= m; ++i){
		if(read() & 1){
			scanf("%s",s + 1);
			int p = S.match();
			if(p == -1)printf("0\n");
			else{
				ll ans = sval[p];
				for(pii v : rem)if(check(v.first, dfn[p], dfnr[p])){ans += v.second;}
				printf("%lld\n",ans);
			}
		}else{int x = read(), y = read(); rem.push_back(pii(x, y));}
		if(i % B == 0){clear();}
	}
	return 0;
}
posted @ 2023-07-09 10:59  Chen_jr  阅读(45)  评论(0编辑  收藏  举报