2024.03 别急记录
1. IOI2018 - 狼人 / werewolf [省选/NOI-]
题意简述:多次询问求是否存在一条
首先求出两棵 kruskal 重构树:
- 第一棵树边权值设为
,由大到小插入每一条边,树上点权设为子树内点权 ,则可通过倍增跳树的方式求得每个点经过 的点能到达的点集合(一个子树内所有叶子); - 第二棵树边权值设为
,由小到大插入每一条边,树上点权设为子树内点权 。
因为一个子树内所有叶子是 dfn 序上一段连续区间,dfn 序又是一个排列,问题转化为了两个排列
设
考虑使用主席树维护
点击查看代码
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]
子集和中位数
设 bitset
做法,不多讲。
由于所有的子集
若子集
使得不存在 使 与 同时 或同时 ,称这样的 为平衡解。
设
我们发现可以使用以下两种操作使得
- 若
,选择一个 令 变为 。 - 若
,选择一个 令 变为 。
设
可以发现对于
发现
考虑 dp:
初始化
从小到大枚举
,表示不变。 ,表示插入一个 。 ,表示删除一个 。
考虑第三种转移的复杂度:
发现当
一个细节:第三个转移对于
点击查看代码
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-]
显然有
考虑设
有四种转移:
; ; ; ;
那么我们使用 trie 树维护
最后,因为题目要求两个集合均非空。那么要减去所有数都归到一个集合的方案。
点击查看代码
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-]
建出回文树,对每次增量添加一个字符后到达的点
点击查看代码
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 [提高+/省选-]
考虑一层一层地跑。预处理出
- 对于任意一条边
,若 ,则使用 更新 。 - 从点集中点开始跑一遍 dij。
注意
点击查看代码
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]
真不难吧这个题?为什么不会?
特判一下
把所有
这一位均为 。则需要满足 中的所有点都选择加法,check 魔力值是否足够,更新 ,递归到 中。 这一位为 , 这一位为 。与上一种类似。 这一位为 , 这一位为 。此时 中的点已经完全优于答案,不用管,递归到另一侧。 这一位均为 。与上一种类似。
所以我们在递归求解的过程中需要记录
点击查看代码
//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-]
设
转移:
最后答案为
初始化应设
点击查看代码
//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]
考虑设
发现
设
那么我们就有
点击查看代码
//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,将
对应的若干个可重集中插入元素 。 - 对于操作 2,将
对应的若干个可重集中删除元素 。 - 对于操作 3,输出
的祖先可重集中最大元素的最大值和 中的较大值。
点击查看代码
//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-]
设
容易猜测一个结论:每次选择
假设这个并列最小值为
最优操作方案是:对于左端点,我们找到二元组
那么可以先进行一次
所以我们得到了一个暴力做法:遍历整个数组,提取出若干个 还需增加的并列最小值,找到两个二元组进行更新。复杂度爆炸。
考虑离散化。若出现在
现在问题转化为了一个序列
- 查询前缀/后缀中大于某值的最小值;
- 一个抽象的修改操作。
发现很难处理修改。然而仔细分析可以发现修改是不需要的。因为根据上述算法,不可能存在两个位置
点击查看代码
//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-]
首先易得每个点海拔为
然后就相当于平面图最小割,等于对偶图最短路即可。
注意建图时两个方向都要建图,每条边把它顺时针翻转一个九十度建边。
点击查看代码
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 做法。
首先做个前缀和,定义一个状态
那么维护一个大根堆,首先将所有状态
查询 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]
首先考虑只从一个门进的情况。可以等价于:
而两个门其实不影响概率。所以答案是
点击查看代码
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-]
首先,一个点的深度等于它的祖先个数
考虑 dp 顺序:先
为什么等价:首先
使用前缀和优化+撤销 dp 可以做到
之后,若
点击查看代码
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]
题意中
推式子,参考 可得:
因为当
前面两项可以线性预处理。最后一项需使用第二类斯特林数行。
点击查看代码
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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步