A.Chmax Rush!(枚举)
题意:
有一个长度为NNN的整数序列SSS。最初,SSS的所有元素都是000。
同时给你两个长度为QQQ的整数序列:P=(P1,P2,…,PQ)P=(P_1,P_2,\dots,P_Q)P=(P1,P2,…,PQ)和V=(V1,V2,…,VQ)V=(V_1,V_2,\dots,V_Q)V=(V1,V2,…,VQ)。
斯努克希望依次对序列SSS执行QQQ次操作。第iii个操作如下:
- 执行以下操作之一:
- 将每个元素S1,S2,…,SPiS_1,S_2,\dots,S_{P_i}S1,S2,…,SPi替换为ViV_iVi。但是,在执行此操作前,如果S1,S2,…,SPiS_1,S_2,\dots,S_{P_i}S1,S2,…,SPi中存在严格大于ViV_iVi的元素,斯努克就会开始哭泣。
- 将每个元素SPi,SPi+1,…,SNS_{P_i},S_{P_i+1},\dots,S_NSPi,SPi+1,…,SN替换为ViV_iVi。但是,在此操作之前,如果SPi,SPi+1,…,SNS_{P_i},S_{P_i+1},\dots,S_NSPi,SPi+1,…,SN中有元素严格大于ViV_iVi,斯努克就会开始哭泣。
求在QQQ的操作序列中,斯努克可以在不哭泣的情况下完成所有操作的次数,答案对998244353998244353998244353取模。
当且仅当有1≤i≤Q1\leq i\leq Q1≤i≤Q使得第iii次操作的选择不同时,两个操作序列是不同的。
分析:
考虑对于任意一个i<ji\lt ji<j,如果Vi>VjV_i>V_jVi>Vj,那么iii与jjj就会互相干扰。
如果Pi=PjP_i = P_jPi=Pj,那么jjj不管怎么选操作最后在PiP_iPi这个点都会出现SPi>VjS_{P_i} > V_jSPi>Vj的现象,此时答案为000。
如果Pi≠PjP_i \neq P_jPi=Pj,那么让PPP小的那个执行操作1→P1 \rightarrow P1→P,大的那个P→nP \rightarrow nP→n。
记录一个方向数组,如果sdi=1sd_i=1sdi=1代表必须往nnn操作,sdi=−1sd_i=−1sdi=−1代表必须往111操作,sdi=0sd_i=0sdi=0代表无约束,枚举i,ji,ji,j即可,如果出现既要一个操作向左又要向右也是无解的。
答案统计就遍历1→Q1 \rightarrow Q1→Q,ansansans初始设为111,如果sdi=0sd_i = 0sdi=0就把答案乘222。
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int N = 5005;
int P[N], V[N];
int n, q, sd[N], ans = 1;
int main() {
cin >> n >> q;
for (int i = 1; i <= q; i++) {
cin >> P[i] >> V[i];
}
for (int j = 1; j <= q; j++) {
for (int i = j + 1; i <= q; i++) {
if (V[i] >= V[j])
continue;
if (P[i] == P[j]) {
cout << 0;
return 0;
}
if (P[j] < P[i]) {
if (sd[i] && sd[i] != 1) {
cout << 0;
return 0;
}
if (sd[j] && sd[j] != -1) {
cout << 0;
return 0;
}
sd[j] = -1, sd[i] = 1;
} else {
if (sd[i] && sd[i] != -1) {
cout << 0;
return 0;
}
if (sd[j] && sd[j] != 1) {
cout << 0;
return 0;
}
sd[j] = 1, sd[i] = -1;
}
}
}
for (int i = 1; i <= q; i++)
ans = ans * ((1 + (sd[i] == 0))) % mod;
cout << ans << endl;
return 0;
}
B.|{floor(A_i/2^k)}|(构造)
题意:
给你正整数NNN和KKK。
长度为NNN的整数序列,其中所有元素都在111和2K−12^K-12K−1之间(含),称为良好序列。
良好序列A=(A1,A2,…,AN)A=(A_1,A_2,\ldots,A_N)A=(A1,A2,…,AN)的得分定义如下:
- 使用介于111和NNN(含)之间的整数iii和非负整数kkk可以表示为⌊Ai2k⌋\displaystyle\left\lfloor\frac{A_i}{2^k}\right\rfloor⌊2kAi⌋的不同整数的个数。
例如,对于A=(3,5)A=(3,5)A=(3,5),五个整数可以表示为⌊Ai2k⌋\displaystyle \left\lfloor\frac{A_i}{2^k}\right\rfloor⌊2kAi⌋:000、111、222、333和555,因此得数为555。
找出一个得分最高的好序列。
每个输入文件都有TTT个测试用例需要解决。
分析:
看到除以2k2^k2k,联想到与位运算有关,想要得到更多的分数那么右移后得到的数就要越多,所以需要构造出更多不同的二进制下位数不超过kkk的数。
从最高位开始,使用类似线段树建树的方式,每次为左边的区间按位或上111,右边的区间不进行处理。kkk为000时直接结束,若递归到叶子节点时未满kkk位,后面全部填111即可。这样二分着构造出来的所有数均不同且不断右移后能得到更多的结果。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int ans[N << 1];
void solve(int l, int r, int k) {
if (!k)
return;
if (l == r) {
while (k--)
ans[l] = (ans[l] << 1) + 1;
return;
}
int mid = (l + r) >> 1;
for (int i = l; i <= mid; i++)
ans[i] = (ans[i] << 1) + 1;
for (int i = mid + 1; i <= r; i++)
ans[i] <<= 1;
solve(l, mid, k - 1);
solve(mid + 1, r, k - 1);
}
int n, k;
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> k;
for (int i = 1; i <= n; i++)
ans[i] = 1;
solve(1, n, k - 1);
for (int i = 1; i <= n; i++)
cout << ans[i] << " ";
cout << endl;
}
return 0;
}
C.Sum of Number of Divisors of Product(数学)
题意:
长度在111和NNN之间(含)的整数序列,其中每个元素的长度在111和MMM之间(含),称为好序列。
好序列的分数定义为XXX的正除数,其中XXX是序列中各元素的乘积。
好序列有∑k=1NMk\displaystyle\sum_{k=1}^{N}M^kk=1∑NMk个。求所有这些序列的分数之和对998244353998244353998244353取模的结果。
分析:
m≤16m≤16m≤16,先找出所有质数2,3,5,7,11,132,3,5,7,11,132,3,5,7,11,13,一共有666个,考虑状压也只有646464。
进一步发现nnn巨大,但是状压状态很少,可以想到矩阵快速幂。考虑怎么构造原DPDPDP序列和矩阵进行转移:
首先要求对于每个长度求和所以肯定要设一维来统计前缀和。
其次我们写出转移,并构造转移矩阵:发现如果设“某一状态为某一质数有没有”无法转移,所以我们先令第i(0≤i<6)i(0≤i<6)i(0≤i<6)个质数的编号为iii,个数为aia_iai(ai=0a_i=0ai=0表示没有这个质数),则显然一个序列的答案为(a0+1)(a1+1)(a2+1)(a3+1)(a4+1)(a5+1)(a_0+1)(a_1+1)(a_2+1)(a_3+1)(a_4+1)(a_5+1)(a0+1)(a1+1)(a2+1)(a3+1)(a4+1)(a5+1)而如果我们在序列末尾加入一个666则答案就变成(a0+2)(a1+2)(a2+1)(a3+1)(a4+1)(a5+1)(a_0+2)(a_1+2)(a_2+1)(a_3+1)(a_4+1)(a_5+1)(a0+2)(a1+2)(a2+1)(a3+1)(a4+1)(a5+1)。
根据c(a+x)(b+y)=c(ab+ay+bx+xy)c(a+x)(b+y)=c(ab+ay+bx+xy)c(a+x)(b+y)=c(ab+ay+bx+xy),设c=(a2+1)(a3+1)(a4+1)(a5+1)c=(a_2+1)(a_3+1)(a_4+1)(a_5+1)c=(a2+1)(a3+1)(a4+1)(a5+1)原式子即为:(a0+2)(a1+2)c=(a0+1)(a1+1)c+(a0+1)c+(a1+1)c+c(a_0+2)(a_1+2)c=(a_0+1)(a_1+1)c+(a_0+1)c+(a_1+1)c+c(a0+2)(a1+2)c=(a0+1)(a1+1)c+(a0+1)c+(a1+1)c+c
设某一状态fSf_SfS表示(ai+1)(aj+1)(ak+1)⋯(al+1),(i,j,k,…,l∈S)(a_i+1)(a_j+1)(a_k+1)⋯(a_l+1),(i,j,k,…,l\in S)(ai+1)(aj+1)(ak+1)⋯(al+1),(i,j,k,…,l∈S)的和。
那么转移过程就变成我们枚举每一位和[1,m][1,m][1,m]间的每一位数字,来看看对于原序列的系数是多少,然后给转移矩阵增加即可,因为一个数字最多有两个不同的质数,找两个变量记录一下即可。
式子形如:fS=fS+fS−i+fS−j+fS−i−jf_S=f_S+f_{S−{i}}+f_{S−{j}}+f_{S−{i}−{j}}fS=fS+fS−i+fS−j+fS−i−j。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 66;
const LL mod = 998244353;
struct Matrix {
LL n, m, num[N][N];
Matrix() {
n = m = 0;
memset(num, 0, sizeof(num));
}
};
Matrix operator*(const Matrix &x, const Matrix &y) {
Matrix c;
c.n = x.n;
c.m = y.m;
for (LL i = 0; i < x.n; i++)
for (LL j = 0; j < y.m; j++)
for (LL k = 0; k < x.m; k++)
(c.num[i][j] += x.num[i][k] * y.num[k][j] % mod) %= mod;
return c;
}
Matrix ksm(Matrix a, LL b) {
Matrix t;
t.n = t.m = a.n;
for (LL i = 0; i < a.n; i++)t.num[i][i] = 1;
for (; b; b >>= 1, a = a * a)if (b & 1)t = t * a;
return t;
}
LL n, m;
LL id[17];
Matrix a, b;
int main() {
id[2] = 1;
id[3] = 2;
id[5] = 3;
id[7] = 4;
id[11] = 5;
id[13] = 6;
cin >> n >> m;
a.n = 1, a.m = 65;
for (LL i = 0; i < 64; i++)
a.num[0][i] = 1;
b.n = b.m = 65;
b.num[64][64] = b.num[63][64] = 1;
for (LL i = 0; i < 64; i++)
for (LL k = 1; k <= m; k++) {
LL t = k, t1 = -1, t2 = -1, s1 = 0, s2 = 0;
for (LL l = 2; l <= t; l++)
if (t % l == 0) {
if (~t1) {
t2 = id[l] - 1;
while (t % l == 0)t /= l, s2++;
} else {
t1 = id[l] - 1;
while (t % l == 0)t /= l, s1++;
}
}
b.num[i][i]++;
if (t1 >= 0 && (i >> t1 & 1))b.num[i ^ (1 << t1)][i] += s1;
if (t2 >= 0 && (i >> t2 & 1))b.num[i ^ (1 << t2)][i] += s2;
if (t1 >= 0 && (i >> t1 & 1) && t2 >= 0 && (i >> t2 & 1))b.num[i ^ (1 << t1) ^ (1 << t2)][i] += s1 * s2;
}
a = a * ksm(b, n + 1);
cout << a.num[0][64] - 1 << endl;
return 0;
}
D.Increment Decrement Again(思维)
题意:
没有两个相邻元素相同的整数序列称为良好序列。
给你两个长度为NNN的良好序列:A=(A1,A2,…,AN)A=(A_1,A_2,\dots,A_N)A=(A1,A2,…,AN)和B=(B1,B2,…,BN)B=(B_1,B_2,\dots,B_N)B=(B1,B2,…,BN)。AAA和BBB中的每个元素都介于000和M−1M-1M−1之间。
您可以对AAA执行以下任意次数的运算,可能为零次:
- 在111和NNN之间选择一个整数iii,并执行以下操作之一:
- 设Ai←(Ai+1) mod MA_i\leftarrow(A_i+1)\bmod MAi←(Ai+1)modM。
- 设Ai←(Ai−1) mod MA_i\leftarrow(A_i-1)\bmod MAi←(Ai−1)modM。此处为(−1) mod M=M−1(-1)\bmod M=M-1(−1)modM=M−1。
但是,不能进行使AAA不再是一个好序列的操作。
判断是否有可能使AAA等于BBB,如果有可能,求这样做所需的最小运算次数。
分析:
先特判m=2m=2m=2。
然后我们看作AAA不对mmm取模,要求变为∀i<n\forall i\lt n∀i<n,∣ai−ai+1∣<m∧ai≠ai+1|a_i−a_{i+1}|\lt m∧a_i≠a_{i+1}∣ai−ai+1∣<m∧ai=ai+1。
可以发现aaa的大小关系是不会变的,所以如果固定了a1′a^′_1a1′,则后面的都是固定的。可以先令a1′=b1a^′_1=b_1a1′=b1,根据大小关系求出任意一组合法的a′a^′a′,接下来问题变为最小化∑i=1n∣ai−ai′−km∣\sum\limits^n_{i=1}|a_i−a^′_i−km|i=1∑n∣ai−ai′−km∣,可以二分类似找中位数的方法解决。复杂度O(nlognlogV)O(nlognlogV)O(nlognlogV)。
代码:
#include<bits/stdc++.h>
typedef long long LL;
const LL inf = 0x3f3f3f3f;
using namespace std;
LL n, m, L, R, mid, p, a[200010], b[200010], c[200010];
int main() {
cin >> n >> m;
for (LL i = 1; i <= n; ++i)
cin >> a[i];
for (LL i = 1; i <= n; ++i)
cin >> b[i];
if (m == 2) {
if (a[1] == b[1])
cout << "0" << endl;
else
cout << "-1" << endl;
return 0;
}
c[1] = b[1];
for (LL i = 2; i <= n; ++i) {
LL val = (b[i] - b[i - 1] + m) % m;
if (a[i] > a[i - 1])
c[i] = c[i - 1] + val;
else
c[i] = c[i - 1] + val - m;
}
for (LL i = 1; i <= n; ++i)
c[i] = a[i] - c[i];
sort(c + 1, c + 1 + n);
L = -1000 * inf;
R = 1000 * inf;
while (L < R) {
mid = L + ((R - L + 1) >> 1);
p = lower_bound(c + 1, c + 1 + n, mid * m) - c;
if (p <= (n + 1) / 2)
L = mid;
else
R = mid - 1;
}
LL ans = 0, s = 0;
for (LL i = 1; i <= n; ++i)
ans += abs(c[i] - L * m);
for (LL i = 1; i <= n; ++i)
s += abs(c[i] - (L + 1) * m);
cout << min(ans, s) << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。