2024.03 别急记录
1. IOI2018 - 狼人 / werewolf [省选/NOI-]
题意简述:多次询问求是否存在一条 \(s\to t\) 的路径 \(a_1,a_2,...,a_k\) 和路径上一个点 \(a_i\) 使 \(a_1,...,a_i\in[L,n]\) 且 \(a_i,...,a_k\in[1,R]\)。
首先求出两棵 kruskal 重构树:
- 第一棵树边权值设为 \(\min(u,v)\),由大到小插入每一条边,树上点权设为子树内点权 \(\min\),则可通过倍增跳树的方式求得每个点经过 \(\geq L\) 的点能到达的点集合(一个子树内所有叶子);
- 第二棵树边权值设为 \(\max(u,v)\),由小到大插入每一条边,树上点权设为子树内点权 \(\max\)。
因为一个子树内所有叶子是 dfn 序上一段连续区间,dfn 序又是一个排列,问题转化为了两个排列 \(a,b\),求 \(a_{[l,r]},b_{[p,q]}\) 是否有交。
设 \(a^{-1}_i\) 表示 \(i\) 在 \(a\) 中的出现位置,若有交则等价于存在 \(x\in[p,q]\) 使 \(a^{-1}_{b_x}\in{[l,r]}\)。设排列 \(c_i=a^{-1}_{b_i}\),则等价于 \(c_{[p,q]}\) 中存在值域 \(\in [l,r]\) 的数。
考虑使用主席树维护 \(c\)。每次询问查询 \(c_{[1,q]},c_{[1,p-1]}\) 中值域 \(\in[l,r]\) 的数的个数。若二者不等则答案为 \(1\),否则为 \(0\)。
点击查看代码
const int N = 4e5 + 10;
int n, m, Q, fa[N], anti[N], c[N];
int ancmin[N][20], lenmin[N], ancmax[N][20], lenmax[N];
int dfnmin[N], dfnmax[N], mnmin[N], mxmin[N], mnmax[N], mxmax[N];
pair<int, int> emin[N], emax[N];
vector<int> gmin[N], gmax[N];
bool cmpmin(pair<int, int> x, pair<int, int> y){
return min(x.first, x.second) > min(y.first, y.second);
}
bool cmpmax(pair<int, int> x, pair<int, int> y){
return max(x.first, x.second) < max(y.first, y.second);
}
int gf(int x){
return x == fa[x] ? x : fa[x] = gf(fa[x]);
}
void dfsmin(int x){
if(x <= n){
++ *dfnmin;
dfnmin[*dfnmin] = x;
mnmin[x] = mxmin[x] = *dfnmin;
return;
}
mnmin[x] = 1e9;
for(int i : gmin[x]){
dfsmin(i);
mnmin[x] = min(mnmin[i], mnmin[x]);
mxmin[x] = max(mxmin[i], mxmin[x]);
}
}
void dfsmax(int x){
if(x <= n){
++ *dfnmax;
dfnmax[*dfnmax] = x;
mnmax[x] = mxmax[x] = *dfnmax;
return;
}
mnmax[x] = 1e9;
for(int i : gmax[x]){
dfsmax(i);
mnmax[x] = min(mnmax[i], mnmax[x]);
mxmax[x] = max(mxmax[i], mxmax[x]);
}
}
int jmpmin(int x, int val){
for(int i = 19; i >= 0; -- i){
if(lenmin[ancmin[x][i]] >= val){
x = ancmin[x][i];
}
}
return x;
}
int jmpmax(int x, int val){
for(int i = 19; i >= 0; -- i){
if(lenmax[ancmax[x][i]] <= val){
x = ancmax[x][i];
}
}
return x;
}
int rt[N], t[N*40], cnt, ls[N*40], rs[N*40];
int add(int p, int l, int r, int x, int v){
++ cnt;
t[cnt] = t[p];
ls[cnt] = ls[p];
rs[cnt] = rs[p];
p = cnt;
if(l == r){
t[p] += v;
} else {
int mid = l + r >> 1;
if(x <= mid){
ls[p] = add(ls[p], l, mid, x, v);
} else {
rs[p] = add(rs[p], mid+1, r, x, v);
}
t[p] = t[ls[p]] + t[rs[p]];
}
return p;
}
int qry(int p, int l, int r, int ql, int qr){
if(!p || qr < l || r < ql){
return 0;
} else if(ql <= l && r <= qr){
return t[p];
} else {
int mid = l + r >> 1;
return qry(ls[p], l, mid, ql, qr)
+ qry(rs[p], mid+1, r, ql, qr);
}
}
vector<int> check_validity(
int N,
vector<int> X,
vector<int> Y,
vector<int> S,
vector<int> E,
vector<int> L,
vector<int> R) {
vector<int> ans;
n = N;
m = X.size();
Q = S.size();
for(int i = 1; i <= m; ++ i){
emin[i].first = X[i-1];
emin[i].second = Y[i-1];
++ emin[i].first;
++ emin[i].second;
emax[i] = emin[i];
}
sort(emin + 1, emin + m + 1, cmpmin);
iota(fa + 1, fa + n + n + 1, 1);
iota(lenmin + 1, lenmin + n + 1, 1);
int cnt = n;
for(int i = 1; i <= m; ++ i){
int x = gf(emin[i].first), y = gf(emin[i].second);
if(x != y){
fa[x] = fa[y] = ++ cnt;
gmin[cnt].push_back(x);
gmin[cnt].push_back(y);
ancmin[x][0] = ancmin[y][0] = cnt;
lenmin[cnt] = min(lenmin[x], lenmin[y]);
}
}
dfsmin(cnt);
for(int i = cnt; i >= 1; -- i){
for(int j = 1; j < 20; ++ j){
ancmin[i][j] = ancmin[ancmin[i][j-1]][j-1];
}
}
sort(emax + 1, emax + m + 1, cmpmax);
iota(fa + 1, fa + n + n + 1, 1);
memset(lenmax, 0x3f, sizeof(lenmax));
iota(lenmax + 1, lenmax + n + 1, 1);
cnt = n;
for(int i = 1; i <= m; ++ i){
int x = gf(emax[i].first), y = gf(emax[i].second);
if(x != y){
fa[x] = fa[y] = ++ cnt;
gmax[cnt].push_back(x);
gmax[cnt].push_back(y);
ancmax[x][0] = ancmax[y][0] = cnt;
lenmax[cnt] = max(lenmax[x], lenmax[y]);
}
}
dfsmax(cnt);
for(int i = cnt; i >= 1; -- i){
for(int j = 1; j < 20; ++ j){
ancmax[i][j] = ancmax[ancmax[i][j-1]][j-1];
}
}
for(int i = 1; i <= n; ++ i){
anti[dfnmin[i]] = i;
}
for(int i = 1; i <= n; ++ i){
c[i] = anti[dfnmax[i]];
rt[i] = add(rt[i-1], 1, n, c[i], 1);
}
for(int i = 0; i < Q; ++ i){
int st = S[i], ed = E[i], le = L[i], ri = R[i];
++ st;
++ ed;
++ le;
++ ri;
int x = jmpmin(st, le), y = jmpmax(ed, ri);
int l = mnmin[x], r = mxmin[x], p = mnmax[y], q = mxmax[y];
int v = qry(rt[q], 1, n, l, r) - qry(rt[p-1], 1, n, l, r);
if(v){
ans.push_back(1);
} else {
ans.push_back(0);
}
}
return ans;
}
2. **AGC020C - Median Sum [*2259]
子集和中位数 \(O(nV)\) 做法!
设 \(mid=\dfrac{\sum a_i}2,\operatorname{sum}(S)=\sum_{i\in S}a_i\),答案显然是 \(\min_S\{\operatorname{sum}(S)|\operatorname{sum}(S)\geq mid\}\)。显然有一个 \(O(\dfrac{n^2V}\omega)\) 的 bitset
做法,不多讲。
由于所有的子集 \(S\) 数量过于多,考虑提取出其中的一部分:
若子集 \(S\) 使得不存在 \(i\) 使 \(\operatorname{sum}(S)\) 与 \(\operatorname{sum}(S\setminus\{i\})\) 同时 \(> mid\) 或同时 \(\leq mid\),称这样的 \(S\) 为平衡解。
设 \(b=\max_i\{i|\operatorname{sum}(\{1,2,...,i\})\leq mid\}\),则易得 \(S=\{1,2,...,b\}\) 为一个平衡解,设其为初始平衡解。设答案为 \(T\),则易得 \(T\) 也为一个平衡解。
我们发现可以使用以下两种操作使得 \(S\) 变为任意一个平衡解,可以发现每次操作后 \(S\) 仍然为平衡解:
- 若 \(\operatorname{sum}(S)\leq mid\),选择一个 \(i\notin S\) 令 \(S\) 变为 \(S\cup\{i\}\)。
- 若 \(\operatorname{sum}(S)> mid\),选择一个 \(i\in S\) 令 \(S\) 变为 \(S\setminus\{i\}\)。
设 \(ok(s,t,w)=1\) 当且仅当存在一个平衡解 \(S\) 使得 \(\operatorname{sum}(S)=w\) 且 \(\forall 1\leq i< s,i\in S,\forall t< i \leq n,i\neq S\),否则 \(ok(s,t,w)=0\)
可以发现对于 \(s\leq s',t'\leq t\) 有 \(ok(s',t',w)\leq ok(s,t,w)\)。所以我们设 \(f(t,w)\) 表示 \(max_s\{s|ok(s,t,w)\}\),若不存在这样的 \(s\) 则 \(f(t,w)=0\)。那么存在平衡解 \(S\) 使得 \(\operatorname{sum}(S)=w\) 当且仅当 \(f(n,w)\neq 0\)。最后计算答案的时候枚举 \(w\) 查看 \(f(n,w)\) 的值即可。
发现 \(w\) 这一维实际上有用的只有 \(O(V)\) 个位置。因为对于平衡解 \(S\),\(\operatorname{sum}(S)\in (mid-V,mid+V]\)。所以 \(f(t,w)\) 状态总数是 \(O(nV)\) 的。
考虑 dp:
初始化 \(f(b,\operatorname{sum}(\{1,2,...,b\}))=b+1\)。原因显然。
从小到大枚举 \(i\in[b+1,n]\),转移:
- \(\forall w\in(mid-V,mid+V], f(i,w)\leftarrow f(i-1,w)\),表示不变。
- \(\forall w\in(mid-V,mid], f(i,w+a_i)\leftarrow f(i-1,w)\),表示插入一个 \(i\)。
- \(\forall w\in(mid,mid+V],j<f(i,w), f(i,w-a_j)\leftarrow j\),表示删除一个 \(j\)。
考虑第三种转移的复杂度:
发现当 \(j<f(i-1,w)\) 时,\(j<f(i-1,w)\leq f(i-1,w-a_j)\),所以 \(f(i,w-a_j)\) 在第一个转移中使用 \(f(i-1,w-a_j)\) 转移过来更优。所以 \(j\in[f(i-1,w),f(i,w))\)。对于一个 \(w\),所有 \(i\) 能对应到的 \(j\) 之和为 \(O(n)\) 的,所以总共的转移次数是 \(O(nV)\)。前两个转移的总转移次数显然是 \(O(nV)\)。所以总共的复杂度是 \(O(nV)\)。
一个细节:第三个转移对于 \(w\) 的枚举应该从大到小,原因显然。
点击查看代码
const int N = 2e3 + 10;
int n, x, a[N];
int f[N*2], g[N*2];
void solve(){
read(n);
int sum = 0, all = 0;
for(int i = 1; i <= n; ++ i){
read(a[i]);
sum += a[i];
all += a[i];
x = max(x, a[i]);
}
sum /= 2;
int b = 0, w = 0;
while(w + a[b+1] <= sum){
++ b;
w += a[b];
}
f[w-sum+x] = b + 1;
for(int i = b + 1; i <= n; ++ i){
memcpy(g, f, sizeof(g));
for(int j = x; j; -- j){
f[j+a[i]] = max(f[j+a[i]], g[j]);
}
for(int j = x + x; j >= x + 1; -- j){
for(int k = f[j]-1; k >= max(1, g[j]); -- k){
f[j-a[k]] = max(f[j-a[k]], k);
}
}
}
int ans = 0;
for(int j = x; j; -- j){
if(f[j]){
ans = all - sum - j + x;
break;
}
}
println(ans);
}
3. 湖北省选模拟 2024 - 花神诞日 / sabzeruz [省选/NOI-]
显然有 \(a\leq b\leq c, a\operatorname{xor} c \geq \min\{a\operatorname{xor} b, b\operatorname{xor} c\}\)。所以我们只用排序后从左往右考虑每个数,记录一下每道菜最后一个数是什么即可。
考虑设 \(f_{i,j}\) 表示前 \(i\) 个数,第 \(i\) 个数被第一道菜选,第二道菜最后选的是第 \(j\) 个数的方案数;\(g_{i,j}\) 表示前 \(i\) 个数,第 \(i\) 个数被第二道菜选,第一道菜最后选的是第 \(j\) 个数的方案数。
有四种转移:
- \([a_{i-1}\operatorname{xor}a_{i}\geq k_1]f_{i-1,j}\to f_{i,j}\);
- \([a_{i-1}\operatorname{xor}a_{i}\geq k_2]g_{i-1,j}\to g_{i,j}\);
- \([a_{j}\operatorname{xor}a_{i}\geq k_2]f_{i-1,j}\to g_{i,i-1}\);
- \([a_{j}\operatorname{xor}a_{i}\geq k_1]g_{i-1,j}\to f_{i,i-1}\);
那么我们使用 trie 树维护 \((x,y)=(a_j,f_{i,j})\) 的二元组,第三种操作实际上相当于对于 \(x\operatorname{xor}a_i\geq k_2\) 的 \(x\) 求和 \(y\);第一种操作实际上相当于什么都不干或者全部置 \(0\),可以在 trie 每个节点上维护一个 lazytag。第二、第四个操作同理。
最后,因为题目要求两个集合均非空。那么要减去所有数都归到一个集合的方案。
点击查看代码
const int N = 2e5 + 10;
const ll P = 1e9 + 7;
ll a[N], k1, k2;
int n, cnt = 2;
struct node{
int ch[2], tag;
ll sum;
} t[N*130];
void psd(int p){
if(t[p].tag){
if(t[p].ch[0]){
t[t[p].ch[0]].sum = 0;
t[t[p].ch[0]].tag = 1;
}
if(t[p].ch[1]){
t[t[p].ch[1]].sum = 0;
t[t[p].ch[1]].tag = 1;
}
t[p].tag = 0;
}
}
void ins(int p, ll v, ll x){
for(ll i = 62; i >= 0; -- i){
psd(p);
t[p].sum = (t[p].sum + x) % P;
if(!t[p].ch[(v>>i)&1]){
t[p].ch[(v>>i)&1] = ++ cnt;
}
p = t[p].ch[(v>>i)&1];
}
t[p].sum = (t[p].sum + x) % P;
}
ll qry(int p, ll v, ll w){
ll ans = 0;
for(ll i = 62; i >= 0; -- i){
psd(p);
if((w >> i) & 1){
if(t[p].ch[!((v>>i)&1)]){
p = t[p].ch[!((v>>i)&1)];
} else {
return ans;
}
} else {
ans += t[t[p].ch[!((v>>i)&1)]].sum;
if(t[p].ch[(v>>i)&1]){
p = t[p].ch[(v>>i)&1];
} else {
return ans;
}
}
}
ans = (ans + t[p].sum) % P;
return ans;
}
void fil(int p){
t[p].sum = 0;
t[p].tag = 1;
}
void solve(){
read(n, k1, k2);
for(int i = 1; i <= n; ++ i){
read(a[i]);
}
sort(a + 1, a + n + 1);
ins(1, (1ll<<61), 1);
ins(2, (1ll<<61), 1);
for(int i = 2; i <= n; ++ i){
ll sg = qry(1, a[i], k2);
ll sf = qry(2, a[i], k1);
if((a[i-1] ^ a[i]) < k1){
fil(1);
}
if((a[i-1] ^ a[i]) < k2){
fil(2);
}
ins(1, a[i-1], sf);
ins(2, a[i-1], sg);
}
int fa = 1, fb = 1;
for(int i = 2; i <= n; ++ i){
if((a[i-1] ^ a[i]) < k1){
fa = 0;
}
if((a[i-1] ^ a[i]) < k2){
fb = 0;
}
}
println((t[1].sum + t[2].sum - fa - fb + P) % P);
}
4. APIO2014 - 回文串 [省选/NOI-]
建出回文树,对每次增量添加一个字符后到达的点 \(cur\),令 \(sum_{cur}\) 加 \(1\)。最后每个点代表的串的出现次数即为失配子树内 \(sum\) 和,拓扑序逆序(直接编号逆序即可)往根贡献即可。
点击查看代码
const int N = 3e5 + 10;
int n, tot = 1, len[N], sum[N], ch[N][26], fl[N], cur;
char s[N];
int gfl(int x, int i){
while(i - len[x] - 1 < 0 || s[i-len[x]-1] != s[i]){
x = fl[x];
}
return x;
}
void solve(){
scanf("%s", s + 1);
n = strlen(s + 1);
fl[0] = 1;
len[1] = -1;
for(int i = 1; i <= n; ++ i){
int pos = gfl(cur, i);
if(!ch[pos][s[i]-'a']){
fl[++tot] = ch[gfl(fl[pos], i)][s[i]-'a'];
ch[pos][s[i]-'a'] = tot;
len[tot] = len[pos] + 2;
}
cur = ch[pos][s[i]-'a'];
++ sum[cur];
}
for(int i = tot; i >= 0; -- i){
sum[fl[i]] += sum[i];
}
ll ans = 0;
for(int i = 0; i <= tot; ++ i){
ans = max(ans, (ll)len[i] * sum[i]);
}
printf("%lld\n", ans);
return;
}
5. APIO2023 - 赛博乐园 / cyberland [提高+/省选-]
考虑一层一层地跑。预处理出 \(1\) 不经过 \(H\) 能到达的点集,每次从这些点开始跑一遍 dij。然后称一次操作为:
- 对于任意一条边 \((u,v)\),若 \(arr_u=2\),则使用 \(dis_u/2+w(u,v)\) 更新 \(dis_v\)。
- 从点集中点开始跑一遍 dij。
注意 \(inf\) 的取值。
点击查看代码
vector<pair<int, int>> g[N];
int a[N], ok[N], n, m, k, ed, vis[N];
double dis[N], nd[N];
void dfs(int x) {
ok[x] = 1;
for (auto i : g[x]) {
if (!ok[i.first] && i.first != ed) {
dfs(i.first);
}
}
}
void dij() {
priority_queue<pair<double, int>> q;
for (int i = 1; i <= n; ++ i) {
vis[i] = 0;
if (ok[i]) {
q.push(make_pair(-dis[i], i));
}
}
while (!q.empty()) {
int x = q.top().second;
q.pop();
if (vis[x]) {
continue;
}
vis[x] = 1;
for (auto i : g[x]) {
int y = i.first, z = i.second;
if (dis[y] > dis[x] + z) {
dis[y] = dis[x] + z;
q.push(make_pair(-dis[y], y));
}
}
}
}
void cpy() {
for (int i = 1; i <= n; ++ i) {
nd[i] = dis[i];
}
for (int i = 1; i <= n; ++ i) {
if (a[i] != 2 || dis[i] > 1e23) {
continue;
}
for (auto j : g[i]) {
int y = j.first, z = j.second;
dis[y] = min(dis[y], nd[i] / 2.0 + z);
}
}
}
double solve(
int nn,
int mm,
int kk,
int hh,
vector<int> x,
vector<int> y,
vector<int> c,
vector<int> arr) {
n = nn;
m = mm;
k = kk;
ed = hh + 1;
for (int i = 1; i <= n; ++ i) {
vector<pair<int, int>> ().swap(g[i]);
a[i] = arr[i - 1];
ok[i] = vis[i] = 0;
dis[i] = nd[i] = 1e24;
}
for (int i = 1; i <= m; ++ i) {
int X = x[i - 1] + 1, Y = y[i - 1] + 1, Z = c[i - 1];
if (X != ed)
g[X].emplace_back(Y, Z);
if (Y != ed)
g[Y].emplace_back(X, Z);
}
dfs(1);
for (int i = 1; i <= n; ++ i) {
if (ok[i] && (i == 1 || a[i] == 0)) {
dis[i] = 0;
}
}
k = min(k, 70);
dij();
while (k--) {
cpy();
dij();
}
double ans = dis[ed] > 1e23 ? (double) -1 : dis[ed];
for (int i = 1; i <= n; ++ i) {
vector<pair<int, int>> ().swap(g[i]);
a[i] = arr[i - 1];
ok[i] = vis[i] = 0;
dis[i] = nd[i] = 1e18;
}
return ans;
}
6. 省选联考 2024 - 魔法手杖 [NOI/NOI+/CTSC]
真不难吧这个题?为什么不会?
特判一下 \(\sum b_i\leq m\) 的情况,此时易得答案为 \((\min a_i)+2^k-1\)。否则至少有一个选择异或,此时答案一定 \(<2^k\)。
把所有 \((a,b)\) 二元组插入一棵 trie 中。设最优答案为 \(now\) 以及对应的 \(x\),考虑按位确定 \(now,x\)。对于遍历到的二叉树上节点 \(p\),在更浅的分叉中钦定选择加法的最小值 \(sum\),分四种情况:
- \(now,x\) 这一位均为 \(1\)。则需要满足 \(p.ch_1\) 中的所有点都选择加法,check 魔力值是否足够,更新 \(sum\),递归到 \(p.ch_0\) 中。
- \(now\) 这一位为 \(1\),\(x\) 这一位为 \(0\)。与上一种类似。
- \(now\) 这一位为 \(0\),\(x\) 这一位为 \(1\)。此时 \(p.ch_0\) 中的点已经完全优于答案,不用管,递归到另一侧。
- \(now,x\) 这一位均为 \(0\)。与上一种类似。
所以我们在递归求解的过程中需要记录 \(p,dep,lst,x,sum,now\),分别表示节点编号、深度、还可用的魔力值大小、前面若干位最优的 \(x\)、已经钦定选择加法中最小的 \(a\)、前面若干位最优的答案。记录这些后就可以一遍深搜解决了。每个点最多遍历两次,所以复杂度 \(O(Tnk)\)。
点击查看代码
//qoj8322
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }
void read(__int128 &x){
// read a __int128 variable
char c; bool f = 0;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') { f = 1; c = getchar(); }
x = c - '0';
while ((c = getchar()) >= '0' && c <= '9') x = x * 10 + c - '0';
if (f) x = -x;
}
void write(__int128 x){
// print a __int128 variable
if (x < 0) { putchar('-'); x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e5 + 10, K = 125;
int T, c, n, m, k, cnt;
__int128 a[N], ans;
ll b[N];
struct node{
int ch[2];
__int128 amin;
ll bsum;
} t[N*K];
__int128 zy(int x){
return (__int128) 1 << x;
}
__int128 fl(int x){
return zy(x) - 1;
}
void solve(int p, int dep, int lst, __int128 x, __int128 sum, __int128 now){
if(dep == -1){
ans = max(ans, now);
} else if(!p){
ans = max(ans, sum + (x | fl(dep+1)));
} else {
int lc = t[p].ch[0], rc = t[p].ch[1];
bool flg = 0;
if(t[lc].bsum <= lst && (x|fl(dep)) + min(sum,t[lc].amin) >= (now|zy(dep))){
solve(rc, dep-1, lst-t[lc].bsum, x, min(sum,t[lc].amin), now|zy(dep));
flg = 1;
}
if(t[rc].bsum <= lst && (x|fl(dep+1)) + min(sum,t[rc].amin) >= (now|zy(dep))){
solve(lc, dep-1, lst-t[rc].bsum, x|zy(dep), min(sum,t[rc].amin), now|zy(dep));
flg = 1;
}
if(!flg){
solve(lc, dep-1, lst, x, sum, now);
solve(rc, dep-1, lst, x|zy(dep), sum, now);
}
}
}
void solve(){
for(int i = 0; i < N*K; ++ i){
t[i].amin = zy(121);
}
scanf("%d%d", &c, &T);
while(T--){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++ i){
read(a[i]);
}
for(int i = 1; i <= n; ++ i){
scanf("%lld", &b[i]);
}
cnt = 1;
for(int i = 1; i <= n; ++ i){
int p = 1;
t[p].bsum += b[i];
t[p].amin = min(t[p].amin, a[i]);
for(int j = k - 1; j >= 0; -- j){
int v = a[i] >> j & 1;
if(!t[p].ch[v]){
t[p].ch[v] = ++ cnt;
}
p = t[p].ch[v];
t[p].amin = min(t[p].amin, a[i]);
t[p].bsum += b[i];
}
}
if(t[1].bsum <= m){
ans = t[1].amin + zy(k) - 1;
} else {
solve(1, k-1, m, 0, zy(k), 0);
}
write(ans);
putchar('\n');
for(int i = 0; i <= cnt; ++ i){
t[i].ch[0] = t[i].ch[1] = t[i].bsum = 0;
t[i].amin = zy(121);
}
ans = 0;
}
}
7. 湖北省选模拟 2024 - 沉玉谷 / jade [省选/NOI-]
设 \(f_{i,j,k}\) 表示 \([i,j]\) 删 \(k\) 次删空方案数,\(g_{i,j,k,l}\) 表示 \([i,j]\) 删 \(k\) 次仅剩颜色 \(l\) 的方案数。
转移:
\(g_{l,r,x+y,a_k}=\sum f_{l,k-1,x}(f_{k+1,r,y}+g_{k+1,r,y,a_k})\dbinom{x+y}x\),即枚举最左边一个未删除的位置 \(k\),两侧分别的操作数 \(x,y\)。
\(f_{l,r,k}=\sum g_{l,r,k-1,c}\),即枚举未删除的颜色 \(c\)。
最后答案为 \(\sum f_{1,n,i}\)。
初始化应设 \(f_{i+1,i,0}=1 (i\in[0,n])\)。
点击查看代码
//P10202
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }
const int N = 55;
const ll P = 1e9 + 7;
int n, a[N];
ll C[N][N], f[N][N][N], g[N][N][N][N];
void upd(ll &x, ll y){
x = (x + y) % P;
}
void solve(){
scanf("%d", &n);
C[0][0] = 1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
C[i][0] = C[i][i] = 1;
for(int j = 1; j < i; ++ j){
C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P;
}
f[i][i-1][0] = 1;
}
f[n+1][n][0] = 1;
for(int len = 1; len <= n; ++ len){
for(int l = 1; l + len - 1 <= n; ++ l){
int r = l + len - 1;
for(int k = l; k <= r; ++ k){
for(int x = 0; x <= k - l; ++ x){
for(int y = 0; y <= r - k; ++ y){
upd(g[l][r][x+y][a[k]],
f[l][k-1][x] *
(f[k+1][r][y] + g[k+1][r][y][a[k]]) % P *
C[x+y][x] % P);
}
}
}
for(int k = 1; k <= len; ++ k){
for(int c = 1; c <= n; ++ c){
upd(f[l][r][k], g[l][r][k-1][c]);
}
}
}
}
ll ans = 0;
for(int i = 1; i <= n; ++ i){
upd(ans, f[1][n][i]);
}
printf("%lld\n", ans);
}
8. ABC342F - Black Jack [*2141]
考虑设 \(g_i\) 表示对方以 \(i\) 结尾的概率,\(f_i\) 表示自己目前投出总和 \(i\),按照最优方案最大获胜概率。
发现 \(g\) 很好求:\(\dfrac{g_i}d\to g_{i+j}[i<l][1\leq j\leq d]\)。稍微变形得 \(g_i=\sum g_j[j<l][j\geq i-d]\),可以前缀和优化。
设 \(pr_i=\sum g_j[l\leq j\leq i],ls=\sum g_j[j>n]\),那么对于 \(i\in[l,n]\),自己在 \(i\) 处停止投的获胜概率是 \(pr_{i-1}+ls\)。
那么我们就有 \(f\) 的递推式:\(f_i=\max\{\dfrac{\sum f_j[i+1\leq j\leq i+d]}d,pr_{i-1}+ls\}\),表示继续投还是停止投,也可以前缀和优化。
点击查看代码
//AT_abc342_f
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }
const int N = 4e5 + 10;
int n, l, d;
double f[N], g[N], sum[N], ls, pr[N];
void solve(){
scanf("%d%d%d", &n, &l, &d);
int m = n + l;
g[0] = sum[0] = 1;
for(int i = 1; i <= m; ++ i){
#define clc(x, y) (sum[y] - (x-1>=0 ? sum[x-1] : 0))
g[i] = clc(i-d, i-1) * 1.0 / d;
if(i < l){
sum[i] = sum[i-1] + g[i];
g[i] = 0;
} else {
sum[i] = sum[i-1];
pr[i] = pr[i-1] + g[i];
}
}
for(int i = 1; i <= m; ++ i){
if(i > n){
ls += g[i];
}
sum[i] = 0;
}
sum[0] = 0;
for(int i = n; i >= 0; -- i){
f[i] = max((sum[i+1] - sum[i+d+1]) / d, pr[i-1] + ls);
sum[i] = sum[i+1] + f[i];
}
printf("%.20lf\n", f[0]);
}
9. ABC342G - Retroactive Range Chmax [*2107]
做法解析:树套树。
对于线段树每个节点使用 std::multiset
维护可重集。
- 对于操作 1,将 \([l,r]\) 对应的若干个可重集中插入元素 \(x\)。
- 对于操作 2,将 \([q_i.l,q_i.r]\) 对应的若干个可重集中删除元素 \(q_i.x\)。
- 对于操作 3,输出 \(i\) 的祖先可重集中最大元素的最大值和 \(a_i\) 中的较大值。
点击查看代码
//AT_abc342_g
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }
const int N = 2e5 + 10;
int n, a[N], q, X[N], Y[N], Z[N];
multiset<int> t[N*4];
void add(int p, int l, int r, int ql, int qr, int x){
if(qr < l || r < ql){
return;
} else if(ql <= l && r <= qr){
t[p].insert(-x);
} else {
int mid = l + r >> 1;
add(p<<1, l, mid, ql, qr, x);
add(p<<1|1, mid+1, r, ql, qr, x);
}
}
void del(int p, int l, int r, int ql, int qr, int x){
if(qr < l || r < ql){
return;
} else if(ql <= l && r <= qr){
auto it = t[p].lower_bound(-x);
t[p].erase(it);
} else {
int mid = l + r >> 1;
del(p<<1, l, mid, ql, qr, x);
del(p<<1|1, mid+1, r, ql, qr, x);
}
}
int ans;
void qry(int p, int l, int r, int x){
ans = max(ans, t[p].empty() ? 0 : -*t[p].begin());
if(l != r){
int mid = l + r >> 1;
if(x <= mid){
qry(p<<1, l, mid, x);
} else {
qry(p<<1|1, mid+1, r, x);
}
}
}
void solve(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
scanf("%d", &q);
for(int i = 1; i <= q; ++ i){
int op, l, r, x;
scanf("%d%d", &op, &l);
if(op == 1){
scanf("%d%d", &r, &x);
tie(X[i], Y[i], Z[i]) = tie(l, r, x);
add(1, 1, n, l, r, x);
} else if(op == 2){
del(1, 1, n, X[l], Y[l], Z[l]);
} else {
ans = a[l];
qry(1, 1, n, l);
printf("%d\n", ans);
}
}
}
10. CCO2023 - Real Mountains [省选/NOI-]
设 \(b_i=\min(\max_{1\leq j\leq i}\{a_j\},\max_{i\leq j\leq n}\{a_j\})\),显然有 \(b\) 是一组最小的可达的满足要求的方案。可以证明 \(b\) 是唯一的一组方案。所以我们的目标就变为求得最小代价使得 \(h\) 变为 \(b\)。
容易猜测一个结论:每次选择 \(h_i\) 最小且需要增加的位置,然后进行一次操作对其 \(+1\)。显然这个结论是正确的,证明可以考虑若存在 \(h_j>h_i\) 且 \(j\) 的操作在 \(i\) 前,两个交换一定不劣。那么如果有若干个 \(h_i\) 并列最小值怎么办?
假设这个并列最小值为 \(x\),有 \(k\) 个,我们要将这些全部变为 \(x+1\)。设最左侧的 \(x\) 为 \(h_l\),最右侧的 \(x\) 为 \(h_r\)。
最优操作方案是:对于左端点,我们找到二元组 \((o,p)\),满足 \(h_o>h_l<h_p,o<l<p\),且不存在 \(h_l<h_{o'}<h_o,o'<l\),也不存在 \(h_l<h_{p'}<h_p,p'>l\)。对于右端点,同理找到一个二元组 \((q,r)\)。
那么可以先进行一次 \((o,l,p)\),一次 \((l,r,q)\),\((k-2)\) 次 \((l,i,r)\) 完成目标。总代价为 \(h_o+h_r+h_p+2k-3+3(k-1)x\)。若 \(h_p>h_q\),还可以先操作右端点再操作左端点。故最优的总代价为 \(h_o+\min(h_p,h_q)+h_r+2k-3+3(k-1)x\)。
所以我们得到了一个暴力做法:遍历整个数组,提取出若干个 还需增加的并列最小值,找到两个二元组进行更新。复杂度爆炸。
考虑离散化。若出现在 \(h\) 中且大于最小值 \(x\) 的数为 \(y\),则一定会使用相同的两个二元组更新相同数量、位置的 \(x\) 一路到 \(y\)。所以我们将三元组 \((h_i,i,-1),(b_i,i,1)\) 进行排序然后扫描线,可以将复杂度优化到 \(O(n^2)\)。
现在问题转化为了一个序列 \(h\),两种操作:
- 查询前缀/后缀中大于某值的最小值;
- 一个抽象的修改操作。
发现很难处理修改。然而仔细分析可以发现修改是不需要的。因为根据上述算法,不可能存在两个位置 \((i,j)\),最开始 \(h_i<h_j\),在 中途的某个过程时既有 \(h_j<b_j\) 又有 \(h_i>h_j\)。所以只用维护查询操作,使用主席树即可。
点击查看代码
//qoj6626
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }
const int N = 1e6 + 10;
const ll P = 1e6 + 3;
int n;
ll a[N], b[N];
tuple<ll, int, int> q[N*2];
ll ans;
ll Sum(ll x, ll y){
return (y * (y-1) / 2 % P - x * (x-1) / 2 % P + P) % P;
}
int t[N*100], ls[N*100], rs[N*100];
int rtl[N], rtr[N], cnt;
int add(int p, int l, int r, int x){
++ cnt;
tie(t[cnt], ls[cnt], rs[cnt]) = tie(t[p], ls[p], rs[p]);
p = cnt;
if(l == r){
++ t[p];
} else {
int mid = l + r >> 1;
if(x <= mid){
ls[p] = add(ls[p], l, mid, x);
} else {
rs[p] = add(rs[p], mid+1, r, x);
}
t[p] = t[ls[p]] + t[rs[p]];
}
return p;
}
int qry(int p, int l, int r, int ge){
if(t[p] == 0 || r <= ge){
return -1;
} else if(l == r){
return l;
} else {
int mid = l + r >> 1, tmp;
if((tmp = qry(ls[p], l, mid, ge)) > ge){
return tmp;
} else {
return qry(rs[p], mid+1, r, ge);
}
}
}
int qrl(int x, int mn){
return qry(rtl[x], 1, 1e9, mn);
}
int qrr(int x, int mn){
return qry(rtr[x], 1, 1e9, mn);
}
void solve(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%lld", &a[i]);
q[i] = { a[i], i, -1 };
}
ll mx = 0;
rtl[0] = ++ cnt;
for(int i = 1; i <= n; ++ i){
mx = max(mx, a[i]);
rtl[i] = add(rtl[i-1], 1, 1e9, a[i]);
b[i] = mx;
}
mx = 0;
rtr[n+1] = ++ cnt;
for(int i = n; i >= 1; -- i){
mx = max(mx, a[i]);
b[i] = min(b[i], mx);
rtr[i] = add(rtr[i+1], 1, 1e9, a[i]);
q[i+n] = { b[i], i, 1 };
}
sort(q + 1, q + n + n + 1);
set<int> st;
for(int i = 1; i < n + n; ++ i){
if(get<2>(q[i]) == -1){
st.insert(get<1>(q[i]));
} else {
st.erase(get<1>(q[i]));
}
if(get<0>(q[i]) != get<0>(q[i+1]) && st.size()){
ll fr = get<0>(q[i]), to = get<0>(q[i+1]);
ll l = *st.begin(), r = *st.rbegin(), k = st.size();
ll oo = qrl(l, fr), pp = qrr(l, fr), qq = qrl(r, fr), rr = qrr(r, fr);
ll p = qrl(l, fr), q = qrr(r, fr);
if(k == 1){
ans += Sum(fr, to);
ans += (oo + pp) % P * (to - fr) % P;
} else {
ans += (oo + rr + min(pp, qq) + k + k - 3) % P * (to - fr) % P;
ans += 3 * (k - 1) % P * Sum(fr, to) % P;
}
ans %= P;
}
}
printf("%lld\n", ans);
}
11. NOI2010 - 海拔 [省选/NOI-]
首先易得每个点海拔为 \(0\) 或 \(1\),且两部分分别连通。
然后就相当于平面图最小割,等于对偶图最短路即可。
注意建图时两个方向都要建图,每条边把它顺时针翻转一个九十度建边。
点击查看代码
const int N = 510;
int n, a[4][N][N];
vector<pair<int, int> > g[N*N];
int dis[N*N], vis[N*N];
#define cg(x, y) ((x-1)*n+y+1)
#define add(u, v, w) g[u].emplace_back(v, w)
void solve(){
scanf("%d", &n);
for(int i = 0; i < 4; i += 2){
for(int j = 0; j <= n; ++ j){
for(int k = 1; k <= n; ++ k){
scanf("%d", &a[i][j][k]);
}
}
for(int j = 1; j <= n; ++ j){
for(int k = 0; k <= n; ++ k){
scanf("%d", &a[i+1][j][k]);
}
}
}
int st = 0, ed = 1;
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= n; ++ j){
if(j == 1) add(st, cg(i, j), a[1][i][0]);
if(i == n) add(st, cg(i, j), a[0][n][j]);
if(j == n) add(cg(i, j), ed, a[1][i][n]);
if(i == 1) add(cg(i, j), ed, a[0][0][j]);
if(i > 1) add(cg(i, j), cg(i-1, j), a[0][i-1][j]);
if(i < n) add(cg(i, j), cg(i+1, j), a[2][i][j]);
if(j > 1) add(cg(i, j), cg(i, j-1), a[3][i][j-1]);
if(j < n) add(cg(i, j), cg(i, j+1), a[1][i][j]);
}
}
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
priority_queue<pair<int, int> > q;
q.push(make_pair(0, 0));
while(!q.empty()){
int x = q.top().second;
q.pop();
if(vis[x]){
continue;
}
vis[x] = 1;
for(auto i : g[x]){
if(dis[i.first] > dis[x] + i.second){
dis[i.first] = dis[x] + i.second;
q.push(make_pair(-dis[i.first], i.first));
}
}
}
printf("%d\n", dis[1]);
}
12. NOI2010 - 超级钢琴 [省选/NOI-]
无脑两个 log 做法。
首先做个前缀和,定义一个状态 \((i,k)\) 权值为 \(\operatorname{kthmax}_{j\in[i+L,\min(i+R,n)]}\{s_j\}-s_i\),如果不存在这样的 kth 则为负无穷。
那么维护一个大根堆,首先将所有状态 \((i,0)\) 插入。每次取队首 \((i,j)\),添加答案,插入 \((i,j+1)\) 即可。
查询 kthmax 可用主席树。
点击查看代码
const int N = 5e5 + 10;
int n, k, L, R, a[N], s[N], b[N], m;
int rt[N], t[N*30], ls[N*30], rs[N*30], cnt = 1;
int add(int p, int l, int r, int x){
++ cnt;
tie(t[cnt], ls[cnt], rs[cnt]) = tie(t[p], ls[p], rs[p]);
p = cnt;
if(l == r){
++ t[p];
} else {
int mid = l + r >> 1;
if(x <= mid){
ls[p] = add(ls[p], l, mid, x);
} else {
rs[p] = add(rs[p], mid+1, r, x);
}
t[p] = t[ls[p]] + t[rs[p]];
}
return p;
}
int qry(int p, int q, int l, int r, int x){
if(l == r){
return l;
} else {
int mid = l + r >> 1;
int k = t[rs[p]] - t[rs[q]];
if(k >= x){
return qry(rs[p], rs[q], mid+1, r, x);
} else {
return qry(ls[p], ls[q], l, mid, x-k);
}
}
}
int kmn(int l, int r, int k){
return qry(rt[r+1], rt[l], 1, m, k);
}
void solve(){
scanf("%d%d%d%d", &n, &k, &L, &R);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
s[i] = s[i-1] + a[i];
b[i] = s[i];
}
b[n+1] = 0;
sort(b + 1, b + n + 2);
m = unique(b + 1, b + n + 2) - b - 1;
priority_queue<tuple<int, int, int> > q;
for(int i = 0; i <= n; ++ i){
s[i] = lower_bound(b + 1, b + m + 1, s[i]) - b;
rt[i+1] = add(rt[i], 1, m, s[i]);
}
for(int i = 0; i <= n - L; ++ i){
q.push({ b[kmn(i+L, min(n, i+R), 1)] - b[s[i]], i, 1 });
}
ll ans = 0;
while(k--){
auto now = q.top();
q.pop();
ans += get<0>(now);
int x = get<1>(now), y = get<2>(now) + 1;
if(min(n, x+R) - (x+L) + 1 >= y){
q.push({ b[kmn(x+L, min(n, x+R), y)] - b[s[x]], x, y });
}
}
printf("%lld\n", ans);
}
13. CF838D - Airplane Arrangements [*2700]
首先考虑只从一个门进的情况。可以等价于:\(n+1\) 个位置排成一个环,\(m\) 个人,每个人选一个位置 \(i\),若 \(i\) 已经被占用则继续选 \(i+1\),若 \(n+1\) 已经被占用则继续选 \(1\),最后找到一个未被占用的位置则占用。最后合法当且仅当 \(n+1\) 这个位置是空的。所有方案总数是 \((n+1)^m\) 个,而其中会有若干个合法的。可以发现每个最终状态出现概率是相同的,所以一个状态合法的概率即为 \(\dfrac{\dbinom {n+1}m}{\dbinom nm}=\dfrac {n+1-m}{n+1}\)。答案是 \((n+1)^m\dfrac {n+1-m}{n+1}\)。
而两个门其实不影响概率。所以答案是 \((2n+2)^m\dfrac {n+1-m}{n+1}\)。
点击查看代码
const int N = 1e6 + 10;
typedef long long ll;
const ll P = 1e9 + 7;
int n, m;
ll qp(ll x, ll y){
ll ans = 1;
while(y){
if(y & 1){
ans = ans * x % P;
}
x = x * x % P;
y >>= 1;
}
return ans;
}
int main(){
cin >> n >> m;
cout << qp(n+n+2, m) * (n+1-m) % P * qp(n+1, P-2) % P;
return 0;
}
14. USACO19DEC - Tree Depth P [省选/NOI-]
首先,一个点的深度等于它的祖先个数 \(+1\)。所以我们只用枚举 \((u,v)\),计算 \(v\) 为 \(u\) 祖先的方案数即可。而 \(v\) 为 \(u\) 祖先等价于 \(u,v\) 之间的数都 \(>a_v\)。
考虑 dp 顺序:先 \(u,v\) 之间的点,再 \(u\),再 \(v\),最后其余所有点。发现这个 dp 实际上等价于 dp 所有点(可以看作一个分组背包)后再撤销掉一个 \([0,u-v]\) 的组。
为什么等价:首先 \([u,v],(v,n]\) 肯定等价;\([1,u)\) 可以看作翻转过后的若干项,也等价。
使用前缀和优化+撤销 dp 可以做到 \(O(n^3)\) dp \(O(n^2)\) 撤销。
之后,若 \(u<v\),则 \(v\) 会和 \([u,v)\) 构成逆序对,此时答案加上 \(f_{n-1,k-(v-u)}\);否则 \(v\) 不会和任何构成逆序对,答案加上 \(f_{n-1,k}\)。(\(n-1\) 是因为撤销掉了一个组别)。
点击查看代码
const int N = 310;
int n, k;
ll P, f[2][N*N], s[N*N], ans[N];
#define ck(x) ((x) >= P ? (x) - P : (x))
int main(){
scanf("%d%d%lld", &n, &k, &P);
f[0][0] = 1;
for(int i = 1; i <= n; ++ i){
s[0] = f[i&1][0] = 1;
for(int j = 1; j <= n * n; ++ j){
f[i&1][j] = s[j] = ck(s[j-1] + f[(i-1)&1][j]);
if(j >= i){
f[i&1][j] = ck(f[i&1][j] - s[j-i] + P);
}
}
}
for(int i = 1; i <= n; ++ i){
ans[i] = f[n&1][k];
}
for(int t = 1; t < n; ++ t){
memset(s, 0, sizeof(s));
ll nw = P - 1;
s[0] = 1;
for(int i = 1; i <= n * n - t; ++ i){
if(i > t){
nw = ck(nw + s[i-t-1]);
}
s[i] = ck(f[n&1][i] + nw);
nw = ck(nw - s[i] + P);
}
for(int u = 1; u <= n - t; ++ u){
//v->u
if(t <= k){
ans[u] = ck(ans[u] + s[k-t]);
}
//u->v
ans[u+t] = ck(ans[u+t] + s[k]);
}
}
for(int i = 1; i <= n; ++ i){
printf("%lld ", ans[i]);
}
puts("");
}
15. CF1528F - AmShZ Farm [*3300]
题意中 \(a\) 的限制可以转化为:有 \(n\) 个人,每个人首先走到 \(a_i\),然后往后走直到第一个没有被占用的位置并占用。由上文 13. 得这种方案数为 \((n+1)^{n-1}\)。我们在此时将 \(a\) 的值域扩展到 \([1,n+1]\)。则对于任意的一个 \(a\),其对应的 \(b\) 个数为 \(\sum_{i=1}^{n+1}cnt_i^k\)。一个合法的 \(a\) 能够映射到 \(n\) 个不合法的 \(a\),且这 \(n+1\) 个 \(a\) 仅仅是平移可以得到。所以这 \(n+1\) 个 \(a\) 对应的 \(b\) 个数都是相同的。所以我们仅用求出对于所有 \({(n+1)}^{n}\) 的 \(b\) 个数总和除掉 \(n+1\) 即可。又因为对于任意的数 \(i\),统计 \(a_{b_k}=i\) 的贡献又都是相同的,所以 \(n+1\) 个 \(i\),两个 \(n+1\) 抵消,答案及为:
\(ans=\sum_{i=0}^n\dbinom nii^kn^{n-i}\),即选择 \(i\) 个位置为钦定值,其它位置为剩余 \(n\) 种值。
推式子,参考 可得:
\(ans=\sum_{i=0}^n n^{\underline i}(n+1)^{n-i}{{k}\brace{i}}\)。
因为当 \(i>k\) 时 \({{k}\brace{i}}=0\),则:
\(ans=\sum_{i=0}^{\min(n,k)} n^{\underline i}(n+1)^{n-i}{{k}\brace{i}}\)。
前面两项可以线性预处理。最后一项需使用第二类斯特林数行。
点击查看代码
const int N = 4e5 + 10;
int n, k, m, tr[N];
typedef long long ll;
const ll P = 998244353, G = 3, iG = (P + 1) / G;
ll f[N], g[N], fac[N];
ll qp(ll x, ll y){
ll ans = 1;
while(y){
if(y & 1){
ans = ans * x % P;
}
x = x * x % P;
y >>= 1;
}
return ans;
}
void ntt(ll *f, int n, bool op){
for(int i = 0; i < n; ++ i){
if(i < tr[i]){
swap(f[i], f[tr[i]]);
}
}
for(int p = 2; p <= n; p <<= 1){
int len = p >> 1;
ll tg = qp(op ? G : iG, (P-1) / p);
for(int k = 0; k < n; k += p){
ll buf = 1;
for(int l = k; l < k + len; ++ l){
int tmp = buf * f[l+len] % P;
f[l+len] = (f[l] - tmp + P) % P;
f[l] = (f[l] + tmp) % P;
buf = buf * tg % P;
}
}
}
}
int main(){
cin >> n >> k;
int kk = k;
fac[0] = 1;
for(int i = 1; i <= k; ++ i){
fac[i] = fac[i-1] * i % P;
}
for(int i = 0; i <= k; ++ i){
f[i] = qp(i, k) * qp(fac[i], P-2) % P;
g[i] = qp(P-1, i) * qp(fac[i], P-2) % P;
}
for(m = k + k, k = 1; k <= m; k <<= 1);
for(int i = 0; i < k; ++ i){
tr[i] = (tr[i>>1] >> 1) | ((i & 1) ? k >> 1 : 0);
}
ntt(f, k, 1);
ntt(g, k, 1);
for(int i = 0; i < k; ++ i){
f[i] = f[i] * g[i] % P;
}
ntt(f, k, 0);
ll ans = 0, dm = 1, pw = qp(n+1, n), inv = qp(n+1, P-2), in = qp(k, P-2);
for(int i = 0; i <= min(n, kk); ++ i){
ans = (ans + dm * pw % P * f[i] % P * in % P) % P;
dm = dm * (n-i) % P;
pw = pw * inv % P;
}
cout << ans << '\n';
return 0;
}
16. BalkanOI2018 - Popa [省选/NOI-]
你怎么知道我不会笛卡尔树?
我们可以发现动态维护的右链上一定能找到一个目前点的因数,所以做法是对的。
点击查看代码
int query(int a, int b, int c, int d);
int solve(int N, int* Left, int* Right){
stack<int> st;
for(int i = 0; i < N; ++ i){
int le = -1;
while(!st.empty() && query(st.top(), i, i, i)){
le = st.top();
st.pop();
}
Left[i] = le;
Right[i] = -1;
if(!st.empty()){
Right[st.top()] = i;
}
st.push(i);
}
int le = -1;
while(!st.empty()){
le = st.top();
st.pop();
}
return le;
}
17. Ynoi ER 2024 - TEST_132 [省选/NOI-]
你可以根号分治的。
点击查看代码
const int N = 1e6 + 10;
typedef long long ll;
const ll P = 1e9 + 7, Q = 1e9 + 6;
int n, m, x[N], y[N], c[N];
vector<int> g[N], mx;
vector<pair<int, int> > f[N];
ll v[N], ans[N], pw[N];
ll qp(ll x, ll y){
ll ans = 1;
while(y){
if(y & 1){
ans = ans * x % P;
}
x = x * x % P;
y >>= 1;
}
return ans;
}
int main(){
scanf("%d%d", &n, &m);
int B = 1000000;
for(int i = 1; i <= n; ++ i){
scanf("%d%d%lld", &x[i], &y[i], &v[i]);
g[x[i]].push_back(i);
++ c[x[i]];
}
for(int i = 1; i <= n; ++ i){
pw[i] = 1;
if(c[i] > B){
mx.push_back(i);
}
}
for(int i = 1; i <= n; ++ i){
if(c[x[i]] > B){
f[y[i]].emplace_back(v[i], x[i]);
} else {
ans[y[i]] = (ans[y[i]] + v[i]) % P;
}
}
while(m--){
int op, p;
scanf("%d%d", &op, &p);
if(op == 1){
if(c[p] <= B){
for(int i : g[p]){
ans[y[i]] = (ans[y[i]] + P - v[i]) % P;
v[i] = v[i] * v[i] % P;
ans[y[i]] = (ans[y[i]] + v[i]) % P;
}
} else {
pw[p] = pw[p] * 2 % Q;
}
} else {
ll res = ans[p];
for(auto i : f[p]){
res = (res + qp(i.first, pw[i.second])) % P;
}
printf("%lld\n", res);
}
}
return 0;
}