冲刺国赛题目乱写
因为本人太菜,一车题没改,所以把部分题题解放到这里
自测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\) 集合内的边的数量。
看成多项式
设 \(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\)
每次选择的变化是个常数
唯一的问题是权值相同并且是祖先关系的时候,考虑对一个点有影响的是子树内和祖先链,对于深度较深的点能选择的子树范围较小,而且祖先链虽然长,但是存在深度的贡献,导致其一定不优于深度浅的
所以一定会先选祖先再选后代,所以计算仍然是个常数。
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\) 给其他边分配权值\(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. 区域划分
性质/结论题,感觉不如**?
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;
}