|
|
Pro4250 时空跳跃 题解$n\le 10$暴搜,时间复杂度 $O(n!m)$,期望得分 $20$。 $n\le 300$令 $p_u$ 表示 $u$ 的父亲节点。根据 $l_i\le r_i<i$,不难有 $p_u<u$。 设 $u<v$,则 $p_v$ 一定在 $(u,v)$ 的路径上。 考虑证明:若 $p_v$ 不在 $(u,v)$ 路径上,当且仅当 $v$ 时 $u$ 的祖先,根据 $p_u<u$ 可以得到 $u$ 的祖先编号小于 $u$,即 $u>v$,与 $u<v$ 矛盾,故原命题得证。 以上完成本题最为关键的结论证明。 若对于每一次加操作,都暴力完成,复杂度显然是指数级。 不过对于每一种加操作,都可以用 $(u,v)$ 表示(下文均假设 $u<v$)且 $(u,v)$ 的加操作可以转化为 $(u,p_v)$ 的加操作。 若对链 $(a,b)$ 的加操作对链 $(c,d)$ 的加操作有转化,必须满足 $b<d$。且对于 $a\ne b$ 的情况,对链 $(a,c)$ 的加操作和链 $(b,c)$ 的加操作之间不会互相转化。 也就是说,我们以第二维按照 $v$ 划分所有链,可以分成若干层,当前层只会影响这层之前的层,且层内部之间不会影响。 可以设计出一个递推状物,设 $g_{u,v}$ 表示所有操作对 $(u,v)$ 这条链加上的期望值,则有:
$${g_{u,p_v}\gets g_{u,p_v}+\frac{g_{u,v}}{r_v-l_v+1}}, u<p_u $$ $${g_{p_{v},u} \gets g_{u,p_v}+\frac{g_{u,v}}{r_v-l_v+1}}, u>p_u$$
注意到编码过程中,为了方便讨论,我们始终只对 $u<v$ 的状态操作,因此若出现 $p_v<u$ 的情况,要操作 $g_{p_v,u}$ 而不是 $g_{u,p_v}$。 其实是简单排列组合原理,每一层内部的转移因为无所谓,所以第一维的枚举顺序可以随便来。 递推完所有的 $g_{u,v}$ 后,考虑计算点 $u$ 的答案,根据结论,只要 $v<u$,那么 $(v,u)$ 的路径一定包含 $(p_u,u)$ 这条链,且对于任意 $(a,u),(b,u)$ 之间互不影响,说明 $(a,u),(b,u)$ 代表的加操作独立,可以直接加起来。 因此答案为 $\sum_{v<u}g_{v,u}$。直接做需要枚举每个点的父亲,时间复杂度 $O(n^3+m)$。期望得分 $60$。 $n\le 2000$考虑优化这个过程,把 $g$ 数组当成一个二维平面,代码中 $k>i$ 的部分实际上是对横着的一段加上了一个数。$k<i$ 的部分是对竖着的一段加上了一个数。 可以用二维差分优化,递推的过程中求差分的前缀和数组,因为要求前缀和,所以第一维的枚举顺序也需要确定。 时间复杂度 $O(n^2+m)$,期望得分 $100$。
题目4250 时空跳跃
AAAAAAAAAA
4
评论
2026-02-07 15:57:29
|
|
|
脑电波题,对上了就很简单。 $n\le 8$搜索看看吧,说不定常数小就有分了,时间复杂度 $O(?)$,期望得分 $0\sim 30$。 特殊性质 A结论:答案为 $n$,手模不难证明。 特殊性质 B结论:答案为 $1$,一望而知。 $n\le 2000$我们考虑每走过一轮周期位置的偏移。如果走过一轮后仍停留在原地,则答案就是将原树 dfs 一遍的指令数。 否则,我们每走过一个周期,一定会向下走,且会向下走相同的偏移量。假设当前位置是 $x$,走过一个周期后到了 $y$,则我们的指令必须包括 $x$ 的子树除去 $y$ 的子树的这个连通块。 可以枚举从根节点走一个周期偏移多少,然后从根节点开始,初始令 $x=1$,$y$ 为 $x$ 走制定偏移量的节点,将 $x$ 的子树除去 $y$ 的子树的这个连通块提出来,然后令 $x\gets y$ 重复这个过程,直到 $y$ 在给定的二叉树之外,此时提出的连通块就是 $x$ 的子树。 将提出的所有连通块并在一起,则我们的指令需要 dfs 这个连通块,剩下的随便做即可,复杂度 $O(n^2)$。
题目4210 Sayaku,移动
WAAAWAWWAW
3
评论
2026-02-07 15:56:05
|
|
|
特殊性质模拟即可,时间复杂度 $O(\sum |s_i|)$,期望得分 $10$。 $n\le 12$考虑状态压缩 dp,设 $f_{i,S}$ 表示将集合 $S$ 中的所有串插入到一个深度为 $i$ 的节点的方案数。 使用枚举子集的方式分裂 $S$ 到 $i+1$ 去转移,时间复杂度 $O(3^n|s_i|)$。貌似可以用高维前缀和优化到 $O(2^n|s_i|n)$。期望得分 $50$。 $n\le 20$正解和特殊性质关系不大。个人觉得正解要比特殊性质好写。 不难发现,若确定了每个字符串 $s_i$,则字典树的节点个数为: $$\sum_{S}(-1)^{|S|-1}\text{LCP}(S)$$ 其中 $\text{LCP}(S)$ 表示集合 $S$ 中所有字符串的最长公共前缀。 好的,若 $s_i$ 未确定,如何计算 $\text{LCP}(S)$其实非常简单?枚举最长公共前缀的长度 $i$,只需要保障前 $i$ 位相同,第 $i+1$ 位不相同即可,时间复杂度为 $O(2^n|s_i|)$,期望得分 $100$。
题目4299 学姐的下午茶
AAAAAAAAAA
4
评论
2026-02-07 15:38:59
|
|
|
Pro4184 轻重数字 题解[CCO 2024] Heavy Light Decomposition前置知识分块,DP 简要题意定义“好的数组”为一个数组内交替出现“轻元素”和“重元素”,轻元素即其在这个数组内是唯一的,重元素即其在数组内出现多次。 有 $n$ 个正整数 $a_i$,求其有多少种划分方案能使划分后的子数组均为好的数组。 分析样例我们首先要搞懂一个东西,就是划分后好的数组,是指这个子数组是好的,在这个子数组内的轻重元素与原数组并没有关系,每个子数组是互相独立的。 对于样例一,其划分方案如下: - $[1], [2], [3], [2], [3]$ - $[1], [2, 3, 2], [3]$ - $[1], [2], [3, 2, 3]$ - $[1, 2, 3, 2], [3]$ 对于样例二,其划分方案如下: - $[1], [2], [1], [3], [1]$ - $[1, 2, 1], [3], [1]$ - $[1, 2, 1, 3], [1]$ - $[1], [2], [1, 3, 1]$ - $[1], [2, 1, 3, 1]$ - $[1, 2, 1, 3, 1]$ 不明白的建议手推一下。 思路分析考虑转移我们定义 $dp[i]$ 为前 $i$ 个元素的合法划分的方案数。 那么,转移方程很显然:$dp[i] = \sum dp[j]$,其中 $j < i$ 且 子数组 $[j + 1, i]$ 是好数组。 实际上含义就是在 $j$ 处划分,新增一个子数组 $[j + 1, i]$,方案累加前 $j$ 个元素的方案数。 考虑好数组的约束如果 $[j + 1, i]$ 为好的数组,那么需要满足: 1. 类型交替:即数组内的元素轻重交替。 2. 奇偶性约束:如果重元素第一次出现在奇数位,那么奇数位全是重元素,反之。 因此我们如果直接枚举所有的 $j$ 去验证 $[j + 1, i]$ 是否为好数组,时间复杂度为 $O(n ^ 2)$。
考虑优化对于当前的位置 $i$,我们设其元素大小为 $v$,用 $odd[v]$ 和 $even[v]$ 来记录 $v$ 在奇数和偶数位最近的出现位置,这样的话可以确定 $j$ 的下界。 为了保证子数组 $[j + 1, i]$ 满足类型交替,需要避免 $v$ 元素在数组内出现奇偶性冲突,那么若 $j + 1 < \min(odd[v], even[v])$ 的话,则会使其冲突。 因此我们使 $minL$ 取所有元素 $min(odd[v], even[v]) + 1$ 的最大值,因此 $j > minL - 1$。 然后是最重要的分块,我们将原数组分块,每个块维护两个核心内容,$sum[k][b]$ 表示 $b$ 块满足在奇偶性 $k$ 下的合法的 $dp[j]$ 之和,$kpos[k][b]$ 是在 $k$ 的奇偶性下块 $b$ 是否满足。另外,为了维护分块时的单个元素, 我们维护 $pos[k][i]$ 是单个位置的 $i$ 是否满足奇偶性 $k$。 当我们查询 $[l, r]$ 内符合条件的 $dp[j]$ 之和时,对于整块,只需要判断 $kpos$ 是否有效,然后累加 $sum$ 即可,对于单个的块边缘的元素,则需要满足 $kpos$ 和 $pos$,有效则累加 $dp[j]$。 在区间更新时,只需要标记区间有效和无效,在完整的块上更新 $kpos$,零散的元素更新 $pos$ 和 $sum$。 时间复杂度分块的单次查询和更新的时间复杂度为 $O(\sqrt{n})$,时间复杂度为 $O(n \sqrt n)$。 简单卡常即可,最慢的点才两秒出头,对于四秒的时间限制完全够用。 代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<unordered_map>
#include<cstring>
using namespace std;
const int MOD = 1e6 + 3;
const int MAXN = 5 * 1e5 + 5;
int kpos[2][MAXN], sum[2][MAXN], pos[2][MAXN];
int L[MAXN], R[MAXN], id[MAXN];
int dp[MAXN], even[MAXN], odd[MAXN];
int a[MAXN], pre[MAXN];
pair<int, int> lst[MAXN];
int n, tot = 0, B;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
x = x * 10 + ch - '0', ch = getchar();
return x * f;
}
inline void update(int k, int l, int r, int x){
if(l > r) return;
int kl = id[l], kr = id[r];
if(kl != kr){
for(int i = kl + 1;i < kr;++ i){
kpos[k][i] += x;
}
if(l == L[kl]){
kpos[k][kl] += x;
}
else{
for(int i = l;i <= R[kl];++ i){
if(pos[k][i]){
sum[k][kl] = (sum[k][kl] + dp[i - 1]) % MOD;
}
pos[k][i] += x;
if(pos[k][i]){
sum[k][kl] = (sum[k][kl] - dp[i - 1] + MOD) % MOD;
}
}
}
if(r == R[kr]){
kpos[k][kr] += x;
}
else{
for(int i = L[kr];i <= r;++ i){
if(pos[k][i]){
sum[k][kr] = (sum[k][kr] + dp[i - 1]) % MOD;
}
pos[k][i] += x;
if(pos[k][i]){
sum[k][kr] = (sum[k][kr] - dp[i - 1] + MOD) % MOD;
}
}
}
}
else{
if(l == L[kl] && r == R[kl]){
kpos[k][kl] += x;
}
else {
for(int i = l;i <= r;++ i){
if(pos[k][i]){
sum[k][kl] = (sum[k][kl] + dp[i - 1]) % MOD;
}
pos[k][i] += x;
if(pos[k][i]){
sum[k][kl] = (sum[k][kl] - dp[i - 1] + MOD) % MOD;
}
}
}
}
}
inline int query(int k, int l, int r){
if(l > r) return 0;
int res = 0;
int kl = id[l], kr = id[r];
if(kl != kr){
for(int i = kl + 1;i < kr;++ i){
if(!kpos[k][i]){
res = (res + sum[k][i]) % MOD;
}
}
if(l == L[kl]){
if(!kpos[k][kl]){
res = (res + sum[k][kl]) % MOD;
}
}
else{
for(int i = l; i <= R[kl]; i++){
if(!pos[k][i] && !kpos[k][kl]){
res = (res + dp[i - 1]) % MOD;
}
}
}
if(r == R[kr]){
if(!kpos[k][kr]){
res = (res + sum[k][kr]) % MOD;
}
}
else{
for(int i = L[kr];i <= r;++ i){
if (!pos[k][i] && !kpos[k][kr]){
res = (res + dp[i - 1]) % MOD;
}
}
}
}
else{
if(l == L[kl] && r == R[kl]){
if(!kpos[k][kl]){
res = (res + sum[k][kl]) % MOD;
}
}
else{
for(int i = l;i <= r;++ i){
if (!pos[k][i] && !kpos[k][kl]){
res = (res + dp[i - 1]) % MOD;
}
}
}
}
return res;
}
int main(){
freopen("digit.in", "r", stdin);
freopen("digit.out", "w", stdout);
n = read();
B = 300;
for(int i = 1; i <= n; i += B){
L[++ tot] = i;
R[tot] = min(n, i + B - 1);
for(int j = i;j <= R[tot];++ j){
id[j] = tot;
}
}
// for(int i = 1;i <= n;++ i){
// cout << i << ' ' << id[i] << '\n;'
// }
dp[0] = 1;
for(int i = 1;i <= n;++ i) a[i] = read();
int leven = 0, lodd = 0, minL = 1;
memset(lst, -1, sizeof(lst));
for(int i = 1;i <= n;++ i){
sum[0][id[i]] = (sum[0][id[i]] + dp[i - 1]) % MOD;
sum[1][id[i]] = (sum[1][id[i]] + dp[i - 1]) % MOD;
dp[i] = dp[i - 1];
int v = a[i];
if(lst[v].second != -1){
update(lst[v].second & 1, lst[v].first + 1, lst[v].second, -1);
}
if(i & 1){
lodd = max(lodd, odd[v]);
odd[v] = i;
}
else{
leven = max(leven, even[v]);
even[v] = i;
}
lst[v] = {pre[v], i};
update(lst[v].second & 1, lst[v].first + 1, lst[v].second, 1);
pre[v] = i;
minL = max(minL, min(odd[v], even[v]) + 1);
if(leven < lodd){
dp[i] = (dp[i] + query(1, max(minL, leven + 1), i - 1)) % MOD;
}
else if (lodd < leven){
dp[i] = (dp[i] + query(0, max(minL, lodd + 1), i - 1)) % MOD;
}
}
printf("%d", dp[n] % MOD);
return 0;
}
题目4184 轻重数字
9
2 条 评论
2026-02-04 20:54:13
|
|
|
Pro3996 勇者 题解更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19349977
思路由于题目的数据范围,我们可以得知,大概是个 $n ^ 3$ 的玩意。 但是我们需要仔细想想怎么计数? 首先,这个玩意和什么有关? 第一肯定是步数,第二是点是否走过,第三是这个点和为我们要的强连通图的关系。 然后我们考虑最难的问题,就是这个强连通图怎么判断,怎么累加答案?我们走到一个点的时候怎么判断其是否能与原来的形成一个强连通呢?还是说其目前自己和其他点成强连通,需要后续的操作去整。 不难发现,其实一个点,其强连通关系只有两种,第一种就是和起点在一个强连通里面,第二种,就是不在起点的强连通里面。 我们定义 $f_{i, j, k}$ 表示目前已经走了 $i$ 步,已经访问了 $j$ 个点,与起点 $1$ 形成的强连通的大小为 $k$。(以下所说的形成强连通均是与 $1$ 形成) 考虑转移。 首先走到点,第一种情况,是没有走过,于是我们有: $f_{i + 1, j + 1, k} \to f_{i + 1,j + 1, k} + f_{i, j, k} \times (n - j)$ 第二种,是走过,但是没有形成强连通的点: $f_{i + 1, j, k} \to f_{i + 1, j, k} + f_{i, j, k} \times (j - k)$ 第三种是,走过,且已经形成了强连通: $f_{i + 1, j, k} \to f_{i + 1, j, k} + f_{i, j, k} \times k$ 于是我们的答案就在 $f_{m, n, n}$。
代码
#include<iostream>
using namespace std;
#define int long long
const int MAXN = 305;
const int MOD = 1e9 + 7;
//f[i][j][k] 走了 i 步,访问了 j 个点,以 1 为根的 scc 大小为 k
int f[MAXN][MAXN][MAXN];
int n, m;
signed main(){
freopen("rotk.in", "r", stdin);
freopen("rotk.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f[0][1][1] = 1;
for(int i = 0;i <= m - 1;i ++){
for(int j = 1;j <= n;j ++){
for(int k = 1;k <= n;k ++){
if(!f[i][j][k]) continue;
//没走的
f[i + 1][j + 1][k] += f[i][j][k] * (n - j) % MOD;
f[i + 1][j + 1][k] %= MOD;
//走过,但不在 1 的scc
f[i + 1][j][k] += f[i][j][k] * (j - k) % MOD;
f[i + 1][j][k] %= MOD;
//走过,在 1 的 scc
f[i + 1][j][j] += f[i][j][k] * k % MOD;
f[i + 1][j][j] %= MOD;
}
}
}
cout << f[m][n][n] % MOD << '\n';
return 0;
}
题目3996 勇者
AAAAAAAAAA
8
评论
2026-02-04 20:51:46
|
|
|
更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19354572
思路给定一棵有 $n$ 个节点的树,巡警车从 $1$ 号节点出发遍历所有边后返回,每条边需经过两次,总距离为 $2*(n-1)$。现可新建 $K$ 条边$(k = \{ 1, 2\})$,要求新建的每条边必须恰好经过一次,求规划新建边的方案,使得巡警车的总巡逻距离最小,并输出该最小值。 我们可以发现,对于 $k = 1$ 的情况,显然你求出一条直径,再把直径的两个端点连上,这样是最优的。 但是当 $k = 2$ 的时候,我们需要再连一次,但是这样的话,我们最好选比较长的,最初的想法是选次长的直径,但是我们想想,如果你选的这条新的直径,和原来的直径,有很多重合的点,那么就不是很优,我们为了处理这个重复的玩意,其实可以把原直径的所有边赋为 $-1$,这样的话,我们再找最长的直径,就不会出问题了。 但是这样需要知道一个问题, dfs 求直径是无法解决边权为负的情况,于是我们考虑树形 dp 的方式,直接再记一次直径的长度即可。 设两次的直径长度为 $L_1, L_2$,则最终答案为: $k = 1$ 时:$2 * (n - 1) - L_1 + 1$ $k = 2$ 时:$2 * (n - 1) - (L_1 - 1) - (L_2 - 1)$
代码
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e5 + 5;
struct node{
int to, nxt, len;
}e[MAXN * 2];
int tot = 0, h[MAXN];
int d[MAXN];
bool vis[MAXN];
void add(int x, int y, int len){
e[++ tot] = {y, h[x], len};
h[x] = tot;
}
int n, k, d1 = 1, d2 = 0, d3 = 1, d4 = 0;
int f[MAXN];
int dp[MAXN], maxx;
void dfs(int u, int fa){
dp[u] = 0;
for(int i = h[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa) continue;
e[i].len = (vis[u] && vis[v]) ? -1 : 1;
dfs(v, u);
maxx = max(maxx, dp[u] + dp[v] + e[i].len);
dp[u] = max(dp[u], dp[v] + e[i].len);
}
}
void dfs1(int u, int fa){
for(int i = h[u];i;i = e[i].nxt){
int v = e[i].to;
if(v == fa) continue;
d[v] = d[u] + e[i].len;
if(d[v] > d[d1]){
d1 = v;
}
dfs1(v, u);
}
}
void dfs2(int u, int fa){
f[u] = fa;
for(int i = h[u];i;i = e[i].nxt){
int v = e[i].to;
if(v == fa) continue;
d[v] = d[u] + e[i].len;
if(d[v] > d[d2]){
d2 = v;
}
dfs2(v, u);
}
}
int main(){
freopen("xunluo.in", "r", stdin);
freopen("xunluo.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> k;
for(int i = 1;i < n;i ++){
int a, b; cin >> a >> b;
add(a, b, 1), add(b, a, 1);
}
dfs1(1, 0);
memset(d, 0, sizeof(d));
dfs2(d1, 0);
int l1 = d[d2];
if(k == 1){
cout << 2 * (n - 1) - l1 + 1 << '\n';
return 0;
}
memset(vis, 0, sizeof(vis));
int tmp = d2;
while(tmp != 0){
vis[tmp] = true;
tmp = f[tmp];
}
maxx = 0;
dfs(1, 0);
int l2 = maxx;
cout << 2 * (n - 1) - (l1 - 1) - (l2 - 1) << '\n';
return 0;
}
题目3483 [APIO 2010]巡逻
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
8
评论
2026-02-04 20:50:35
|