Atcoder杂题笔记
大概会把博客当草稿纸用(
当然写出正解还是会把正解贴出来。
[ARC080E] Young Maids (待补代码)
给定正偶数
给定
首先,令
- 选择
中两个相邻元素 ,按原顺序设它们是 和 . 从 中移除 和 ,将它们按顺序接在 的前面。
试求可能的形成的
note:
可以发现最终排列相邻两个数在原串相对位置固定。
观察样例大概能看出第一位只有可能是奇数位的。
考虑黑白染色,很明显在最终排列中对于任意
于是每次贪心的拿出最小的黑白数对,然后递归求解这个数对左边、右边、内部的答案,其中内部颜色需反转,将三个数列进行归并即可。
但是这样时间复杂度是错误的。
正解是这个的优化,利用 vector 的 swap 时间复杂度是
[ARC076F] Exhausted?
有
高桥君和他的朋友一共有
高桥君他们每个人坐的椅子的坐标都很讲究,第
可这样计算下去,可能会让他们不能都坐在椅子上休息。青木君关心高桥君他们的健康,尽可能多地增加椅子,让高桥君他们都能够坐在椅子上休息。 椅子可以添加到任意的实数坐标上,请求出需要添加椅子数量的最小值。
note:
忙猜这图论题。
将每个人和能坐的坐标连边后二分图的最大匹配就是答案。但是时间复杂度会炸裂。
(待补)
[ARC148E] ≥ K
给定长度为
note & solution:
好玩的计数题。
将数分成两类,大于或等于
而在不同类型的数中,两个数
于是考虑按
仍然分类讨论插入
-
对于小于
的数。先在当前可用的位置插入当前数。假设这个数有 个,则产生的贡献是 。因为对 排序了,所以后面的任何数都不能插入到当前数的旁边,因此可用的的空位数量会减少, 。特别地,如果当前空位不够放置 个数,则无解。 -
对于大于
的数,这种情况可以使用插板法计算贡献。贡献为 。因为后面的数可以任意插入在当前数旁边,因此可用的空位数量会增加, 。
分类讨论并计算答案即可。需要注意排序时如果
code:
#include<iostream> #include<fstream> #include<algorithm> #include<vector> #define int long long using namespace std; const int modd = 998244353; int n, k; int frac[200005], inv[200005], a[200005], h[200005]; bool del[200005]; int ksm(int u, int v){ if(v == 0) return 1; int ans = ksm(u, v >> 1); ans = ans * ans % modd; if(v & 1) ans = ans * u % modd; return ans; } int abss(int u){ return u < 0 ? -u : u; } bool cmp(int u, int v){ int x1 = abss(2 * u - k), y1 = abss(2 * v - k); return (x1 != y1 ? x1 > y1 : u > v); } int getC(int u, int v){ return (((frac[u] * inv[v]) % modd) * inv[u - v]) % modd;} int s = 1, ans = 1; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> k; frac[0] = 1, inv[0] = 1; for(int i = 1; i <= n + 1; i ++) frac[i] = (frac[i - 1] * i) % modd; inv[n + 1] = ksm(frac[n + 1], modd - 2); for(int i = n; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % modd; for(int i = 1; i <= n; i ++) cin >> a[i]; sort(a + 1, a + n + 1, cmp); int cnt = 0, pos = 0; a[0] = -1, a[n + 1] = -2; for(int i = 1; i <= n + 1; i ++) if(a[i] == a[pos]) cnt ++, del[i] = true; else h[pos] = cnt, cnt = 1, pos = i; for(int i = 1; i <= n; i ++){ if(del[i]) continue; if(2 * a[i] - k >= 0) (ans *= getC(s + h[i] - 1, s - 1)) %= modd, s += h[i]; else{ if(s < h[i]){ cout << 0; return 0;} (ans *= getC(s, h[i])) %= modd, s -= h[i]; } } cout << ans; return 0; }
[ARC127E] Priority Queue
给定一个长度为
有一个集合
,选择一个数 ,并把这个数加入到集合中,这里 必须是之前没有选择过的数 ,将集合中最大的数删掉
问最后
note:
看起来还是从结果入手。
最终序列的长度是确定的,为
考虑什么样的数列不可能成为最终答案。首先想到的是如果
如果从结果考虑,那么第一种操作变成了删除一个集合中的数,第二种操作变成了加入一个没被删除过的最大值。目标状态为空。对于一个结果,其第二个操作的操作集合是已经固定的了。
从后往前枚举操作,假设目前
基于贪心的思想,我们贪心地删除当前最大的数。
定义
-
如果这一次操作是
且 ,那么现在有能够用来删除的数,而下一个用来删除的数在 。 -
如果这一次操作是
,则判定是否有一个给你用的最大值。假设现在枚举的是 则 个数可用。而可以将可用数 的直接 ban 掉了。同时这个最大值可以抵消掉一个 操作,令 。
我们发现
所以
口胡完毕,代码待补。
update on 20230823 : 这是错误的。晚点补正解思路。
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; const int modd = 998244353; int a, b; int c[10005], f[5005][5005]; int s, lim[10005], cnt, d = 1, k; signed main(){ cin >> a >> b; for(int i = 1; i <= a + b; i ++) cin >> c[i]; k = a - b; for(int i = a + b; i >= 1; i --){ if(c[i] == 1){ if(cnt) cnt --; else k --; } else{ lim[k] = max(lim[k], d); d ++, cnt ++; } } for(int i = 0; i <= b; i ++) f[a - b + 1][i] = 1; for(int i = a - b; i >= 1; i --){ for(int j = lim[i]; j <= b; j ++){ if(j) f[i][j] = f[i][j - 1]; (f[i][j] += f[i + 1][j]) %= modd; } } cout << f[1][b]; return 0; }
[ARC027D] ぴょんぴょんトレーニング
有
note
0823 15:50
没法直接从线性 DP 的角度出发,那就直接走区间 DP 然后优化。
询问数量不多(?
只记录需要询问的可以吗。
相邻的直接暴力线性 DP。设
update on 0823 16:16:
然后会很惊喜的发现这是错的,因为询问用到的点不一定是必经点。
那怎么办呢?
我发现直接区间 dp 是可行的,滚动数组滚
update on 0823 16:24:
不可行,因为直接区间 dp 是
那能考虑容斥吗?
似乎可行(?
反正
update on 0824 21:02
虽然时间复杂度好像很像莫队的样子但是没想出怎么实现 add 和 del。
从头思考一遍。
反正看静态询问和这个数据范围就很莫队。
如果莫队的话,solve 函数是已经写完的,也就是只要 add 和 del 能实现一个这题就能直接切。
update on 0824 21:26
问了机房大佬 SError_,好像被他秒了(
大致思路是对每个点维护个转移矩阵然后每次乘一下就行了。
solution:
题意:有
考虑朴素的 DP。设状态
初始值为指定的左端点的
那么有个
考虑优化这一 dp。我们发现
假设现在记录了
那么可以构造转移矩阵将
这里以
而根据 dp 的初始值,可构造初始矩阵如下。
那么转移即变成了求初始矩阵与一段区间的乘积。不带修求区间乘就有很多很多种方法了。但是用线段树需要注意空间复杂度。
我喜欢暴力所以我了分块。
时间复杂度是
code:
#include<iostream> #include<fstream> #include<algorithm> #include<cmath> #define int long long using namespace std; const int modd = 1000000007; struct matr{ int st[10][10]; int n, m; matr(int nl, int ml){ n = nl, m = ml; for(int i = 0; i < n; i ++) for(int j = 0; j < m; j ++) st[i][j] = 0; } matr(int nl, int ml, bool fl){ n = nl, m = ml; for(int i = 0; i < n; i ++) for(int j = 0; j < m; j ++) st[i][j] = (i == j); } matr(){ n = 10, m = 10; } int write(){ for(int i = 0; i < n; i ++){ for(int j = 0; j < m; j ++) cout << st[i][j] << " "; cout << "\n"; } return 0; } }I(10, 10, 1); matr A[300005], B[805]; matr mul(matr t1, matr t2){ matr m3(t1.n, t2.m); for(int i = 0; i < t1.n; i ++) for(int j = 0; j < t2.m; j ++) for(int k = 0; k < t2.m; k ++) (m3.st[i][j] += (1ll * (t1.st[i][k] % modd) * (t2.st[k][j] % modd)) % modd) %= modd; return m3; } matr D(1, 10); matr E(1, 10); int n, q; int T, bl; int L[805], R[805], belong[300005]; int query(int l, int r){ if(belong[l] == belong[r]){ for(int i = l; i <= r; i ++) E = mul(E, A[i]); return E.st[0][0]; } int u = belong[l], v = belong[r]; for(int i = l; i <= R[u]; i ++) E = mul(E, A[i]); for(int i = u + 1; i < v; i ++) E = mul(E, B[i]); for(int i = L[v]; i <= r; i ++) E = mul(E, A[i]); return E.st[0][0]; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; D.st[0][0] = 1; for(int i = 1, x; i <= n; i ++){ cin >> x; A[i].n = 10, A[i].m = 10; for(int j = 0; j < x; j ++) A[i].st[0][j] = 1; for(int j = 0; j < 9; j ++) A[i].st[j + 1][j] = 1; } T = 605, bl = n / T; for(int i = 1; i <= bl; i ++) B[i] = I, L[i] = R[i - 1] + 1, R[i] = L[i] + T - 1; if(R[bl] < n) bl ++, L[bl] = R[bl - 1] + 1, R[bl] = n; for(int i = 1; i <= bl; i ++) for(int j = L[i]; j <= R[i]; j ++){ belong[j] = i; B[i] = mul(B[i], A[j]); } cin >> q; for(int i = 1, l, r; i <= q; i ++){ E = D; cin >> l >> r; if(l == r){ cout << 1 << "\n"; continue; } r --; cout << query(l, r) << "\n"; } return 0; }
[ARC122E] Increasing LCMs
给定长度为
问是否能将
令
solution:
构造题。
很显然,任何序列的
有:
用唯一分解定理转化一下得:
维护
由于
code:
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; vector<int> ans; bool vis[105]; int n; int a[105]; int lcm(int x, int y){ return x / __gcd(x, y) * y; } int solve(){ int cnt = 0; for(int i = 1; i <= n; i ++){ if(vis[i]) continue; int nw = 1; for(int j = 1; j <= n; j ++){ if(j == i || vis[j]) continue; nw = lcm(nw, __gcd(a[i], a[j])); } if(nw < a[i]){ vis[i] = 1; cnt = 1; ans.push_back(a[i]); break; } } if(cnt) solve(); if(ans.size() == n) return 1; return 0; } signed main(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i]; if(solve()){ cout << "YES\n"; reverse(ans.begin(), ans.end()); for(int i = 0; i < ans.size(); i ++) cout << ans[i] << " "; } else cout << "NO\n"; return 0; }
[AGC018C] Coins
有
solution:
做反悔堆时看到的题。
可以先钦定全选金币,令
假设我们第
移项,得:
按
code:
#include<iostream> #include<fstream> #include<algorithm> #include<queue> #define int long long using namespace std; const int inf = 0xcfcfcfcfcfcfcfcf; int x, y, z, n, ans; struct node_t{ int a, b; }c[100005]; bool cmp(node_t u, node_t v){ return u.a - u.b > v.a - v.b; } int f[100005], g[100005]; priority_queue<pair<int, int> > q; signed main(){ cin >> x >> y >> z, n = x + y + z; for(int i = 1, u, v, w; i <= n; i ++){ cin >> u >> v >> w; ans += u, v -= u, w -= u; c[i].a = v, c[i].b = w; } sort(c + 1, c + n + 1, cmp); int nw = 0; for(int i = 1; i <= n; i ++){ if(nw < y){ q.push(make_pair(-c[i].a, i)); nw ++; f[i] = f[i - 1] + c[i].a; } else{ f[i] = f[i - 1] + c[i].a; q.push(make_pair(-c[i].a, i)); int tp = q.top().second, vl = q.top().first; q.pop(); f[i] += vl; } } nw = 0; while(!q.empty()) q.pop(); for(int i = n; i >= 1; i --){ if(nw < z){ q.push(make_pair(-c[i].b, i)); nw ++; g[i] = g[i + 1] + c[i].b; } else{ g[i] = g[i + 1] + c[i].b; q.push(make_pair(-c[i].b, i)); int tp = q.top().second, vl = q.top().first; g[i] += vl; q.pop(); } } int c1 = inf; for(int i = y; i <= n - z; i ++) c1 = max(c1, g[i + 1] + f[i]); cout << ans + c1; return 0; }
[ARC059F] バイナリハック
小z得到了一个键盘,里面只有
键
退格键可以删除前面打出的那个字符。
小z可以操作这个键盘
注意:当前没有字符也可以使用退格键
note:
首先设
考虑第一步操作。
先不考虑在字符串开头退格的情况。为了保证前面填好的字符不被删除,要随时保证在任意时刻插入的字符多于删除的字符。换种表示方式,如果将插入看成左括号,删除看成右括号,有这一段操作一定是合法的括号串。
插入有两种方案,设一个合法括号的左括号数量为
因为这一步操作实际上对敲出最终字符串无贡献,因此这一步操作对答案的贡献仅与长度有关。对于一个给定长度
因此只需要求给定长度的合法括号串数量。这是个卡特兰数问题。设长度为
再考虑在字符串开头退格的情况。我们需要得到的是经过
边界为
那么需要的经过
至此我们已经完成了求解对于任意
继续考虑第二步操作
设
有
边界是
然后就得到了一个
先记下
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> #include<string> #define int long long using namespace std; const int modd = 1000000007; int N, n; string s; int fac[10005], inv[10005], mi[10005], val[10005]; int ksm(int u, int v){ int ret = 1; while(v){ if(v & 1) ret = ret * u % modd; u = u * u % modd, v >>= 1; } return ret; } int C(int n, int m){ return (fac[n] * inv[n - m] % modd) * inv[m] % modd; } int H(int n){ return ((C(2 * n, n) - C(2 * n, n - 1)) % modd + modd) % modd; } int g[5005][5005], f[5005][5005]; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> N >> s, n = s.length(); n ++, N ++; fac[0] = inv[0] = 1; for(int i = 1; i <= N * 2; i ++) fac[i] = fac[i - 1] * i % modd; inv[N * 2] = ksm(fac[N * 2], modd - 2); for(int i = (N * 2) - 1; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % modd; mi[0] = 1; for(int i = 1; i <= N * 2; i ++) mi[i] = mi[i - 1] * 2 % modd; for(int i = 1; i <= N * 2; i ++) val[i] = H(i) * mi[i] % modd; val[0] = 1; g[0][0] = 1; for(int i = 1; i <= N ; i ++) for(int j = 0; j <= N; j ++) if(j == 0) g[i][j] = (g[i - 1][j + 1] + g[i - 1][j]) % modd; else g[i][j] = (g[i - 1][j + 1] + 2 * g[i - 1][j - 1] % modd) % modd; for(int i = 1; i <= N; i ++) f[1][i] = g[i - 1][0]; for(int i = 2; i <= n; i ++) for(int j = 1; j <= N; j ++){ for(int k = 1; k <= j; k ++){ if((j - k - 1) < 0 || (j - k - 1) & 1) continue; (f[i][j] += (f[i - 1][k] * val[(j - k - 1) / 2]) % modd) %= modd; } } cout << f[n][N]; return 0; }
然后发现这个东西是个卷积,一看模数不是 NTT 模数,摆了(
solution:
被题解薄纱。
设
显然有两种转移方式,一种是退格,一种是增加一个数字。
如果退格,那么会删除一个字符,并且不关心最后一个字符是否匹配。因为最后一个字符匹配或者不匹配的方案数是相同的,所以将
如果增加字符,那么默认直接匹配,所以
然后做完了。
code
#include<iostream> #include<fstream> #include<algorithm> #include<string> #define int long long using namespace std; const int modd = 1000000007; int n; string s; int f[5005][5005]; signed main(){ cin >> n >> s; f[0][0] = 1; for(int i = 1; i <= n; i ++) for(int j = 0; j <= i; j ++) f[i][j] = (f[i - 1][max(j - 1, 0ll)] + f[i - 1][j + 1] * 2 % modd) % modd; cout << f[n][s.length()]; return 0; }
代码很短。但是好像我的做法暴力卷积一下可以做到
但是不是很会卷积,所以还是摆(
[ARC129D] -1+2-1
给定一个环
你可以执行任意次如下操作:
选择一个位置
你需要将-1
.
solution:
推式子题。
首先显然一定有
否则无解。
设
令
则
那么有
又因为
所以有
如果
那么无解。
否则可以通过
根据定义,
需要构造方案使
code:
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; const int inf = 0x3f3f3f3f3f3f3f3f; int n, s, s1, d1, x1; int a[200005], sum[200005]; int d[200005]; signed main(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i], s += (n - i) * a[i], s1 += a[i], sum[i] = sum[i - 1] + a[i]; if(s % n != 0 || s1 != 0){ cout << -1; return 0; } d1 = - s / n, s = 0; for(int i = 1; i <= n; i ++) d[i] = d1 + sum[i - 1]; for(int i = 2; i <= n; i ++){ s += d[i]; int xi = x1 + s; if(xi < 0){ x1 += 0 - xi; } } s = 0; d[1] = x1; for(int i = 1; i <= n; i ++) d[i] += d[i - 1], s += d[i]; cout << s; return 0; }
[ABC180F] Unbranched
求
-
图中无自环
-
每个点度数最多为
-
所有连通块最多恰好有
个点
答案对
solution:
图计数类 DP。
每个点度数最多为
套路设
钦定一个特殊点
这个强连通分量是链
首先枚举强连通分量的大小
这个强连通分量是环
同理。
状态转移方程懒得写了,按照上面的推就行了。
code:
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> #define int long long using namespace std; const int modd = 1000000007; const int inv2 = 500000004; int ksm(int u, int v){ int ret = 1; while(v){ if(v & 1) ret = ret * u % modd; u = u * u % modd, v >>= 1; } return ret; } int fac[305], inv[305]; int C(int u, int v){ return (fac[u] * inv[v] % modd) * inv[u - v] % modd; } int f[305][305]; int L, n, m; int solve(int l){ memset(f, 0, sizeof(f)); f[0][0] = 1; for(int i = 1; i <= n; i ++){ for(int j = 0; j <= m; j ++){ for(int k = 1; k <= min(l, min(i, j + 1)); k ++){ (f[i][j] += f[i - k][j - k + 1] * C(n - i + k - 1, k - 1) % modd * fac[k] % modd * (k > 1 ? inv2 : 1) % modd) %= modd; } for(int k = 2; k <= min(l, min(i, j)); k ++){ (f[i][j] += f[i - k][j - k] * C(n - i + k - 1, k - 1) % modd * fac[k - 1] % modd * (k > 2 ? inv2 : 1) % modd) %= modd; } } } return f[n][m]; } signed main(){ fac[0] = inv[0] = 1; for(int i = 1; i <= 300; i ++) fac[i] = i * fac[i - 1] % modd; inv[300] = ksm(fac[300], modd - 2); for(int i = 299; i >= 1; i --) inv[i] = (i + 1) * inv[i + 1] % modd; cin >> n >> m >> L; cout << ((solve(L) - solve(L - 1)) % modd + modd) % modd; return 0; }
[ARC106F] Figures
有
solution:
Prufer 序列和生成函数什么的完全不会,所以记录个组合做法。
记
考虑如何才能让整张图连成一棵树。考虑为每个点指定一个特殊的孔,那么每次连边操作可以看成从一个特殊孔连向一个非特殊孔。这里指定特殊孔的方案数显然是
在树中,每个点仅有一个父亲,所以考虑为每个特殊点指定父亲。
需要指定父亲的特殊点仅有
即有:
计算这个式子的时间复杂度是
code:
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; const int modd = 998244353; int a[200005], n, sum, ans1 = 1, s, ans2 = 1; signed main(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i], sum += a[i], (ans1 *= a[i]) %= modd; s = sum - n; s %= modd; for(int i = 0; i < n - 2; i ++) (ans2 *= (((s - i) % modd + modd) % modd)) %= modd; cout << ans1 * ans2 % modd; return 0; }
[AGC016E] Poor Turkeys
有
1.若
2.若
3.若
注意,第
求有多少个
solution:
SError_ 推荐的一道好玩题。
自己思考的时候大概想到了我们需要贪心的保护一只鸡的情况下求有多少鸡是一定活不下来的,但是没有想到倒流式 DP。
设状态
正着考虑比较难,考虑时光倒流。假设我们在某一步因为要保护某只鸡所以要炖了另一只,那么被炖的那只鸡一定要保证在这之前是安全的。因此状态
探究下
设
考虑两只最终有可能留下来的鸡
那么根据这两个结论直接 dp 然后模拟即可,时间复杂度为
code:
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int n, m; int p1[100005], p2[100005]; bool f[405][405], flg[405]; int ans; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> m; for(int i = 1; i <= m; i ++) cin >> p1[i] >> p2[i]; for(int i = 1; i <= n; i ++){ f[i][i] = 1; for(int j = m; j >= 1; j --){ int u = p1[j], v = p2[j]; if(f[i][u] && f[i][v]){ flg[i] = 1; break; } if(f[i][u]) f[i][v] = 1; if(f[i][v]) f[i][u] = 1; } } for(int i = 1; i <= n; i ++){ if(flg[i]) continue; for(int j = i + 1; j <= n; j ++){ if(flg[j]) continue; bool cg = 1; for(int k = 1; k <= n; k ++) cg = cg && (f[i][k] != 1 || f[j][k] != 1); ans += cg; } } cout << ans; return 0; }
[AGC015D] A or...or B Problem
从
solution:
首先对于
设
l = 329 = 101 0 01001 r = 361 = 101 1 01001 z = 352 = 101 1 00000
那么可以把原区间拆成两部分,
仅在 中运算
显然,因为或运算不会进位,也不会让答案变小,一定可以构成
仅在 中运算
找到
l = 329 = 101 0 0 1 001 r = 361 = 101 1 0 1 001 z = 352 = 101 1 0 0 000 p = 15 = 000 0 0 1 111
那么一定可以构造出
同时在 和 中运算
分析与上面雷同因此省略。
答案为区间
将三个区间并起来即可得到最终答案。
code:
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int l, r, z, nd; int getz(int u, int v){ int x = 1, z2 = 0; while(u || v){ if(u % 2 != v % 2) z2 = max(z2, x); x <<= 1; u >>= 1, v >>= 1; } int y = r; int z3 = z2; int nw = 1; while(z3){ if((y & nw)) y -= nw; if(z3 == 1) y |= z2; z3 >>= 1; nw <<= 1; } nd = r - y; return y; } signed main(){ cin >> l >> r; if(l == r) { cout << 1; return 0; } z = getz(l, r); int ans = z - l; int l1 = z, r1 = z, l2 = (l | z), r2 = (z | (z - 1)); int kw = 0; for(int i = 1; i <= nd; i <<= 1) if(i & nd) kw = i; for(int i = 1; i <= kw; i <<= 1) nd = nd | i; int p = z + nd; r1 = p; if(r1 < l2) ans += r1 - l1 + r2 - l2 + 2; else ans += r2 - l1 + 1; cout << ans; return 0; }
[AGC021D] Reversed LCS
设
给一个字符串
solution:
看起来是道水紫,开题到做完只用了 15min,然后一看 Difficulty 2284 乐了。
有一个结论,有
然后就直接区间 dp,做完了。
code:
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> using namespace std; char s[305];int n, p; int f[305][305][305]; int main(){ cin >> s + 1 >> p, s[0] = '#', n = strlen(s) - 1; for(int i = 1; i <= n; i ++) for(int j = 0; j <= p; j ++) f[i][i][j] = 1; for(int r = 1; r <= n; r ++){ for(int l = r - 1; l >= 1; l --){ for(int k = 0; k <= p; k ++){ f[l][r][k] = f[l + 1][r][k], f[l][r][k] = max(f[l][r][k], f[l][r - 1][k]); if(s[l] == s[r]) f[l][r][k] = max(f[l][r][k], f[l + 1][r - 1][k] + 2); else if(k != 0) f[l][r][k] = max(f[l][r][k], f[l + 1][r - 1][k - 1] + 2); } } } int ans = 0; for(int i = 1; i <= n; i ++) for(int j = i; j <= n; j ++) ans = max(ans, f[i][j][p]); cout << ans; return 0; }
[AGC055D] ABC Ultimatum
称一个长度为
Solution
一道大 dp。
考虑转化条件。假设在目前的方案中前
首先考虑字母
- 对于
,有 的值一定不大于前缀 的个数。
同理,分析
- 设
, , ,有 ,其中 代表前缀最大值。
必要性已经证明出来了,充分性自己不会证明,故扔张图上来。
记录下
code
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> #define int long long using namespace std; const int modd = 998244353; char s[50]; int n, m, ans; int f[52][17][17][17][17][17]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> m, n = m * 3; cin >> s + 1; f[0][0][0][0][0][0] = 1; for(int i = 0; i < n; i ++){ for(int a = 0; a <= m; a ++){ for(int b = 0; b <= m; b ++){ int c = i - a - b; if(c < 0 || c > m) continue; for(int xa = 0; xa <= m; xa ++){ for(int xb = 0; xb <= m; xb ++){ for(int xc = 0; xc <= m; xc ++){ if(xa + xb + xc > m) continue; if(s[i + 1] == 'A' || s[i + 1] == '?') (f[i + 1][a + 1][b][max(xa, a + 1 - c)][xb][xc] += f[i][a][b][xa][xb][xc]) %= modd; if(s[i + 1] == 'B' || s[i + 1] == '?') (f[i + 1][a][b + 1][xa][max(xb, b + 1 - a)][xc] += f[i][a][b][xa][xb][xc]) %= modd; if(s[i + 1] == 'C' || s[i + 1] == '?') (f[i + 1][a][b][xa][xb][max(xc, c + 1 - b)] += f[i][a][b][xa][xb][xc]) %= modd; } } } } } } for(int i = 0; i <= m; i ++) for(int j = 0; j <= m; j ++) for(int k = 0; k <= m; k ++) if(i + j + k <= m) (ans += f[n][m][m][i][j][k]) %= modd; cout << ans; return 0; }
[ARC154D] A + B > C ?
有一个隐藏的长度为
你可以询问交互库 ? i j k
,交互库会判断 Yes
,否则回答 No
。你需要在至多
交互库不自适应,即排列
solution:
做了一段时间 DP 了做一道交互题玩一下(
首先对于 ? 1 1 x
为 No
。假设
有了 ? x p y
得到是否有
那么有了比较就能排序了。用稳定的归并排序即可达到较少询问。调用 stl 中的 stable_sort 即可。
因为比较可能会有重复,所以记忆化一下可以再压缩一下询问次数。
code:
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> using namespace std; int ans[2005], a[2005]; int p = 1, n; int ask(int u, int v, int w){ cout << "? "; cout << u << " " << v << " " << w << "\n"; fflush(stdout); char s[4]; cin >> s; return s[0] == 'Y'; } int answer(){ cout << "! "; for(int i = 1; i <= n; i ++) cout << ans[i] << " "; cout << "\n"; fflush(stdout); return 0; } int rea[2005][2005]; bool cmp(int u, int v){ if(rea[u][v] != -1) return rea[u][v]; rea[u][v] = (!ask(u, p, v)); return rea[u][v]; } int main(){ cin >> n; memset(rea, -1, sizeof(rea)); for(int i = 2; i <= n; i ++) if(ask(p, p, i)) p = i; for(int i = 1; i <= n; i ++) a[i] = i; stable_sort(a + 1, a + n + 1, cmp); for(int i = 1; i <= n; i ++) ans[a[i]] = i; answer(); return 0; }
[AGC010D] Decrementing
黑板上写着
高桥君和青木君将使用这些数来玩一个游戏。高桥君在这个游戏中是先手,他们将轮流进行以下操作(以下两步相当于一次操作):
- 选择黑板中大于
的一个数,将其减 。 - 此后,将黑板上所有数全部除以所有数的最大公约数。
当黑板上的数全部为
- 从
到 的所有数的最大公约数为 。
solution
好玩博弈。
考虑什么情况是先手必胜,很容易得到是像这样的东西:
这个时候只要把
考虑回溯一步,因为最优情况一定会避免如上状态,因此上一步后手一定会让其中一个
因此得到
当有
扩展一下,场上的偶数个数为奇数时,先手一定能够保护好这奇数个偶数,直到场上出现
那么如果场上有偶数个偶数时,先手就会很危险,急切需要改变场上的数字的奇偶性。
为了改变奇偶性,唯一的方法是用一步做到
即在场上没有
至此已经判断完所有情况,依次执行以下流程即可判断:
-
判断是否出现了
,如果出现了 则直接判断即可。 -
记场上的偶数的个数为
,如果 是奇数则先手必胜。 -
此时若场上奇数的个数大于
,则后手必胜。 -
否则操作场上唯一的奇数,然后全部除以数列的
,先手权转交后手后回到流程开始继续判断。
设数列值域为
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int n, a[100005]; int checkone(){ int t = 0; for(int i = 1; i <= n; i ++) if(!(a[i] & 1)) t ++; return (!(t & 1)) + 1; } int check(){ int t = 0, t1 = 0, p; for(int i = 1; i <= n; i ++) if(a[i] == 1) return checkone(); else if(!(a[i] & 1)) t ++; else t1 ++, p = i; if(t & 1) return 1; if(t1 > 1) return 2; a[p] --; int gcd = a[1]; for(int i = 2; i <= n; i ++) gcd = __gcd(a[i], gcd); for(int i = 1; i <= n; i ++) a[i] /= gcd; return 3; } signed main(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i]; int q, opt = 1; while((q = check()) == 3) opt ^= 1; if(opt == (q & 1)) cout << "First"; else cout << "Second"; return 0; }
[AGC005D] ~K Perm Counting
如果一个排列
由于答案很大,请输出答案对
【数据范围】
note
这题做的比较久,所以少见的记一下 note。
看到这题想到是个图论题。
首先用图论的角度思考了一下错排问题,将
或者说用容斥的角度思考了一下错排问题,这种时候就是一个简单 DP。
那么考虑原题的图论意义。
考虑
那么按一道构造题的思路,把对
然后就是不能连横向边,但是到处跑来跑去连环很烦,所以止步于此/ll
solution
那如果把
这样就变成了一张二分图,左部是表示
一样的把不能连的边拍成一张表格。
因为这样是一次一次连边而不是连一堆环所以好做多了。
那就和上面的容斥思路一致,改为考虑连这些不可连的边。注意到每个点入度出度为
在表格上 DP 可以,但没必要所以我们可以把表格拍到一条线上,然后记录一下每一行的末尾。
设
显然有状态转移方程:
注意在表格中每一行开头无法用第二条柿子转移。
然后套路容斥即可。
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; const int modd = 924844033; int n, ans, k; int f[4005][4005][2]; int fac[2005]; bool vis[4005]; signed main(){ cin >> n >> k; fac[0] = 1; for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % modd; for(int i = 1, nw = 0; i <= k; i ++){ for(int u = 0; u <= 1; u ++){ for(int j = i; j <= n; j += k){ nw ++; if(i != j) vis[nw] = 1; } } } f[0][0][0] = 1; for(int i = 1; i <= 2 * n; i ++){ for(int j = 0; j <= n; j ++){ f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]) % modd; if(vis[i] && j) f[i][j][1] = f[i - 1][j - 1][0]; } } for(int i = 0; i <= n; i ++){ if(i & 1) ans = (ans - (((f[n * 2][i][0] + f[n * 2][i][1]) % modd) * fac[n - i]) % modd + modd) % modd; else ans = (ans + (((f[n * 2][i][0] + f[n * 2][i][1]) % modd) * fac[n - i]) % modd) % modd; } cout << ans; return 0; }
[AGC030D] Inversion Sum & [CF258D] Little Elephant and Broken Sorting
([AGC030D] Inversion Sum)
给你一个长度为
答案需要对
([CF258D] Little Elephant and Broken Sorting)
有一个
求进行m次操作以后的期望逆序对个数。
solution
Atcoder 你怎么抄 CF 啊(
[CF258D] Little Elephant and Broken Sorting
首先 和的期望 等于 期望的和,一个逆序对会产生
然后是一个概率 dp。设
对于一次操作
考虑第三个点
即对于一次操作状态转移方程为:
然后就做完了。
[AGC030D] Inversion Sum
如果做过上面那道题,这题很显然,只需要在上一题的基础上乘上
是这样,吗?
注意到这题不是排列,因此没有
那也很显然,将概率均摊一下即可。
然后做完了。但是其实如果没做过上面那题,这题最有意思的地方也就是把 总和 转换为 期望乘上方案数,也是一个比较难想到的点。
code
[CF258D] Little Elephant and Broken Sorting
#include<iostream> #include<fstream> #include<algorithm> #include<iomanip> #define int long long using namespace std; int n, T; double a[5005]; double f[5005][5005], ans; signed main(){ cin >> n >> T; for(int i = 1; i <= n; i ++) cin >> a[i]; for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) f[i][j] = (double)(a[i] > a[j]); while(T --){ int u, v; cin >> u >> v; for(int i = 1; i <= n; i ++){ if(i == u || i == v) continue; f[i][u] = f[i][v] = (f[i][u] + f[i][v]) / 2.0; f[u][i] = f[v][i] = (f[u][i] + f[v][i]) / 2.0; } f[u][v] = f[v][u] = 0.5; } for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++) ans += f[i][j]; cout << fixed << setprecision(9) << ans; return 0; }
[AGC030D] Inversion Sum
#include<iostream> #include<fstream> #include<algorithm> #include<iomanip> #define int long long using namespace std; int n, T; int a[5005]; int f[5005][5005], ans; const int modd = 1000000007, inv2 = 500000004; int ret = 1; signed main(){ cin >> n >> T; for(int i = 1; i <= T; i ++) (ret *= 2) %= modd; for(int i = 1; i <= n; i ++) cin >> a[i]; for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) f[i][j] = (a[i] > a[j]); while(T --){ int u, v; cin >> u >> v; f[u][v] = f[v][u] = ((f[u][v] + f[v][u]) % modd * inv2) % modd; for(int i = 1; i <= n; i ++){ if(i == u || i == v) continue; f[i][u] = f[i][v] = ((f[i][u] + f[i][v]) % modd) * inv2 % modd; f[u][i] = f[v][i] = ((f[u][i] + f[v][i]) % modd) * inv2 % modd; } } for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++) (ans += f[i][j]) %= modd; cout << ans * ret % modd; return 0; }
[AGC016D] XOR Replace
一个序列,一次操作可以将某个位置变成整个序列的异或和。 问最少几步到达目标序列。
solution
翻 SError_ 的做题记录翻到的。
懒得写了明天补。
还是懒得写后天补……
一周后终于开始补了!
重要性质:设
那么我们把
那么就可以先把无解判掉了。有解当且仅当此时
感觉有点像小时候玩的华容道啊,那就按照华容道的玩法,假设第
这启发我们可以建图。首先
也就是,最终答案
是这样吗?
如果第
可以用并查集维护连通块,然后就没了。
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int n, sm, sm1, cnt; int a[100005], b[100005]; int c[100005], d[100005]; int fa[500005], ct[500005], num; int find(int u){if(u == fa[u]) return u; return fa[u] = find(fa[u]);} signed main(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i], sm ^= a[i], c[i] = a[i]; for(int i = 1; i <= n; i ++) cin >> b[i], sm1 ^= b[i], d[i] = b[i]; n ++, a[n] = sm, b[n] = sm1, c[n] = sm, d[n] = sm1; sort(c + 1, c + n + 1), sort(d + 1, d + n + 1); for(int i = 1; i <= n; i ++) if(c[i] != d[i]){cout << -1;return 0;} for(int i = 1; i <= n; i ++) if(a[i] != b[i] || i == n) ct[++ num] = a[i], ct[++ num] = b[i], cnt += (i < n); if(!cnt){cout << 0; return 0;} sort(ct + 1, ct + num + 1); num = unique(ct + 1, ct + num + 1) - ct - 1; for(int i = 1; i <= num; i ++) fa[i] = i; for(int i = 1; i <= n; i ++){ if(a[i] != b[i] || i == n){ a[i] = lower_bound(ct + 1, ct + num + 1, a[i]) - ct; b[i] = lower_bound(ct + 1, ct + num + 1, b[i]) - ct; fa[find(a[i])] = find(b[i]); } } for(int i = 1; i <= num; i ++) if(fa[i] == i) cnt ++; cout << cnt - 1; return 0; }
[ARC124D] Yet Another Sorting Problem
给定长度为
第一行输入
solution
做完上面那题后做这题轻松很多。
虽然还是看了题解(
建图,形成若干个环。画一下发现,当我们对一条边
但是前提是这个环需要可以交换,也就是这个环不能是同色的。
我们肯定想让环的数量越多越好,所以我们直接将异色的同色环两两匹配,即一次交换把这两个环拼起来,然后把不能匹配的随便找个异色环塞进去。假设有异色环
因为每个点都属于一个环,而每个环会让答案减去
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int n, m; int a[200005]; bool vis[200005]; int cnt, cnt1, cnt2, cnt3, ans; signed main(){ cin >> n >> m; for(int i = 1; i <= n + m; i ++) cin >> a[i]; for(int i = 1, u, flg = 0, flg1 = 0; i <= n + m; i ++){ flg = 0, flg1 = 0; if(vis[i]) continue; vis[i] = true, u = i; if(a[u] == u){cnt3 ++;continue;} if(u <= n) flg |= 1; else flg1 |= 1; while(a[u] != i){ u = a[u], vis[u] = true; if(u <= n) flg |= 1; else flg1 |= 1; } cnt ++; if(flg && !flg1) cnt1 ++; else if(!flg && flg1) cnt2 ++; } ans = max(cnt1, cnt2); cnt -= max(cnt1, cnt2); ans += (n + m - cnt3) - cnt; cout << ans; return 0; }
[AGC027D] Modulo Matrix
- 构造一个
的矩阵. 要求:- 所有元素互不相同.
- 满足
. - 对于任意两个相邻的数字 ,
都相等,且均为正整数。
- 可以证明方案一定存在.
solution
可以想到黑白染色,然后让白点的权值为周围黑点的
但是如果黑点随便填,那么随便填下就超过
所以要尽量使白点周围的黑点的共同因子尽可能大。为了匹配各种黑点,所以考虑从对角线入手,对每条主对角线和副对角线分配两个质数,这样每两个黑点做
因为要为
那么调整下质数的顺序,相邻的对角线按大-小-大-小这么分配质数即可。
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int prime[2005], tot; bool vis[100005]; int n; int a[505][505]; int l[505], r[505]; int prework(){ for(int i = 2; i <= 8000; i ++){ if(!vis[i]) prime[++ tot] = i; if(tot == 1000) break; for(int j = 1; j <= tot && i * prime[j] <= 8000; j ++){ vis[i * prime[j]] = 1; if(i % prime[j] == 0) break; } } return 0; } int gcd(int u, int v){ if(u < 0 || v < 0){ cout << "< 0!!!\n"; exit(0); return 0; } if(u < v) return gcd(v, u); if(v == 0) return u; return gcd(u % v, v); } int lcm(int u, int v){ // cout << u << " " << v << "\n"; // cout << u << " " << v << "\n ----------- \n"; return u / gcd(u, v) * v; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); prework(); for(int i = 2; i <= 500; i += 2) swap(prime[i], prime[1000 - i]); cin >> n; if(n == 2){ cout << "4 7\n23 10"; return 0; } for(int i = 1; i <= n; i ++) l[i] = prime[i], r[i] = prime[i + n]; for(int i = 1; i <= n; i ++){ for(int j = 1; j <= n; j ++){ if((i & 1) == (j & 1)){ int u = (i - j) / 2 + ((n + 1) / 2); int v = (i + j) / 2; a[i][j] = l[u] * r[v]; } } } for(int i = 1; i <= n; i ++){ for(int j = 1; j <= n; j ++){ if(!a[i][j]){ int lm = 1; if(i + 1 <= n) lm = lcm(lm, a[i + 1][j]); if(i - 1 >= 1) lm = lcm(lm, a[i - 1][j]); if(j + 1 <= n) lm = lcm(lm, a[i][j + 1]); if(j - 1 >= 1) lm = lcm(lm, a[i][j - 1]); a[i][j] = lm + 1; } cout << a[i][j] << " "; if(a[i][j] > 1e15){ cout << "WA!"; exit(0); } } cout << "\n"; } return 0; }
[ARC053D] 2 つの山札
给定两个
选择
问最后能得到多少种不同的序列
solution
题意看起来非常复杂,而这题的一难点在于转化题意。
画一个矩阵,横排代表
问题转化为从左上角走到右下角,只能向右走或向下走来转移状态,形成的不同数列的数量。
先不考虑重复,可以得到个很简单的方程:
然后来去重。
我们发现出现重复的条件是走到格子
此时去找在这之前也满足
推导下得到方程,其中
因为满足
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; int a[1005], b[1005]; int C[1005]; int f[1005][1005]; int n; const int modd = 1000000007; int jc[2005], inv[2005]; int ksm(int u, int v){ int ret = 1; while(v){ if(v & 1) (ret *= u) %= modd; v >>= 1, (u *= u) %= modd; } return ret; } int getC(int u, int v){ return (jc[u] * inv[v] % modd) * inv[u - v] % modd; } signed main(){ C[0] = 1; jc[0] = inv[0] = 1; for(int i = 1; i <= 2000; i ++) jc[i] = jc[i - 1] * i % modd; inv[2000] = ksm(jc[2000], modd - 2); for(int i = 1999; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % modd; for(int i = 1; i <= 1000; i ++) C[i] = (getC(2 * i, i) - getC(2 * i, i - 1) + modd) % modd; cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i]; for(int i = 1; i <= n; i ++) cin >> b[i]; f[1][1] = 1; for(int i = 1; i <= n; i ++){ for(int j = 1; j <= n; j ++){ if(i > 1 || j > 1) f[i][j] = (f[i - 1][j] + f[i][j - 1]) % modd; if(a[i] == b[j]){ for(int k = 1, cnt = 0; k < i && k < j; k ++){ if(a[i - k] == b[j - k]) f[i][j] = ((f[i][j] - C[cnt] * f[i - k][j - k] % modd) + modd) % modd, cnt ++; } } } } cout << f[n][n]; return 0; }
[AGC014D] Black and White Tree
-
给出一颗
个节点组成的树,每个节点都可以被染成白色或者黑色; -
有高桥(先手)和青木(后手)两个人————高桥可以把任意一个点染成白色,青木则可以把任意一个点染成黑色,每个点只可染色一次。
-
重复上述操作直到所有点都被染色后,只执行一次执行以下操作:
-
把所有青木染成黑色的节点的相邻的白点感染成“次黑色”。
-
次黑色不能继续感染白点。
-
-
若操作完毕后仍还有白点存在,即高桥(先手)胜,反之则青木(后手)胜。
-
现在给出这棵树,问当前此树是先手必胜还是后手必胜。
solution
如果一个黑色节点和一个白色节点相邻,那么我们说黑色节点控制了白色节点。
那么后手的胜利条件即为所有白色节点都被控制。现在考虑后手的策略。
首先如果这棵树有完美匹配(即用一半的边覆盖整棵树),那么显然后手可以在先手染白一个点后直接染黑其匹配点,这样所有白点都会被控制。
因此只考虑这棵树没有完美匹配的情况。
考虑一个叶子节点。如果叶子节点的父亲节点被染成了白色,那么这个叶子节点一定要染成黑色,否则叶子节点将永远无法被控制。
现在假设进行了一次如上操作,两个点
这样持续进行下去,每次删除两个点。因为这棵树没有完美匹配,所以最后肯定剩下很多零散的点。
那么这个时候先手随便取一个零散的点,后手找不到任意一个点去控制住这个点,因此先手胜。
因此我们得到结论:后手必胜的充要条件是这棵树有完美匹配。
接下来考虑如何判定一棵树是否有完美匹配。
首先如果这棵树的点数是奇数,那么显然没有。
我们尝试递归构造出完美匹配。
在一个完美匹配中,任意一棵子树只有两种状态,要么这棵子树本身是完美匹配,要么这棵子树删去根节点后是完美匹配,并且根节点与父亲节点连边形成匹配。边界为,空树是完美匹配,而叶子节点则需要和父亲节点匹配。
假设现在考虑到一个点
-
如果
的子节点中有两棵子树状态为需要和父亲节点匹配来形成完美匹配,那么这无法做到,因此这棵树没有完美匹配。 -
如果
的子结点中有一棵子树需要和父亲节点连边,那么将 和这一子树匹配。其他子树已经是完美匹配了,因此此时这棵子树形成完美匹配。 -
如果
的子节点全部都是完美匹配,那么 需要和父亲节点匹配来形成完美匹配。
按这种条件已经可以进行 dp 判定了。但是还能够再简洁些。
我们考虑每个子树的大小。容易发现需要和父亲节点匹配的子树大小一定是奇数,而本身形成完美匹配的子树大小一定是偶数。那么这三个条件等价于判断子节点的子树大小的奇偶性,如果一个节点的子结点中有两棵子树的大小为奇数则不可行。
至此完成了完美匹配的判定,根据完美匹配的判定判断先手必胜或必败即可。
code
#include <iostream> #include <fstream> #include <algorithm> #include <vector> //#define int long long using namespace std; int n; vector<int> edge[100005]; int siz[100005]; bool flg = true; int dfs(int u, int fa) { siz[u] = 1; bool chk = 0; for (int v : edge[u]) { if (v == fa) continue; dfs(v, u); if (chk == 1 && (siz[v] & 1)) flg = false; chk = chk || (siz[v] & 1), siz[u] += siz[v]; } return 0; } signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n; if (n & 1) { cout << "First"; return 0; } for (int i = 1, u, v; i < n; i ++) cin >> u >> v, edge[u].push_back(v), edge[v].push_back(u); dfs(1, 0); if (flg == false) cout << "First"; else cout << "Second"; return 0; }
[AGC008D] K-th K
给你一个长度为
条件如下:
-
的长度为 ,并且满足数字 都各出现恰好 次。 -
对于
,数字 在 中第 次出现的位置是 。
solution
其实数字
位置是 。 位置前面有 个 。 位置后面有 个 。
考虑一个一个条件去满足。先在
然后贪心的,按
同理,按
没什么好讲的,除了能够拆成三个条件做以外没什么了。
code
#include <iostream> #include <fstream> #include <algorithm> #include <vector> //#define int long long using namespace std; pair<int, int> a[505]; int n; int lst, ans[250005]; bool cmp(pair<int, int> u, pair<int, int> v) { return u.first > v.first; } signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n; for (int i = 1; i <= n; i ++) cin >> a[i].first, a[i].second = i, ans[a[i].first] = i; sort(a + 1, a + n + 1); for (int i = 1; i <= n; i ++) { int cnt = a[i].second - 1; if (cnt != 0) for (int j = 1; j < a[i].first; j ++) { if (!ans[j]) ans[j] = a[i].second, cnt --; if (cnt == 0) break; } if (cnt) { cout << "No"; return 0; } } for (int i = n; i >= 1; i --) { int cnt = n - a[i].second; if (cnt != 0) for (int j = n * n; j > a[i].first; j --) { if (!ans[j]) ans[j] = a[i].second, cnt --; if (cnt == 0) break; } if (cnt) { cout << "No"; return 0; } } cout << "Yes\n"; for (int i = 1; i <= n * n; i ++) cout << ans[i] << " "; return 0; }
[AGC032D] Rotation Sort
给定一个排列, 你可以花费
solution
首先任意一个数要么不操作,要么只操作一次。因为如果操作两次,那么完全可以在第一次操作的时候跑到第二次操作跑到的地方。
显然,那些没有动过的数会形成一个上升序列。
考虑 dp。设
主动型转移。如果下一个数决定不动,或是向右移动,那么条件是下一个数需要大于前一个不动的数。即有转移:
否则下一个数一定只能向左移动,去跨过上一个不动的数达成上升。
然后做完了。
code
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> #define int long long using namespace std; const int inf = 0x3f3f3f3f3f3f3f3f; int f[5005][5005]; int n, a[5005], A, B; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> A >> B; for(int i = 1; i <= n; i ++) cin >> a[i]; memset(f, 0x3f, sizeof(f)); f[0][0] = 0; for(int i = 0; i < n; i ++){ for(int j = 0; j <= i; j ++){ if(a[i + 1] > a[j]) f[i + 1][i + 1] = min(f[i][j], f[i + 1][i + 1]), f[i + 1][j] = min(f[i][j] + A, f[i + 1][j]); else f[i + 1][j] = min(f[i + 1][j], f[i][j] + B); } } int ans = inf; for(int i = 0; i <= n; i ++) ans = min(ans, f[n][i]); cout << ans; return 0; }
[AGC023C] Painting Machines
- 有一排
个格子,从左到右编号为 到 。 - 有
个机器,从左到右编号为 到 ,操作第 个机器可以将第 个和第 个格子染黑。 - 定义一个
的排列 的分数为,依次操作 ,第一次染黑所有格子的时刻。 - 求所有排列
的分数之和,对 取模。 .
Solution
考虑操作第
即,如果对于排列中的一个数
那么对于一个排列
考虑通过枚举无效序列的长度来统计方案,设此时无效序列的长度为
首先,对第一台机器的操作和对第
可以得到一个结论,如果有
我们在原序列中选
通过组合数可以得到选数的方案数为
那么有:
那么无效序列长度恰好为
最终答案为
code
#include<iostream> #include<fstream> #include<algorithm> #define int long long using namespace std; const int modd = 1000000007; int n, m, res = 0; int ksm(int u, int v){ int ret = 1; while(v){ if(v & 1) ret = ret * u % modd; u = u * u % modd, v >>= 1; } return ret; } int fac[1000005], inv[1000005]; int C(int u, int v){ return (fac[u] * inv[v] % modd) * inv[u - v] % modd; } int f[1000005]; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); fac[0] = inv[0] = 1; for(int i = 1; i <= 1000000; i ++) fac[i] = (fac[i - 1] * i) % modd; inv[1000000] = ksm(fac[1000000], modd - 2); for(int i = 999999; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % modd; cin >> n; if(n == 2) return cout << 1, 0; if(n == 3) return cout << 4, 0; m = n - 3; int tk = m; for(int i = 0; i <= m; i ++){ if(2 * i - 1 > m){ tk = i - 1; break; } int tmp = C(m - i + 1, i); f[i] = (tmp * fac[n - 1 - i] % modd) * fac[i] % modd; } for(int i = 0; i <= tk; i ++) f[i] = (((f[i] - f[i + 1]) % modd) + modd) % modd; for(int i = 0; i <= tk; i ++) res = (res + (f[i] * (n - 1 - i) % modd)) % modd; cout << res; return 0; }
[ABC201F] Insertion Sort
你可以以任意次序进行任意多次下列操作:
- 选择一个人,设其编号为
,支付 的代价将其移动到任意位置。 - 选择一个人,设其编号为
,支付 的代价将其移动到最左端。 - 选择一个人,设其编号为
,支付 的代价将其移动到最右端。
其中
你的目标是使得所有人的编号从左至右递增。输出达成目标的最小代价。
- 入力は全て整数
solution
很像 [AGC032D] Rotation Sort 啊。
首先一个数要么不操作,要么只操作一次。因为如果操作两次,那么完全可以忽略第一次操作。
有一个很显然的推论,就是不操作的数会形成一个上升序列。因为三种操作都不会改变不操作的数的相对位置。
这题和 Rotation Sort 的区别在于指定的区间只能是
懒了,明天写。
[ABC338G] evall
给一个由 123456789+*
组成的字符串
定义
求
Solution
感觉思路不难想,就是写起来有点麻烦。
设
因为乘法的优先级大于加法, 所以可以把原串里的加号当作分割点,先累加
在连续的乘法段内的
1. 没有跨过乘号
简单的递推。设
这一段的贡献即为
2. 中跨过了乘号
设
假设上一个乘号的前一个位置为
那么有
将
接下来处理加法。
记录
假设上一个加号的前一个位置为
那么有
最终答案即为
然后就是注意一下取模,然后这题就做完了。
我的代码中将
code
#include<iostream> #include<fstream> #include<algorithm> #include<string> #define int long long using namespace std; namespace solve1{ int n; const int modd = 998244353; string s; int l[1000005], r[1000005]; int val[1000005], f[1000005]; int h[1000005]; int ans; int tot; int main(){ cin >> s; n = s.length(); s = " " + s; tot = 1, l[1] = 1; for(int i = 1; i <= n; i ++) if(s[i] == '+') r[tot] = i - 1, l[++ tot] = i + 1; r[tot] = n; for(int k = 1; k <= tot; k ++){ int pre = 0, lst = 1, nw = 0, cnt = 0; for(int i = l[k]; i <= r[k]; i ++){ if(s[i] == '*') pre = (pre * nw % modd + f[i - 1]) % modd, lst = (lst * nw % modd), h[r[k]] = (h[r[k]] * nw % modd + f[i - 1]) % modd; else if(i == l[k] || s[i - 1] == '*'){ f[i] = s[i] - '0', cnt = 1; (ans += f[i]) %= modd; nw = s[i] - '0'; val[i] = lst * nw % modd; ans = (ans + pre * nw % modd) % modd; } else{ f[i] = (f[i - 1] * 10 + cnt * (s[i] - '0') % modd) % modd; f[i] = (f[i] + (s[i] - '0')) % modd, cnt ++; (ans += f[i]) %= modd; nw = nw * 10 + (s[i] - '0'); nw %= modd; ans = (ans + pre * nw % modd) % modd; val[i] = lst * nw % modd; } } h[r[k]] = (h[r[k]] * nw % modd + f[r[k]]) % modd; } int lt = 0, ct = 0, un0 = 0; for(int i = 1; i <= n; i ++){ if(s[i] != '+' && s[i] != '*') un0 ++, (ans += lt + ct * val[i] % modd) %= modd; else if(s[i] == '+'){ lt = (lt + val[i - 1] * ct % modd + h[i - 1]) % modd; ct = ct + un0, un0 = 0; } } cout << ans; return 0; } } signed main(){ int T = 1; while(T --) solve1::main(); return 0; }
[ARC159C] Permutation Addition
Description
给定一个长度为
你需要进行如下操作
- 选择一个
的排列 ,将序列 变为 。
solution
一种比较简单的构造题。
首先对于一次操作,最后增加的总和一定是
对于奇数而言,有
那么如果不满足上面的那两个条件可以直接输出 NO
。
我们发现把序列加上一次
那么如果交换一下前两个数,将序列加上
那么思路就很清晰了。不断的找序列最大的数和最小的数,用一次如上操作使两个数的距离减小,直到序列全部相等为止。
但是这样捆绑起来的两个操作会让序列整体增加
解决方法很简单。只用先将序列随便加上一个排列使得
code
#include<iostream> #include<fstream> #include<algorithm> using namespace std; namespace solve1{ int n; pair<int, int> a[55]; int ans[10005][55], tot; int check(){ int c = a[1].first; for(int i = 1; i <= n; i ++) if(a[i].first != c) return false; return true; } int main(){ cin >> n; int sum = 0; for(int i = 1; i <= n; i ++) cin >> a[i].first, a[i].second = i, sum += a[i].first; if(n % 2 == 1 && sum % n != 0) return cout << "No", 0; if(n % 2 == 0 && sum % (n / 2) != 0) return cout << "No", 0; if(n % 2 == 0 && sum % n != 0){ tot ++; for(int i = 1; i <= n; i ++) ans[tot][i] = i, a[i].first += i; } while(!check() && tot <= 10000){ sort(a + 1, a + n + 1); swap(a[2], a[n]); tot ++; ans[tot][a[1].second] = 2, ans[tot][a[2].second] = 1; a[1].first += 2, a[2].first ++; for(int i = 3; i <= n; i ++){ a[i].first += i; ans[tot][a[i].second] = i; } tot ++; for(int i = 1; i <= n; i ++){ a[i].first += (n - i + 1); ans[tot][a[i].second] = n - i + 1; } } if(tot > 10000){ cout << "No"; return 0; } cout << "Yes\n"; cout << tot << "\n"; for(int i = 1; i <= tot; i ++){ for(int j = 1; j <= n; j ++) cout << ans[i][j] << ' '; cout << "\n"; } return 0; } } signed main(){ int T = 1; while(T --) solve1::main(); return 0; }
本文作者:AzusidNya
本文链接:https://www.cnblogs.com/AzusidNya/p/17621444.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步