ABC262 解题报告
abc 262 解题报告
A
Description
给出一个\(Y\),找到最小的\(X\)满足\(X\ge Y\)且\(X\%4=2\)。
Sol
枚举\([Y,Y+3]\)之间的所有数并判断即可。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
signed main() {
int x = Read();
for(int i = x; ; i++) {
if(i % 4 == 2) {
cout << i << endl;
return 0;
}
}
return 0;
}
B
Description
给出一张\(n\)个点\(m\)条边的无向图,请找出所有的三元组\((A,B,C)\)满足\(AB,AC,BC\)都直接有边相连。
\(n\le 100\)
Sol
\(n\)如此之小,显然暴力枚举\(A,B,C\)即可,用邻接矩阵存图即可判断是否有边直接相连。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, m, a[105][105];
signed main() {
n = Read(), m = Read();
for(int i = 1; i <= m; i++) {
int x = Read(), y = Read();
a[x][y] = a[y][x] = 1;
}
int ans = 0;
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
for(int k = j + 1; k <= n; k++)
ans += (a[i][j] && a[j][k] && a[i][k]);
cout << ans << endl;
return 0;
}
C
Description
给出一个长度为\(n\)的数列,找出满足下列条件的\((i,j)\)个数:
-
\(1\le i<j\le n\)
-
\(\min(a_i,a_j)=i\)
-
\(\max(a_i,a_j)=j\)
\(n\le 2\times 10^5,1\le a_i\le n\)
Sol
显然只有两种情况,要么\(a_i=i,a_j=j\),要么\(a_i=j,a_j=i\),接下来对两种情况分别进行统计
- \(a_i=i,a_j=j\)
找出所有满足\(a_i=i\)的\(i\)的个数,记为\(c\),则该情况的总数记为\(C_c^2\)
- \(a_i=j,a_j=i\)
找出所有满足\(a_i\not= i\)且\(a_{a_i}=i\)的\(i\)的个数,由于每对\((i,j)\)会被算两次,所以还需除\(2\)
上述两种情况相加即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, a[500005];
signed main() {
n = Read();
for(int i = 1; i <= n; i++) a[i] = Read();
int cnt = 0, ans = 0;
for(int i = 1; i <= n; i++)
if(a[i] == i) ++cnt;
ans += cnt * (cnt - 1) / 2;
for(int i = 1; i <= n; i++)
if(a[i] < i && a[a[i]] == i) ++ans;
cout << ans << endl;
return 0;
}
D
Description
给定一个长为\(n\)的数列\(\{A\}\),要求找出所有\(\{A\}\)的平均数为整数的子序列(可以非连续),求出个数对998244353取模的值
\(n\le 100\)
Sol
由于子序列的数字之和肯定为整数,所以只需要考虑子序列的和是否为子序列长度的倍数。
考虑DP,设\(f_{i,j,k}\)表示目前选了\(i\)个数,要选出的子序列长度为\(j\),这\(i\)个数之和模\(j=k\)的方案数,答案即为\(\sum_{i=1}^n f_{i,i,0}\)
转移方程式也很简单,枚举\(i,j,k\),考虑下一个数选择哪一个数,假设选择\(a_l\),则转移为
时间复杂度\(O(n^4)\)
Code
点击查看代码
#include<bits/stdc++.h>
#define Mod 998244353
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, a[105], f[105][105][105];
signed main() {
n = Read();
for(int i = 1; i <= n; i++) a[i] = Read();
for(int j = 1; j <= n; j++) {
f[0][j][0] = 1;
for(int i = 1; i <= n; i++) {
int ff = a[i] % j;
for(int k = i - 1; k >= 0; k--)
for(int l = 0; l < j; l++) {
(f[k + 1][j][(l + ff) % j] += f[k][j][l]) %= Mod;
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++)
(ans += f[i][i][0]) %= Mod;
cout << ans << endl;
return 0;
}
E
Description
给定一张\(n\)个点\(m\)条边的无向图,要求统计出符合以下条件的方案数对998244353取模的值:
-
有恰好\(k\)个点被染红
-
有偶数条边满足其两个顶点只有恰好一个为红色
\(n,m\le 2\times 10^5\)
Sol
设一个点的度数为\(d\),考虑将其涂红时产生的变化,设与其直接相连的点中有\(i\)个为红色
那么题述种类的边产生的变化为\((d-i)-i=d-2i\),发现奇偶性的变化只与\(d\)有关,当\(d\)为奇数时奇偶性改变,反之不变
所以选择点时只需选择偶数个度数为奇数的点即可。
设度数为奇数的点的数量为\(c_1\),为偶数的点数量为\(c_2\),答案即为\(\sum_{i|i\%2=0}C_{c_1}^i\times C_{c_2}^{k-i}\)
预处理组合数相关变量即可。
时间复杂度\(O(n+m)\)
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define Mod 998244353
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, m, k, du[200005], fac[200005], inv[200005], finv[200005];
int C(int n, int k) {
if(k > n || k < 0) return 0;
return fac[n] * finv[k] % Mod * finv[n - k] % Mod;
}
signed main() {
fac[0] = finv[0] = inv[0] = fac[1] = finv[1] = inv[1] = 1;
for(int i = 2; i <= 200000; i++) {
inv[i] = (Mod - Mod / i) * inv[Mod % i] % Mod;
fac[i] = fac[i - 1] * i % Mod;
finv[i] = finv[i - 1] * inv[i] % Mod;
}
n = Read(), m = Read(), k = Read();
for(int i = 1; i <= m; i++) {
int x = Read(), y = Read();
++du[x], ++du[y];
}
int cnt1 = 0, cnt2 = 0;
for(int i = 1; i <= n; i++)
(du[i] & 1) ? ++cnt1 : ++cnt2;
int ans = 0;
for(int i = 0; i <= cnt1; i += 2)
(ans += C(cnt1, i) * C(cnt2, k - i)) %= Mod;
cout << ans << endl;
return 0;
}
F
Description
给出一个\([1,n]\)的排列,可以以任意顺序做以下操作至多\(k\)次
-
操作\(1\):将排列尾部的数置于头部
-
操作\(2\):删去排列中任意一个数
请求出最终能够得到的字典序最小的数列。
Sol
显然做\(k\)次操作最优,因为每次可以通过删去末尾的数来减小排列的字典序。
操作的方式有两种,一种是只进行操作\(2\),一种是都进行。
首先考虑只进行操作\(2\)的情况:
由于要求字典序最小,所以小的数放在前面一定比大的数放在前面优,考虑贪心:
对于\(1\)来说,肯定第一位是最优的,如果能通过删数放置到第一位那就直接删,否则我们要将\(1\)的位置尽量提前,于是所有删去的数均要在\(1\)之前。
对于后面的数同理执行即可,执行完毕后即得到最终的字典序最小的数组。
接下来考虑加入操作\(1\),发现其本质上是把后面的一段数平移至开头,而且我们发现如下性质:
如果我们需要将\(1 3 2\)平移至开头,需要\(3\)次操作,之后还需要把\(3\)删去,这是\(4\)次操作,但如果在平移之前直接把\(3\)删去,那么只需要\(2\)次操作就可以将\(1 2\)平移至开头,共\(3\)次,所以说平移之前先把要删的数删掉是最优的,也就意味着我们平移过来的数列可以随意执行删除操作而不影响总操作次数。
然后考虑平移的区间,观察到平移后以小的数打头一定比以大的数打头优,所以找到\([n-k+1,n]\)间最小的数,平移的区间一定是从它开始,于是平移后再按相同的方式执行删除操作即可。
时间复杂度\(O(n)\)
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, k, a[200005], pos[200005], vis[200005], b[200005], c[200005], cnt1, cnt2;
signed main() {
n = Read(), k = Read();
for(int i = 1; i <= n; i++) a[i] = Read(), pos[a[i]] = i;
if(k == 0) {
for(int i = 1; i <= n; i++) printf("%d ", a[i]);
return 0;
}
if(k == n - 1) {
cout << 1 << endl;
return 0;
}
int fg = -1;
for(int i = 1; i <= n; i++)
if(pos[i] >= n - k + 1) {
fg = pos[i]; break;
}
int num1 = k - (n - fg + 1), lst = 1, l = 1, r = fg - 1, res = fg - 1 - num1;
for(int i = 1; i <= n; i++) {
if(pos[i] < l || pos[i] > r || vis[pos[i]]) continue ;
if(!res) {vis[pos[i]] = 1; continue ;}
int len = pos[i] - l;
if(num1 >= len) {
for(int j = l; j < pos[i]; j++) vis[j] = 1;
num1 -= len; l = pos[i] + 1;
}
else r = pos[i] - 1;
res = r - l + 1 - num1;
}
lst = fg + 1, res = -1;
for(int i = 1; i < fg; i++)
if(!vis[i]) {res = a[i]; break;}
for(int i = 1; i <= n; i++) {
if(pos[i] <= fg || vis[pos[i]]) continue ;
if(i > res) {vis[pos[i]] = 1; continue ;}
for(int j = lst; j < pos[i]; j++) vis[j] = 1;
lst = pos[i] + 1;
}
int st = fg;
for(int i = 1; i <= n; i++) {
int nw = (st + i - 2) % n + 1;
if(!vis[nw]) b[++cnt1] = a[nw];
}
memset(vis, 0, sizeof(vis));
res = n - k, num1 = k, l = 1, r = n;
for(int i = 1; i <= n; i++) {
if(pos[i] < l || pos[i] > r || vis[pos[i]]) continue ;
if(!res) {vis[pos[i]] = 1; continue ;}
int len = pos[i] - l;
if(num1 >= len) {
for(int j = l; j < pos[i]; j++) vis[j] = 1;
num1 -= len, l = pos[i] + 1;
}
else r = pos[i] - 1;
res = r - l + 1 - num1;
}
for(int i = 1; i <= n; i++)
if(!vis[i]) c[++cnt2] = a[i];
int flag = -1;
for(int i = 1; i <= min(cnt1, cnt2); i++) {
if(b[i] != c[i]) {
flag = (b[i] > c[i]) ? 2 : 1;
break;
}
}
if(flag == -1) flag = (cnt1 > cnt2) ? 2 : 1;
for(int i = 1; i <= ((flag == 1) ? cnt1 : cnt2); i++)
printf("%d ", (flag == 1) ? b[i] : c[i]);
return 0;
}
G
Description
给定一个长为\(n\)的数列\(\{A\}\),一个空栈\(S\)和一个空数列\(X\)。
按照从\(1\)到\(n\)的顺序对每个\(i\)可以执行如下操作:
-
让\(A_i\)入栈
-
不管这个\(A_i\)
在任意\(S\)非空的时刻可以将栈顶的数放入\(X\)中,求最终如果在\(X\)中数单调不减的情况下\(X\)中最多能有几个数。
\(n,A_i\le 50\)
Sol
对于每个数,如果它最终不在\(X\)中,我们就不管它,反之就让它入栈,也意味着栈里的数都要进入\(X\)。
那么当一个数入栈时,如果栈里有数,那么这些数必定都要大于入栈的数,否则最终\(X\)不可能单调不减。
那么当\(X\)中最后一个数入栈时,栈一定为空。
我们考虑DP,设\(f_{i,j,k,l}\)代表只选\([i,j]\)中\([k,l]\)间的数能够得到的最大的\(X\),那么答案即为\(f_{1,n,1,50}\)
考虑转移,如果能选的最大的\(l\)最后不在\(X\)内,那么\(f_{i,j,k,l}=f_{i,j,k,l-1}\)
如果\(l\)在\(X\)内,那么因为\(l\)为当前最大数,放入\(l\)之前栈一定为空,假设\(i,j\)间存在\(m\)满足\(a_m=l\),那么\(m\)放进去之前栈一定为空
设\([i,m)\)间放入的最大数为\(n\),那么在对\([i,m)\)做完所有操作后\(n\)一定在当前\(X\)的尾部(因为\(S\)已经被清空了),那么\((m,j]\)间入栈的数一定不小于\(n\),否则不满足题意。
于是就有转移\(f_{i,j,k,l}=\max_n\{f_{i,m-1,k,n}+1+f_{m+1,j,n,l}\}\)
时间复杂度\(O(n^5)\)
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, a[55], f[55][55][55][55];
signed main() {
n = Read();
for(int i = 1; i <= n; i++) a[i] = Read();
for(int len = 1; len <= n; len++) {
for(int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
for(int m = l; m <= r; m++) {
for(int i = 1; i <= a[m]; i++)
for(int j = i; j <= a[m]; j++)
f[l][r][i][a[m]] = max(f[l][r][i][a[m]], f[l][m - 1][i][j] + f[m + 1][r][j][a[m]] + 1);
for(int i = 1; i <= 50; i++)
for(int j = i; j <= 50; j++) f[l][r][i][j] = max(f[l][r][i][j], f[l][r][i][j - 1]);
}
}
}
cout << f[1][n][1][50] << endl;
return 0;
}
Ex
Description
有\(q\)个形如L R X
的限制条件及\(m\),试求出满足如下条件的长度为\(n\)的数列\(\{A\}\)的个数对998244353取模的值。
-
对于所有\(i\in[1,n]\),需要满足\(A_i\in [0,m]\)
-
对于所有\(i\in[1,q]\),需要满足\(\max\{A_{L_i},A_{L_i+1},...,A_{R_i}\}=X_i\)
\(n,q\le 2\times 10^5,1\le m<998244353\)
Sol
考虑所有\(X\)相等且\([L_i,R_i]\)覆盖每个数的情况
设\(f_{i,j}\)表示定下了前\(i\)个数的值且最后一个\(X\)在第\(j\)位,那么转移方程式即为
直接DP是\(O(n^2)\)的,题解使用了一种叫in-place DP
的方式进行优化,具体思想为
直接在\(f_{i-1}\)的基础上更改\(f_i\),如果更改较快的话即可优化复杂度
考虑本题如何优化复杂度:由于\(f_{i,j}\)在\(i\not=j\)的情况下只会由\(f_{i-1,j}\)乘上一个定值转移而来,于是我们可以只保留所有的\(f_{i,i}\),然后需要用到\(f_{i,j}\)时乘上一个常数即可。
然而这样还是\(O(n^2)\)的,我们考虑记录一个\(S\)代表\(\sum_{k=1}^{i-1}f_{i-1,k}\times X^{i-k}\),每次算到\(f_{i,i}\)时直接赋值即可,下面考虑如何更新\(S\):
由于转移方程式中的\(X\)是一个常数,\(f_{i+1,j}\)永远等于\(f_{i,j}\times X\),所以新的\(S'=\sum_{k=1}^{i}f_{i,k}\times X^{i-k+1}=S\times X+f_{i,i}\)(考虑在读入优化时是如何读入十进制数的),稍加维护即可。
注意如果存在限制条件\(k\)满足\(R_k\le i\),那么\(L_k\)之前的所有\(f\)数组的值应该清零,因为从\(L_k\)之前的\(f\)转移过了代表\([L_k,i]\)之间的数全部不等于\(X\),也就不满足限制条件\(k\)。
然后考虑\(X\)不相等的情况,存在没有被覆盖的位置直接将答案乘上\(m+1\)即可。
然后我们定义一个\(B\)数列,初始化为\(m\),对于每个限制条件\(j\),我们将\(B_i=\min\{B_i,X_j\},i\in[L_j,R_j]\)
这个可以用STL或者线段树实现\(O(n\log n)\)的复杂度
然后对于一个限制条件\(j\),如果存在一个\(i\in[L_j,R_j]\)满足\(B_i<X_j\),那么这个\(i\)位置无论如何取数都不可能等于\(X\),也就是不会影响该限制条件,以此类推,我们可以证明所有\(B_i=X\)的位置是独立的。
对于每个\(X\),将所有\(B_i=X\)的位置和询问按照上述方式处理即可,设每个\(B_i=X\)的贡献为\(k\),则最终答案为\(\prod k\)
时间复杂度\(O(n\log n)\)
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define Mod 998244353
#define pii pair<int, int>
using namespace std;
int Read() {
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') f = -1, ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return (f == -1) ? -x : x;
}
int n, m, q, ans = 1, vis[200005], minn[200005], c[200005], f[200005], pw[200005], us[200005];
vector<int> tag[200005], v[200005];
struct node {
int l, r, x;
bool operator < (node A) const {
if(x != A.x) return x < A.x;
return r < A.r;
}
}a[200005];
signed main() {
n = Read(), m = Read(), q = Read();
for(int i = 1; i <= q; i++) {
a[i].l = Read(), a[i].r = Read(), a[i].x = Read(); c[i] = a[i].x;
tag[a[i].l].push_back(a[i].x); vis[a[i].l] += 1;
tag[a[i].r + 1].push_back(-a[i].x); vis[a[i].r + 1] += -1;
}
for(int i = 1; i <= n; i++) {
vis[i] = vis[i - 1] + vis[i];
if(!vis[i]) ans = 1ll * ans * (m + 1) % Mod;
}
multiset<int> s; s.insert(m);
for(int i = 1; i <= n; i++) {
int sz = tag[i].size();
for(int j = 0; j < sz; j++)
if(tag[i][j] < 0) s.erase(s.find(-tag[i][j]));
else s.insert(tag[i][j]);
minn[i] = *s.begin();
}
c[q + 1] = m;
sort(c + 1, c + q + 2);
int Q = unique(c + 1, c + q + 2) - c - 1;
sort(a + 1, a + q + 1);
for(int i = 1; i <= q; i++) a[i].x = lower_bound(c + 1, c + Q + 1, a[i].x) - c, us[a[i].x] = 1;
for(int i = 1; i <= n; i++) {
minn[i] = lower_bound(c + 1, c + Q + 1, minn[i]) - c;
if(minn[i] != Q || vis[i]) v[minn[i]].push_back(i);
}
int nw = 1;
for(int i = 1; i <= Q; i++) {
int sz = v[i].size(), nl = 0, cnt = 0, sum = 0;
if(sz == 0 && us[i]) ans = 0;
pw[0] = 1;
for(int j = 1; j <= sz; j++) pw[j] = 1ll * pw[j - 1] * c[i] % Mod;
f[0] = 1; sum = f[0];
for(int j = 0; j < sz; j++) {
int maxnl = 0;
while(a[nw].x == i && (j + 1 == sz || a[nw].r < v[i][j + 1])) {
maxnl = max(maxnl, a[nw].l);
++nw;
}
f[++cnt] = sum;
sum = (1ll * sum * c[i] % Mod + f[cnt]) % Mod;
while(cnt >= nl && maxnl > ((nl == 0) ? 0 : v[i][nl - 1])) {
((sum -= 1ll * f[nl] * pw[cnt - nl] % Mod) += Mod) %= Mod;
f[nl] = 0, ++nl;
}
}
for(int j = nl; j <= cnt; j++) f[j] = 0;
ans = 1ll * ans * sum % Mod;
}
cout << ans << endl;
return 0;
}