A.Please Sign(图论)
题意:
给定长度为nnn的数组aaa,长度为n−1n-1n−1的数组ppp,现在执行无数次以下操作:
- 对于2≤i≤n2 \le i \le n2≤i≤n ,按顺序执行api=api+aia_{p_i}=a_{p_i}+a_iapi=api+ai。
确定最后a1a_1a1是正数还是负数还是000。
分析:
考虑所有iii向pip_ipi建边,发现是一棵以111为根的树,再计算每个节点距离111的深度,同一深度的权重相同,因此需将每个深度的节点的权值和都相加,如果权值和为000 ,说明这一深度层无用,否则最终答案的正负取决于最深深度节点的权值和。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2.5e5 + 5;
const int inf = 0x3f3f3f3f;
LL n, a[N], p[N], f[N], dep[N], mx;
vector<int> e[N];
void dfs(int u) {
mx = max(mx, dep[u]);
for (auto v: e[u]) {
dep[v] = dep[u] + 1;
dfs(v);
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 2; i <= n; i++)
cin >> p[i], e[p[i]].push_back(i);
dfs(1);
for (int i = 2; i <= n; i++)
f[dep[i]] += a[i];
int flag = 0;
for (int i = n; i; i--)
if (f[i]) {
flag = f[i] > 0 ? 1 : -1;
break;
}
a[1] = flag * inf + a[1];
if (a[1] > 0)
cout << "+";
else if (a[1] == 0)
cout << "0";
else
cout << "-";
return 0;
}
B.Subsegments with Small Sums(思维)
题意:
给出正整数sss,和数列aaa,记f(a)f(a)f(a)表示:将序列分成xxx块子序列,并保证每一块子序列的和都小于等于sss。记f(a)f(a)f(a)为其中最小的xxx。
现在给定长度为nnn的数列aaa,求 ∑1≤l≤r≤nf((al,al+1,⋯,ar))∑1≤l≤r≤nf((a_l,a_{l+1},⋯,a_r))∑1≤l≤r≤nf((al,al+1,⋯,ar)).
分析:
考虑贪心,从左往右考虑这个序列,如果上一段能放下就放进上一段,否则新开一段。再考虑左端点相同时每个右端点的答案,设这个和为 flf_lfl。分为两种情况:
- 只有一段,对答案的贡献为111。
- 有至少两段,但根据上面的贪心,它们第一段结束的位置均相同。设这个位置是xxx,那么满足r≥xr \ge xr≥x的区间可以看作[x,r][x,r][x,r]答案+1+1+1,即fl=fx+(n−l+1)f_l=f_x+(n-l+1)fl=fx+(n−l+1)。
求和的ans=∑i=1i=nfians=\sum\limits_{i=1}^{i=n}f_ians=i=1∑i=nfi。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const int inf = 0x3f3f3f3f;
LL a[N], sum[N], f[N];
int main() {
LL n, s;
cin >> n >> s;
for (int i = 1; i <= n; i++)
cin >> a[i], sum[i] = sum[i - 1] + a[i];
for (int i = n; i; i--) {
int nxt = lower_bound(sum + 1, sum + n + 1, sum[i - 1] + s + 1) - sum;
f[i] = f[nxt] + (n - i + 1);
}
LL ans = 0;
for (int i = 1; i <= n; i++)
ans += f[i];
cout << ans << endl;
return 0;
}
C.Not So Consecutive(dp)
题意:
给出长度为nnn的序列aaa,定义一个序列aaa是好的,要求:
- 1≤ai≤n≤50001 \le a_i \le n \le 50001≤ai≤n≤5000
- 序列aaa中没有任何一个数字xxx的连续出现次数>x>x>x。
现在给定一个长度为nnn的序列aaa,序列中的元素不是−1-1−1就是1−n1-n1−n中的任意一个数,请将所有的−1-1−1替换成1−n1-n1−n的任意数字,计算最后结果有多少不同的序列aaa是好的,并将结果对998244353998244353998244353进行取模。
分析:
f[i][j]f[i][j]f[i][j]表示填了前iii个位置,第jjj个位置填的数字是jjj的方案数。枚举这个连续段的起点,得到转移为:
fi,j=∑k=max(0,i−j)i−1[∀l∈[k+1,i],Al=−1orAl=j]∑col≠jfk,colf_{i,j}= \sum\limits_{k=max(0,i-j)}^{i-1}[∀l∈[k+1,i],A_l=−1 or A_l=j]\sum\limits_{col\neq j}f_{k,col}fi,j=k=max(0,i−j)∑i−1[∀l∈[k+1,i],Al=−1orAl=j]col=j∑fk,col。
方括号里的式子是为了找iii前面最后一个填了不为jjj 的数的位置,设这个位置为 lstlstlst。对每个数维护 posjpos_jposj 表示数jjj当前最后一次出现的位置。那么对于每个新的iii,我们记录 pospospos 的最大值和次大值。
同时将col≠jcol \neq jcol=j这个条件容斥掉,得到最后转移方程为:
fi,j=∑k=lsti−1∑col=1nfk,col−∑k=lsti−1fk,jf_{i,j}=\sum\limits_{k=lst}^{i-1} \sum\limits_{col=1}^{n} f_{k,col}-\sum\limits_{k=lst}^{i-1}f_{k,j}fi,j=k=lst∑i−1col=1∑nfk,col−k=lst∑i−1fk,j
记si,j=∑k=1ifk,js_{i,j}=\sum\limits_{k=1}^{i}f_{k,j}si,j=k=1∑ifk,j,sumi=∑j=1i∑col=1nfi,colsum_i=\sum\limits_{j=1}^{i} \sum\limits_{col=1}^{n}f_{i,col}sumi=j=1∑icol=1∑nfi,col
前缀和优化,n2n^2n2转移即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e3 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
int pos[N], f[N][N], s[N][N], sum[N], n, a[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
f[0][0] = 1, sum[0] = 1;
for (int i = 1; i <= n; i++) {
if (a[i] != -1)
pos[a[i]] = i;
int mx1 = 0, mx2 = 0;
for (int j = 1; j <= n; j++) {
if (pos[j] > mx1)
mx2 = mx1, mx1 = pos[j];
else if (pos[j] > mx2)
mx2 = pos[j];
}
for (int j = 1; j <= n; j++) {
int lst = max(i - j, (a[mx1] == j) ? mx2 : mx1);
f[i][j] = sum[i - 1] - (lst ? sum[lst - 1] : 0) - (s[i - 1][j] - (lst ? s[lst - 1][j] : 0));
f[i][j] = (f[i][j] % mod + mod) % mod;
s[i][j] = (s[i - 1][j] + f[i][j]) % mod;
(sum[i] += f[i][j]) %= mod;
}
(sum[i] += sum[i - 1]) %= mod;
}
cout << (sum[n] - sum[n - 1] + mod) % mod << endl;
return 0;
}
D Add to Make a Permutation(思维)
题意:
给你一个序列aaa,0≤ai≤n−10 \le a_i \le n-10≤ai≤n−1,你可以进行任意次以下操作:
- 任意选择mmm个元素,把他们的值增加111并对nnn取模。
询问能否通过任意次操作使得aaa变成一个0−n−10-n-10−n−1的排列。
分析:
设序列 aaa 操作得到未被取模的最终序列 bbb
bbb一定满足以下条件:
-
bi≤aib_i \le a_ibi≤ai
-
bi%nb_i \% nbi%n两两不同
-
令s=∑i=1n(bi−ai)s=\sum\limits_{i=1}^{n}(b_i-a_i)s=i=1∑n(bi−ai),有s%m=0s\%m=0s%m=0
-
max(bi−ai)≤smmax({b_i−a_i}) \le \frac{s}{m}max(bi−ai)≤ms
在一定的操作次数下max(b∗i−ai)max({b*i−a_i})max(b∗i−ai) 越小越容易满足条件。将aaa序列升序排序,最优的对应关系一定是将bbb也升序排序。
发现答案只和sss 的值有关,有结论:若有解,一定存在一种最优方案形如 b=(x,x+1,⋯,x+n)b=(x,x+1,⋯,x+n)b=(x,x+1,⋯,x+n)
证明:假设我们有一个最优的 bbb 序列,且 bn−b1b_n-b_1bn−b1>n。那么令 b1←bn−nb_1←b_n−nb1←bn−n,bn←b1+nb_n←b_1+nbn←b1+n,依次对照上面的所有条件:
-
因为 bn−n>b1b_n−n>b_1bn−n>b1,新的 b1b_1b1 比原来大。同时因为 b1>a1b_1>a_1b1>a1 , an−a1<na_n − a_1 < nan−a1<n,有 b1+n>anb_1+n>a_nb1+n>an,满足条件111
-
加减nnn不改变取模后的值。
-
总和没变,S 不变。
-
bn−anb_n-a_nbn−an 一定变小。前者有 bn−an>bn−n−a1b_n-a_n>b_n-n-a_1bn−an>bn−n−a1,所以也变小,满足条件。
那么我们只需考虑最小化xxx的值。根据第一个条件可以得到xxx的下界。第四个条件xxx每增加 1,不等式左侧增加111,右侧增加nm\frac{n}{m}mn。因为 n>mn>mn>m,第四个条件也为xxx提供了下界。
接下来令 xxx满足第三个条件即可,发现 xxx 和 x+nx+nx+n 是等价的,xxx 可以直接枚举nnn次。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const int mod = 998244353;
LL n, m, a[N], sum, x;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
x = max(x, a[i] - i + 1);
for (int i = 1; i <= n; i++)
sum += (x + i - 1) - a[i];
bool flag = 0;
for (int i = 0; i <= n; i++)
if ((sum + n * i) % m == 0) {
x += i, sum += n * i, flag = 1;
break;
}
if (!flag) {
cout << -1 << endl;
return 0;
}
LL mx = 0;
for (int i = 1; i <= n; i++)
mx = max(mx, x + i - 1 - a[i]);
while (mx > sum / m)
mx += m / __gcd(n, m), sum += n * m / __gcd(n, m);
cout << sum / m << endl;
return 0;
}
E Avoid Boring Matches(思维)
题意:
有2n2^n2n个人,参加一项锦标赛。规则如下:
- 每个人都有一顶颜色为红色或者蓝色的帽子,由字符串SSS给出。
- 重复以下操作,直到只剩下一个参与者:假设2k2k2k是当前的参与者人数。将参与者分成两组。可以自由选择如何配对它们。 然后,每组举行一场比赛,赢家留下来,输家离开比赛。 参与者按强度降序编号,因此编号较小的参与者总是获胜。
两个戴着红帽子的参与者之间的比赛称为无聊的比赛。 你的目标是安排配对,这样在比赛期间就不会发生无聊的比赛。
现在你可以执行任意次以下操作:
- 选择SSS中相邻的两个字符并交换他们。
询问是否有可能实现目标。 如果是,请查找所需的最小操作数。
分析:
RRR 比 BBB 多一定无解,否则可以换成 BBBB...RRRBBBB...RRRBBBB...RRR的形式,一定有解。
先找判断序列合法的充要条件。考虑每轮的最优匹配策略,我们希望更多的 BBB能留到下一轮,因此要尽可能地让 BBB 和它后面的 RRR 配对。
在多种配对方案能保留的 $B $数量相同时,我们希望留下的 $B $位置尽可能靠前,因为这样在下下轮它们被保留下来的概率更大。因此我们得到这样的贪心策略:从左到右考虑每个 BBB,将它和右边第一个未被配对的 RRR配对。最终将所有剩下未配对的数两两配对。根据上文可以知道这样做是最优的。设 tit_iti表示长度为 2i2^i2i,且为所有 BBB尽可能靠右的合法解。
令 t0t_0t0=RRR,考虑从 ti−1t_{i−1}ti−1 得到 tit_iti。从左到右处理 ti−1t_{i−1}ti−1 的每一位,若当前位是 RRR,表示这位没被匹配,ti=ti+Rt_i=t_i+Rti=ti+R;当前位是 BBB,我们希望下一个BBB尽量靠后,即与之匹配的 RRR尽量更近,ti=ti+BRt_i=t_i+BRti=ti+BR。不足 2i2^i2i 位用 BBB 补齐。
设 tnt_ntn的第 jjj 个 BBB 的位置为 TjT_jTj,原序列位置为 SjS_jSj。那么若存在 Sj>TjS_j>T_jSj>Tj,则该序列不合法。证明大概是如果一个 Sj<TjS_j < T_jSj<Tj,匹配数不会变多;但如果Sj>Tj,BRS_j > T_j,BRSj>Tj,BR的匹配数一定减少。
答案是令 SSS 满足上述条件的最小操作次数ans=∑j=12nmax(0,Tj−Sj)ans=\sum\limits_{j=1}^{2^n}max(0,T_j-S_j)ans=j=1∑2nmax(0,Tj−Sj)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = (1 << 18) + 5;
int n;
string s, t[N];
int tot, pos[N];
int main() {
cin >> n;
cin >> s;
t[0] = "R";
for (int i = 1; i <= n; i++) {
for (auto c: t[i - 1])
if (c == 'R')
t[i] += "R";
else
t[i] += "BR";
while (t[i].size() < (1 << i))
t[i] += "B";
}
for (int i = 0; i < (1 << n); i++)
if (t[n][i] == 'B')
pos[++tot] = i;
tot = 0;
long long ans = 0;
for (int i = 0; i < (1 << n); i++)
if (s[i] == 'B') {
tot++;
if (i > pos[tot])
ans += i - pos[tot];
if (tot == (1 << n - 1))
break;
}
if (tot < (1 << n - 1))
cout << -1 << endl;
else
cout << ans << endl;
return 0;
}
学习交流
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

博客围绕多个算法题展开,涵盖图论、思维、dp等类型。详细阐述各题题意,如根据数组操作判断元素正负、序列分割求和等;给出分析思路,像建图计算深度、贪心策略等;还附上对应代码。最后提供学习交流QQ群,方便分享做题思路与技巧。
443

被折叠的 条评论
为什么被折叠?



