CSP-S2022

[CSP-S 2022] 假期计划

题目描述

小熊的地图上有 n n n 个点,其中编号为 1 1 1 的是它的家、编号为 2 , 3 , … , n 2, 3, \ldots, n 2,3,,n 的都是景点。部分点对之间有双向直达的公交线路。如果点 x x x z 1 z_1 z1 z 1 z_1 z1 z 2 z_2 z2、……、 z k − 1 z_{k - 1} zk1 z k z_k zk z k z_k zk y y y 之间均有直达的线路,那么我们称 x x x y y y 之间的行程可转车 k k k 次通达;特别地,如果点 x x x y y y 之间有直达的线路,则称可转车 0 0 0 次通达。

很快就要放假了,小熊计划从家出发去 4 4 4不同的景点游玩,完成 5 5 5 段行程后回家:家 → \to 景点 A → \to 景点 B → \to 景点 C → \to 景点 D → \to 家且每段行程最多转车 k k k 次。转车时经过的点没有任何限制,既可以是家、也可以是景点,还可以重复经过相同的点。例如,在景点 A → \to 景点 B 的这段行程中,转车时经过的点可以是家、也可以是景点 C,还可以是景点 D → \to 家这段行程转车时经过的点。

假设每个景点都有一个分数,请帮小熊规划一个行程,使得小熊访问的四个不同景点的分数之和最大。

输入格式

第一行包含三个正整数 n , m , k n, m, k n,m,k,分别表示地图上点的个数、双向直达的点对数量、每段行程最多的转车次数。

第二行包含 n − 1 n - 1 n1 个正整数,分别表示编号为 2 , 3 , … , n 2, 3, \ldots, n 2,3,,n 的景点的分数。

接下来 m m m 行,每行包含两个正整数 x , y x, y x,y,表示点 x x x y y y 之间有道路直接相连,保证 1 ≤ x , y ≤ n 1 \le x, y \le n 1x,yn,且没有重边,自环。

输出格式

输出一个正整数,表示小熊经过的 4 4 4 个不同景点的分数之和的最大值。

样例 #1

样例输入 #1

8 8 1
9 7 1 8 2 3 6
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 1

样例输出 #1

27

样例 #2

样例输入 #2

7 9 0
1 1 1 2 3 4
1 2
2 3
3 4
1 5
1 6
1 7
5 4
6 4
7 4

样例输出 #2

7

提示

【样例解释 #1】

当计划的行程为 1 → 2 → 3 → 5 → 7 → 1 1 \to 2 \to 3 \to 5 \to 7 \to 1 123571 时, 4 4 4 个景点的分数之和为 9 + 7 + 8 + 3 = 27 9 + 7 + 8 + 3 = 27 9+7+8+3=27,可以证明其为最大值。

行程 1 → 3 → 5 → 7 → 8 → 1 1 \to 3 \to 5 \to 7 \to 8 \to 1 135781 的景点分数之和为 24 24 24、行程 1 → 3 → 2 → 8 → 7 → 1 1 \to 3 \to 2 \to 8 \to 7 \to 1 132871 的景点分数之和为 25 25 25。它们都符合要求,但分数之和不是最大的。

行程 1 → 2 → 3 → 5 → 8 → 1 1 \to 2 \to 3 \to 5 \to 8 \to 1 123581 的景点分数之和为 30 30 30,但其中 5 → 8 5 \to 8 58 至少需要转车 2 2 2 次,因此不符合最多转车 k = 1 k = 1 k=1 次的要求。

行程 1 → 2 → 3 → 2 → 3 → 1 1 \to 2 \to 3 \to 2 \to 3 \to 1 123231 的景点分数之和为 32 32 32,但游玩的并非 4 4 4 个不同的景点,因此也不符合要求。

【样例 #3】

见附件中的 holiday/holiday3.inholiday/holiday3.ans

【数据范围】

对于所有数据,保证 5 ≤ n ≤ 2500 5 \le n \le 2500 5n2500 1 ≤ m ≤ 10000 1 \le m \le 10000 1m10000 0 ≤ k ≤ 100 0 \le k \le 100 0k100,所有景点的分数 1 ≤ s i ≤ 10 18 1 \le s_i \le {10}^{18} 1si1018。保证至少存在一组符合要求的行程。

测试点编号 n ≤ n \le n m ≤ m \le m k ≤ k \le k
1 ∼ 3 1 \sim 3 13 10 10 10 20 20 20 0 0 0
4 ∼ 5 4 \sim 5 45 10 10 10 20 20 20 5 5 5
6 ∼ 8 6 \sim 8 68 20 20 20 50 50 50 100 100 100
9 ∼ 11 9 \sim 11 911 300 300 300 1000 1000 1000 0 0 0
12 ∼ 14 12 \sim 14 1214 300 300 300 1000 1000 1000 100 100 100
15 ∼ 17 15 \sim 17 1517 2500 2500 2500 10000 10000 10000 0 0 0
18 ∼ 20 18 \sim 20 1820 2500 2500 2500 10000 10000 10000 100 100 100

分析

考场上打了1h+1h,亏死

第一个1h: 辨别出家和景区构成一个无向图,其中家为1,从1号开始,每次通过不超过k次转车到达四个景区,求获得分数最大值

首先想最最暴力的:(考场上没打)

dfs(当前点,已经游览过几个景区, 第几次转车,总分数)

然后考虑优化转车:

对每个点求出其k次转车内可到达的点(宽搜,O(n2)), 重新建图

只要最短距离小于等于k+1,就可以通过转车互相到达

再dfs(当前点,已经游览过几个景区,总分数)

以上过程共经历30—40min, 亏就亏在这里:后来花在这道题上的1.5h毫无进步

于是贴出考场代码(考场上只到了这里)

#include <bits/stdc++.h>
using namespace std;
struct node{
	int now, s;
};
long long n, m, k, a[2510], x, y, ans = 0, dis[2510][2510], p[2510][2510], vh[2510];
vector <long long> an[2510], bn[2510];
queue <node> q;
long long read(){
	long long re = 0, ps = 1;
	char ch = getchar();
	for (;(ch < '0' || ch > '9') && ch != '-';ch = getchar());
	if (ch == '-') ps = -1;
	for (;ch < '0' || ch > '9'; ch = getchar());
	for (;ch >= '0' && ch <= '9'; ch = getchar()) re = re * 10 + ch - '0';
	return ps * re;
} 
void dij(int k1){
	while (!q.empty()) q.pop(); 
	node s;
	s.now = k1;
	s.s = 0;
	q.push(s);
	while (!q.empty()){
		node nod = q.front();
		q.pop();
		if (nod.s > k + 1) break;
		if (dis[k1][nod.now] < dis[0][0]) continue;
		dis[k1][nod.now] = nod.s;
		for (int i = 0; i < an[nod.now].size(); ++i){
			int v = an[nod.now][i];
			if (dis[k1][v] == dis[0][0]){
				s.now = v;
				s.s = nod.s + 1;
				q.push(s);
			}
		} 
	}
}

void dfs(int now, int step, long long sum){
	if (step >= 4){
		if (p[now][1])ans = max(ans, sum);
		return;
	}
	for (int i = 0; i < bn[now].size(); ++i){
		if (!vh[bn[now][i]] && bn[now][i] != 1){
			vh[bn[now][i]] = 1;
			dfs(bn[now][i], step + 1, sum + a[bn[now][i]]);
			vh[bn[now][i]] = 0;
		}
	}
}
int main(){
	memset(dis, 0x3f, sizeof(dis));
	n = read();m = read();k = read();
	for (int i = 2; i <= n; ++i) a[i] = read();
	for (int i = 1; i <= m; ++i){
		x = read();y = read();
		an[x].push_back(y);
		an[y].push_back(x);
	}
	for (int i = 1; i <= n; ++i) dij(i);
	memset(p, 0, sizeof(p));
	for (int i = 1; i < n; ++i){
		for (int j = i + 1; j <= n; ++j)
			if (dis[i][j] <= k + 1){
				bn[i].push_back(j);
				bn[j].push_back(i);
				p[i][j] = p[j][i] = 1;
			}
	}
	dfs(1, 0, 0);
	cout << ans << endl;
	return 0;
} 

但是,如果新建的图可能很稠密,导致需要从n中选择4个景区的dfs效率最低为O(n4)

于是考场上的我很不放心,继续思考到第1h,无果。跳第二题。

考后

事实上在优化转车后我们得到了一个新图,我们的目的是找到一组a,b,c,d使得存在

1——a——b——c——d——1

显然枚举abcd会爆,结合数据量只能用到O(n2),考虑枚举其中的两个:

一、枚举ab或cd或ad

显然不行,另外两个还要考虑是否有边连通

二、枚举bc

由于剩下的a和d都只要1连通,只需要在与1有边相连的点中找就可以了

由于此题要的是权值和最大,不难想到贪心——对于每个点i,给它能直接到达且与1相连的点按权值排序,总要挑选更大值。

然后就是重复的问题:

首先枚举的bc是很容易保证互不重复的,关键在于a和d。拿a举例,预处理时可以保证b要连到的点不是自己,于是a只可能与c和d重复。解决方案:预处理出3个待选的a,其中至少有一个可以不与其它点重复。d同理。

于是接下来就很简单了:

首先按前面的思路重新建图,优化掉转车

然后对于每个点,求出其能直接到达且能到达1的权值前三大的点

最后枚举b、c及根据bc得出的a和d,如果没有重复,更新ans

code

用这个替掉上面dfs(1, 0, 0)

	for (int i = 2; i <= n; ++i){
		sort(bn[i].begin(), bn[i].end(), cmp);
		for (int j = 0; j < bn[i].size(); ++j){
			if (d[i][0] >= 3) break;
			if (p[1][bn[i][j]]) d[i][++d[i][0]] = bn[i][j];
		}
	}
	for (int i = 2; i <= n; ++i){
		for (int j = 2; j <= n; ++j){
			if (!p[i][j]) continue;
			for (int k = 1; k <= d[i][0]; ++k){
				for (int l = 1; l <= d[j][0]; ++l){
					int s = d[i][k], t = d[j][l];
					if (i != t && j != s && s != t){
						ans = max(ans, a[i] + a[j] + a[s] + a[t]);
					} 
				}
			}
		}
	}

反思

一、关于这道题目

实质上是对问题的剖析,一层一层解开问题。第一层:转车;第二层:选择景点。在选择景点这一层,我们又遇到了一个“重复”的问题,此时要么深搜加标记,要么枚举其中的个别点。前者是很容易超时的;对于后者,我们就要考虑枚举多少点、哪些点,然后就可以继续深入下去。

我当时一直在考虑能否用一种类似于动规的方式处理——事实上不能选取重复点意味着每次选择都有后效,最起码简单的动规思路是解决不了这个问题的——并且一直在扣。一条思路走不下去就是应该及时换一种思路, 说不定就能打开新世界。

二、关于考场

拿到70分就应该及时走人…最多再思考10min,关键时刻就要果断。

[CSP-S 2022] 策略游戏

题目背景

由于众所周知的原因,官方数据现置于子任务 0,剩余的子任务为民间数据。

题目描述

小 L 和小 Q 在玩一个策略游戏。

有一个长度为 n n n 的数组 A A A 和一个长度为 m m m 的数组 B B B,在此基础上定义一个大小为 n × m n \times m n×m 的矩阵 C C C,满足 C i j = A i × B j C_{i j} = A_i \times B_j Cij=Ai×Bj。所有下标均从 1 1 1 开始。

游戏一共会进行 q q q 轮,在每一轮游戏中,会事先给出 4 4 4 个参数 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,满足 1 ≤ l 1 ≤ r 1 ≤ n 1 \le l_1 \le r_1 \le n 1l1r1n 1 ≤ l 2 ≤ r 2 ≤ m 1 \le l_2 \le r_2 \le m 1l2r2m

游戏中,小 L 先选择一个 l 1 ∼ r 1 l_1 \sim r_1 l1r1 之间的下标 x x x,然后小 Q 选择一个 l 2 ∼ r 2 l_2 \sim r_2 l2r2 之间的下标 y y y。定义这一轮游戏中二人的得分是 C x y C_{x y} Cxy

小 L 的目标是使得这个得分尽可能大,小 Q 的目标是使得这个得分尽可能小。同时两人都是足够聪明的玩家,每次都会采用最优的策略。

请问:按照二人的最优策略,每轮游戏的得分分别是多少?

输入格式

第一行输入三个正整数 n , m , q n, m, q n,m,q,分别表示数组 A A A,数组 B B B 的长度和游戏轮数。

第二行: n n n 个整数,表示 A i A_i Ai,分别表示数组 A A A 的元素。

第三行: m m m 个整数,表示 B i B_i Bi,分别表示数组 B B B 的元素。

接下来 q q q 行,每行四个正整数,表示这一次游戏的 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2

输出格式

输出共 q q q 行,每行一个整数,分别表示每一轮游戏中,小 L 和小 Q 在最优策略下的得分。

样例 #1

样例输入 #1

3 2 2
0 1 -2
-3 4
1 3 1 2
2 3 2 2

样例输出 #1

0
4

样例 #2

样例输入 #2

6 4 5
3 -1 -2 1 2 0
1 2 -1 -3
1 6 1 4
1 5 1 4
1 4 1 2
2 6 3 4
2 5 2 3

样例输出 #2

0
-2
3
2
-1

提示

【样例解释 #1】

这组数据中,矩阵 C C C 如下:

[ 0 0 − 3 4 6 − 8 ] \begin{bmatrix} 0 & 0 \\ -3 & 4 \\ 6 & -8 \end{bmatrix} 036048

在第一轮游戏中,无论小 L 选取的是 x = 2 x = 2 x=2 还是 x = 3 x = 3 x=3,小 Q 都有办法选择某个 y y y 使得最终的得分为负数。因此小 L 选择 x = 1 x = 1 x=1 是最优的,因为这样得分一定为 0 0 0

而在第二轮游戏中,由于小 L 可以选 x = 2 x = 2 x=2,小 Q 只能选 y = 2 y = 2 y=2,如此得分为 4 4 4

【样例 #3】

见附件中的 game/game3.ingame/game3.ans

【样例 #4】

见附件中的 game/game4.ingame/game4.ans

【数据范围】

对于所有数据, 1 ≤ n , m , q ≤ 10 5 1 \le n, m, q \le {10}^5 1n,m,q105 − 10 9 ≤ A i , B i ≤ 10 9 -{10}^9 \le A_i, B_i \le {10}^9 109Ai,Bi109。对于每轮游戏而言, 1 ≤ l 1 ≤ r 1 ≤ n 1 \le l_1 \le r_1 \le n 1l1r1n 1 ≤ l 2 ≤ r 2 ≤ m 1 \le l_2 \le r_2 \le m 1l2r2m

测试点编号 n , m , q ≤ n, m, q \le n,m,q特殊条件
1 1 1 200 200 2001, 2
2 2 2 200 200 2001
3 3 3 200 200 2002
4 ∼ 5 4 \sim 5 45 200 200 200
6 6 6 1000 1000 10001, 2
7 ∼ 8 7 \sim 8 78 1000 1000 10001
9 ∼ 10 9 \sim 10 910 1000 1000 10002
11 ∼ 12 11 \sim 12 1112 1000 1000 1000
13 13 13 10 5 {10}^5 1051, 2
14 ∼ 15 14 \sim 15 1415 10 5 {10}^5 1051
16 ∼ 17 16 \sim 17 1617 10 5 {10}^5 1052
18 ∼ 20 18 \sim 20 1820 10 5 {10}^5 105

其中,特殊性质 1 为:保证 A i , B i > 0 A_i, B_i > 0 Ai,Bi>0
特殊性质 2 为:保证对于每轮游戏而言,要么 l 1 = r 1 l_1 = r_1 l1=r1,要么 l 2 = r 2 l_2 = r_2 l2=r2

分析

对于小L,假定他已经选择Ai, 那么有一下几种情况:

1.Ai为正数,由于小Q的目的是让数尽量小, 他要选择一个最小的Bj,令其为Bm,若Bm大于0,小L要选一个最大的Ai使结果更大,若Bm等于0,结果为0,若Bm小于0,小L要选最小的Ai

2.Ai为0,小Q无论选什么结果都是0

3.Ai为负数,小Q要选择一个最大的Bi,令其为Bm,若Bm大于0,要最大的Ai,若Bm小于0,要最小的Ai,Bm等于0,结果为0

考场思路

此题经历约1h,考场上思路比较混乱(难怪会错),又过了不少时间,尽量解释吧

首先感觉出只要得到小L和小Q(行和列)上正数最大值、最小值,负数最大值、最小值,是否有0这题就简单了

于是打线段树,处理出来以上几个量

接下来的思路用代码解释(在主程序里)

#include <bits/stdc++.h>
using namespace std;
struct node{
	long long l, r, mx0, mx1, mi0, mi1, k0;
}t1[400010], t2[400010], u0;
long long n, m, q, a[100010], b[100010];

long long read(){
	long long re = 0, ps = 1;
	char ch = getchar();
	for (;(ch < '0' || ch > '9') && ch != '-';ch = getchar());
	if (ch == '-') ps = -1;
	for (;ch < '0' || ch > '9'; ch = getchar());
	for (;ch >= '0' && ch <= '9'; ch = getchar()) re = re * 10 + ch - '0';
	return ps * re;
} 
node merg(node a, node b){
	node re;
	re.l = re.r = re.mx0 = re.mx1 = re.mi0 = re.mi1 = re.k0 = 0;
	re.l = a.l;re.r = b.r;
	re.mx0 = max(a.mx0, b.mx0);
	if (a.mi0 > 0){
		re.mi0 = a.mi0;
		if (b.mi0 > 0) re.mi0 = min(re.mi0, b.mi0);
	} 
	else if (b.mi0 > 0) re.mi0 = b.mi0;
	re.mi1 = min(a.mi1, b.mi1);
	if (a.mx1 < 0){
		re.mx1 = a.mx1;
		if (b.mx1 < 0) re.mx1 = max(re.mx1, b.mx1);
	} 
	else if (b.mx1 < 0) re.mx1 = b.mx1;
	re.k0 = max(a.k0, b.k0);
	return re;
} 
void build1(long long l, long long r, long long id){
	if (l == r){
		t1[id].l = t1[id].r = l;
		t1[id].mx0 = t1[id].mx1 = t1[id].mi0 = t1[id].mi1 = 0;
		if (a[l] == 0) t1[id].k0 = 1;
		else t1[id].k0 = 0;
		if (a[l] > 0) t1[id].mx0 = t1[id].mi0 = a[l];
		if (a[l] < 0) t1[id].mx1 = t1[id].mi1 = a[l];
		return;
	}
	long long mid = (l + r) / 2;
	build1(l, mid, id * 2);
	build1(mid + 1, r, id * 2 + 1);
	t1[id] = merg(t1[id * 2], t1[id * 2 + 1]);
}
void build2(long long l, long long r, long long id){
	if (l == r){
		t2[id].l = t2[id].r = l;
		t2[id].mx0 = t2[id].mx1 = t2[id].mi0 = t2[id].mi1 = 0;
		if (b[l] == 0) t2[id].k0 = 1;
		else t2[id].k0 = 0;
		if (b[l] > 0) t2[id].mx0 = t2[id].mi0 = b[l];
		if (b[l] < 0) t2[id].mx1 = t2[id].mi1 = b[l];
		return;
	}
	long long mid = (l + r) / 2;
	build2(l, mid, id * 2);
	build2(mid + 1, r, id * 2 + 1);
	t2[id] = merg(t2[id * 2], t2[id * 2 + 1]);
}
node query1(long long id, long long l, long long r){
	if (t1[id].l == l && t1[id].r == r) return t1[id];
	if (t1[id].l == t1[id].r) return t1[id];
	long long mid = (t1[id].l + t1[id].r) / 2;
	if (l > mid) return query1(id * 2 + 1, l, r);
	if (r <= mid) return query1(id * 2, l, r);
	if (l <= mid && r > mid){
		return merg(query1(id * 2, l, mid), query1(id * 2 + 1, mid + 1, r));
	}
}
node query2(long long id, long long l, long long r){
	if (t2[id].l == l && t2[id].r == r) return t2[id];
	if (t2[id].l == t2[id].r) return t2[id];
	long long mid = (t2[id].l + t2[id].r) / 2;
	if (l > mid) return query2(id * 2 + 1, l, r);
	if (r <= mid) return query2(id * 2, l, r);
	if (l <= mid && r > mid){
		return merg(query2(id * 2, l, mid), query2(id * 2 + 1, mid + 1, r));
	}
}
int main(){
	u0.l = u0.r = u0.mi1 = u0.mi0 = u0.mx1 = u0.mx0 = u0.k0 = 0;
	n = read();m = read();q = read();
	for (long long i = 1; i <= n; ++i) a[i] = read();
	for (long long i = 1 ;i <= m; ++i) b[i] = read();
	build1(1, n, 1);
	build2(1, m, 1);
	long long l1, l2, r1, r2;
	while (q--){
		l1 = read();r1 = read();l2 = read();r2 = read();
		node s1 = query1(1, l1, r1);//s1,s2用结构体分别表示行列相关量
        //mx0,mi0分别表示正数最大、最小值,mx1,mi1分别表示负数最大、最小值,若无正数或负数,以上值赋为0,k0表示是否有0
		node s2 = query2(1, l2, r2);
		if (s2.mx0 != 0 && s2.mi1 != 0){//如果列上有正有负,无论行上怎么取,小Q都能使结果为负数或0
			if (s1.k0) cout << 0 << endl;//那么优先考虑0
			else cout << max(s1.mi0 * s2.mi1, s1.mx1 * s2.mx0) << endl;//否则小L选择正数最小值或负数最大值
            //问题就出在这里:忘记判断行上是否有正数或负数就直接用了
		}
		else{//下面的部分是对的
			if (s2.mx0 == 0){//列上没有正数
				if (s1.mi1 != 0){//行上有负数,结果一定大于等于0
					if (s2.k0) cout << 0 << endl;//小Q让结果尽量为0
					else cout << s1.mi1 * s2.mx1 << endl; //否则小Q用最大的负数止损
				}
				else{//行上没有负数,结果一定小于等于0
					if (s1.k0) cout << 0 << endl;//小L让结果尽量为0
					else cout << s1.mi0 * s2.mi1 << endl;//否则小L用最小的正数
				}
			}
			else{//列上没有负数,以下大意同上
				if (s1.mx0 != 0){
					if (s2.k0) cout << 0 << endl;
					else cout << s1.mx0 * s2.mi0 << endl;
				}
				else{
					if (s1.k0) cout << 0 << endl;
					else cout << s1.mx1 * s2.mx0 << endl;
				}
			}
		}
	}
	return 0;
} 

由于有一部分没有考虑行上是否有正/负数,导致所有包括正数和负数的数据都崩掉了,只剩下40分

考场上主要精力都放在了线段树上,仔细检查了半天(但是并没有出错),同时想着回去做第一题,忽略了最后的判断可能存在问题,也没有仔细思考怎样处理最后的判断更不容易出错。

最后正确的代码(片段,其余相同)

if (s2.mx0 != 0 && s2.mi1 != 0){
			if (s1.k0) cout << 0 << endl;
			else{
				if (s1.mi0){
					if (s1.mx1) cout << max(s1.mi0 * s2.mi1, s1.mx1 * s2.mx0) << endl;
					else cout << s1.mi0 * s2.mi1 << endl;
				}
				else cout << s1.mx1 * s2.mx0 << endl;
			}
		}

不知抱着怎样的心态,我又回到了第一题,又思考了1h,无果,回到第三题。

反思

对于这道题,暴力就能拿到60分。而用上线段树,一来容易出错,二来费时费力。
考场上应该先把所有题目看一遍,得出自己能做到的目标分数,比方说这次后两题暴力可以拿到近100分,就应该先舍掉可能拿不到的40分。

[CSP-S 2022] 星战

题目背景

由于众所周知的原因,官方数据现置于子任务 0,剩余的子任务为民间数据。

题目描述

在这一轮的星际战争中,我方在宇宙中建立了 n n n 个据点,以 m m m 个单向虫洞连接。我们把终点为据点 u u u 的所有虫洞归为据点 u u u 的虫洞。

战火纷飞之中这些虫洞很难长久存在,敌人的打击随时可能到来。这些打击中的有效打击可以分为两类:

  1. 敌人会摧毁某个虫洞,这会使它连接的两个据点无法再通过这个虫洞直接到达,但这样的打击无法摧毁它连接的两个据点。
  2. 敌人会摧毁某个据点,由于虫洞的主要技术集中在出口处,这会导致该据点的所有还未被摧毁的虫洞被一同摧毁。而从这个据点出发的虫洞则不会摧毁

注意:摧毁只会导致虫洞不可用,而不会消除它的存在。

为了抗击敌人并维护各部队和各据点之间的联系,我方发展出了两种特种部队负责修复虫洞:

  • A 型特种部队则可以将某个特定的虫洞修复。
  • B 型特种部队可以将某据点的所有损坏的虫洞修复。

考虑到敌人打击的特点,我方并未在据点上储备过多的战略物资。因此只要这个据点的某一条虫洞被修复,处于可用状态,那么这个据点也是可用的。

我方掌握了一种苛刻的空间特性,利用这一特性我方战舰可以沿着虫洞瞬移到敌方阵营,实现精确打击。

为了把握发动反攻的最佳时机,指挥部必须关注战场上的所有变化,为了寻找一个能够进行反攻的时刻。总指挥认为:

  • 如果从我方的任何据点出发,在选择了合适的路线的前提下,可以进行无限次的虫洞穿梭(可以多次经过同一据点或同一虫洞),那么这个据点就可以实现反击
  • 为了使虫洞穿梭的过程连续,尽量减少战舰在据点切换虫洞时的质能损耗,当且仅当只有一个从该据点出发的虫洞可用时,这个据点可以实现连续穿梭
  • 如果我方所有据点都可以实现反击,也都可以实现连续穿梭,那么这个时刻就是一个绝佳的反攻时刻。

总司令为你下达命令,要求你根据战场上实时反馈的信息,迅速告诉他当前的时刻是否能够进行一次反攻

输入格式

输入的第一行包含两个正整数 n , m n,m n,m

接下来 m m m 行每行两个数 u , v u,v u,v,表示一个从据点 u u u 出发到据点 v v v 的虫洞。保证 u ≠ v u \ne v u=v,保证不会有两条相同的虫洞。初始时所有的虫洞和据点都是完好的。

接下来一行一个正整数 q q q 表示询问个数。

接下来 q q q 行每行表示一次询问或操作。首先读入一个正整数 t t t 表示指令类型:

  • t = 1 t = 1 t=1,接下来两个整数 u , v u, v u,v 表示敌人摧毁了从据点 u u u 出发到据点 v v v 的虫洞。保证该虫洞存在且未被摧毁。
  • t = 2 t = 2 t=2,接下来一个整数 u u u 表示敌人摧毁了据点 u u u。如果该据点的虫洞已全部 被摧毁,那么这次袭击不会有任何效果。
  • t = 3 t = 3 t=3,接下来两个整数 u , v u, v u,v 表示我方修复了从据点 u u u 出发到据点 v v v 的虫洞。保证该虫洞存在且被摧毁。
  • t = 4 t = 4 t=4,接下来一个整数 u u u 表示我方修复了据点 u u u。如果该据点没有被摧毁的虫洞,那么这次修复不会有任何效果。

在每次指令执行之后,你需要判断能否进行一次反攻。如果能则输出 YES 否则输出 NO

输出格式

输出一共 q q q 行。对于每个指令,输出这个指令执行后能否进行反攻。

样例 #1

样例输入 #1

3 6
2 3
2 1
1 2
1 3
3 1
3 2
11
1 3 2
1 2 3
1 1 3
1 1 2
3 1 3
3 3 2
2 3
1 3 1
3 1 3
4 2
1 3 2

样例输出 #1

NO
NO
YES
NO
YES
NO
NO
NO
YES
NO
NO

提示

【样例解释 #1】

虫洞状态可以参考下面的图片, 图中的边表示存在且未被摧毁的虫洞:

【样例 #2】

见附件中的 galaxy/galaxy2.ingalaxy/galaxy2.ans

【样例 #3】

见附件中的 galaxy/galaxy3.ingalaxy/galaxy3.ans

【样例 #4】

见附件中的 galaxy/galaxy4.ingalaxy/galaxy4.ans

【数据范围】

对于所有数据保证: 1 ≤ n ≤ 5 × 10 5 1 \le n \le 5 \times {10}^5 1n5×105 1 ≤ m ≤ 5 × 10 5 1 \le m \le 5 \times {10}^5 1m5×105 1 ≤ q ≤ 5 × 10 5 1 \le q \le 5 \times {10}^5 1q5×105

测试点 n ≤ n \le n m ≤ m \le m q ≤ q \le q特殊限制
1 ∼ 3 1 \sim 3 13 10 10 10 20 20 20 50 50 50
4 ∼ 8 4 \sim 8 48 10 3 {10}^3 103 10 4 {10}^4 104 10 3 {10}^3 103
9 ∼ 10 9 \sim 10 910 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105保证没有 t = 2 t = 2 t=2 t = 4 t = 4 t=4 的情况
11 ∼ 12 11 \sim 12 1112 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105保证没有 t = 4 t = 4 t=4 的情况
13 ∼ 16 13 \sim 16 1316 10 5 {10}^5 105 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105
17 ∼ 20 17 \sim 20 1720 5 × 10 5 5 \times {10}^5 5×105 5 × 1 0 5 5\times 10^5 5×105 5 × 10 5 5 \times {10}^5 5×105

分析

奇怪的是,考场上的我发现能够实现连续穿梭就是每个点都能实现反击的充分条件时,我的第一反应是:我是不是把题目理解错了?

于是又看了几遍,还是不理解为什么要这么做,发现No比较多,全部输出No摆烂。

考后

再看这道题,题意就是这样。其实只需要判断能否实现连续穿梭,即每个点出度都为1即可。

首先考虑40分暴力:每次操作后更新关联点的出度,最后判断是否所有点出度都为1,O(n2)

再考虑50分:保证没有t=2和t=4,即每次只摧毁或修复1条边,关系到1个点,更新出度变为O(1),我们可以知道上一个状态的出度为1的点的个数,进行操作后也只需判断出度为1的点增多或减少,最后判断是否为n即可。

code

#include <bits/stdc++.h>
using namespace std;
int u, v, t, n, m, q, an[1010][1010], bn[1010][1010], cd[101000], vh[1010];
void getit(){
	for (int i = 1; i <= m; ++i){
		cin >> u >> v;
		cd[u]++;
	}
	cin >> q;
	int cnt = 0;
	for (int i = 1; i <= n; ++i) if (cd[i] == 1) cnt++;
	while (q--){
		cin >> t >> u >> v;
		if (t == 1){
			if (cd[u] == 1) cnt--;
			cd[u]--;
			if (cd[u] == 1) cnt++;
		}
		else{
			if (cd[u] == 1) cnt--;
			cd[u]++;
			if (cd[u] == 1) cnt++;
		} 
		if (cnt == n) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
}
int main(){
	cin >> n >> m;
	if (n> 1000){
		getit();
		return 0;
	}
	for (int i = 1; i <= m; ++i){
		cin >> u >> v;
		an[u][v] = bn[u][v] = 1;
		cd[u]++;
	}
	cin >> q;
	while (q--){
		cin >> t;
		if (t == 1){
			cin >> u >> v;
			bn[u][v] = 0;
			cd[u]--;
		}
		if (t == 2){
			cin >> u;
			for (int i = 1; i <= n; ++i){
				if (bn[i][u]){
					cd[i]--;
					bn[i][u] = 0;
				}
			}
		}
		if (t == 3){
			cin >> u >> v;
			bn[u][v]++;
			cd[u]++;
		}
		if (t == 4){
			cin >> u;
			for (int i = 1; i <= n; ++i){
				if (an[i][u] && bn[i][u] == 0){
					cd[i]++;
					bn[i][u] = 1;
				}
			}
		}
		bool flag = true;
		for (int i = 1; i <= n; ++i) if (cd[i] != 1) flag = false;
		if (flag) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
}

[CSP-S 2022] 数据传输

题目背景

请勿滥用本题评测,后果自负。

由于众所周知的原因,官方数据现置于子任务 0,剩余的子任务为民间数据。

题目描述

小 C 正在设计计算机网络中的路由系统。

测试用的网络总共有 n n n 台主机,依次编号为 1 ∼ n 1 \sim n 1n。这 n n n 台主机之间由 n − 1 n - 1 n1 根网线连接,第 i i i 条网线连接个主机 a i a_i ai b i b_i bi。保证任意两台主机可以通过有限根网线直接或者间接地相连。受制于信息发送的功率,主机 a a a 能够直接将信息传输给主机 b b b 当且仅当两个主机在可以通过不超过 k k k 根网线直接或者间接的相连。

在计算机网络中,数据的传输往往需要通过若干次转发。假定小 C 需要将数据从主机 a a a 传输到主机 b b b a ≠ b a \neq b a=b),则其会选择出若干台用于传输的主机 c 1 = a , c 2 , … , c m − 1 , c m = b c_1 = a, c_2, \ldots, c_{m - 1}, c_m = b c1=a,c2,,cm1,cm=b,并按照如下规则转发:对于所有的 1 ≤ i < m 1 \le i < m 1i<m,主机 c i c_i ci 将信息直接发送给 c i + 1 c_{i + 1} ci+1

每台主机处理信息都需要一定的时间,第 i i i 台主机处理信息需要 v i v_i vi 单位的时间。数据在网络中的传输非常迅速,因此传输的时间可以忽略不计。据此,上述传输过程花费的时间为 ∑ i = 1 m v c i \sum_{i = 1}^{m} v_{c_i} i=1mvci

现在总共有 q q q 次数据发送请求,第 i i i 次请求会从主机 s i s_i si 发送数据到主机 t i t_i ti。小 C 想要知道,对于每一次请求至少需要花费多少单位时间才能完成传输。

输入格式

输入的第一行包含三个正整数 n , Q , k n, Q, k n,Q,k,分别表示网络主机个数,请求个数,传输参数。数据保证 1 ≤ n ≤ 2 × 10 5 1 \le n \le 2 \times {10}^5 1n2×105 1 ≤ Q ≤ 2 × 10 5 1 \le Q \le 2 \times {10}^5 1Q2×105 1 ≤ k ≤ 3 1 \le k \le 3 1k3

输入的第二行包含 n n n 个正整数,第 i i i 个正整数表示 v i v_i vi,保证 1 ≤ v i ≤ 10 9 1 \le v_i \le {10}^9 1vi109

接下来 n − 1 n - 1 n1 行,第 i i i 行包含两个正整数 a i , b i a_i, b_i ai,bi,表示一条连接主机 a i , b i a_i, b_i ai,bi 的网线。保证 1 ≤ a i , b i ≤ n 1 \le a_i, b_i \le n 1ai,bin

接下来 Q Q Q 行,第 i i i 行包含两个正整数 s i , t i s_i, t_i si,ti,表示一次从主机 s i s_i si 发送数据到主机 t i t_i ti 的请求。保证 1 ≤ s i , t i ≤ n 1 \le s_i, t_i \le n 1si,tin s i ≠ t i s_i \ne t_i si=ti

输出格式

Q Q Q 行,每行一个正整数,表示第 i i i 次请求在传输的时候至少需要花费多少单位的时间。

样例 #1

样例输入 #1

7 3 3
1 2 3 4 5 6 7
1 2
1 3
2 4
2 5
3 6
3 7
4 7
5 6
1 2

样例输出 #1

12
12
3

提示

【样例解释 #1】

对于第一组请求,由于主机 4 , 7 4, 7 4,7 之间需要至少 4 4 4 根网线才能连接,因此数据无法在两台主机之间直接传输,其至少需要一次转发;我们让其在主机 1 1 1 进行一次转发,不难发现主机 1 1 1 和主机 4 , 7 4, 7 4,7 之间都只需要两根网线即可连接,且主机 1 1 1 的数据处理时间仅为 1 1 1,为所有主机中最小,因此最少传输的时间为 4 + 1 + 7 = 12 4 + 1 + 7 = 12 4+1+7=12

对于第三组请求,由于主机 1 , 2 1, 2 1,2 之间只需要 1 1 1 根网线就能连接,因此数据直接传输就是最优解,最少传输的时间为 1 + 2 = 3 1 + 2 = 3 1+2=3

【样例 #2】

见附件中的 transmit/transmit2.intransmit/transmit2.ans

该样例满足测试点 2 2 2 的限制。

【样例 #3】

见附件中的 transmit/transmit3.intransmit/transmit3.ans

该样例满足测试点 3 3 3 的限制。

【样例 #4】

见附件中的 transmit/transmit4.intransmit/transmit4.ans

该样例满足测试点 20 20 20 的限制。

【数据范围】

对于所有的测试数据,满足 1 ≤ n ≤ 2 × 10 5 1 \le n \le 2 \times {10}^5 1n2×105 1 ≤ Q ≤ 2 × 10 5 1 \le Q \le 2 \times {10}^5 1Q2×105 1 ≤ k ≤ 3 1 \le k \le 3 1k3 1 ≤ a i , b i ≤ n 1 \le a_i, b_i \le n 1ai,bin 1 ≤ s i , t i ≤ n 1 \le s_i, t_i \le n 1si,tin s i ≠ t i s_i \ne t_i si=ti

测试点 n ≤ n \le n Q ≤ Q \le Q k = k = k=特殊性质
1 1 1 10 10 10 10 10 10 2 2 2
2 2 2 10 10 10 10 10 10 3 3 3
3 3 3 200 200 200 200 200 200 2 2 2
4 ∼ 5 4 \sim 5 45 200 200 200 200 200 200 3 3 3
6 ∼ 7 6 \sim 7 67 2000 2000 2000 2000 2000 2000 1 1 1
8 ∼ 9 8 \sim 9 89 2000 2000 2000 2000 2000 2000 2 2 2
10 ∼ 11 10 \sim 11 1011 2000 2000 2000 2000 2000 2000 3 3 3
12 ∼ 13 12 \sim 13 1213 2 × 10 5 2 \times {10}^5 2×105 2 × 10 5 2 \times {10}^5 2×105 1 1 1
14 14 14 5 × 10 4 5 \times {10}^4 5×104 5 × 10 4 5 \times {10}^4 5×104 2 2 2
15 ∼ 16 15 \sim 16 1516 10 5 {10}^5 105 10 5 {10}^5 105 2 2 2
17 ∼ 19 17 \sim 19 1719 2 × 10 5 2 \times {10}^5 2×105 2 × 10 5 2 \times {10}^5 2×105 2 2 2
20 20 20 5 × 10 4 5 \times {10}^4 5×104 5 × 10 4 5 \times {10}^4 5×104 3 3 3
21 ∼ 22 21 \sim 22 2122 10 5 {10}^5 105 10 5 {10}^5 105 3 3 3
23 ∼ 25 23 \sim 25 2325 2 × 10 5 2 \times {10}^5 2×105 2 × 10 5 2 \times {10}^5 2×105 3 3 3

特殊性质:保证 a i = i + 1 a_i = i + 1 ai=i+1,而 b i b_i bi 则从 1 , 2 , … , i 1, 2, \ldots, i 1,2,,i 中等概率选取。

分析

考场上以为自己肯定拿不到分,没怎么看

考后

题意:给出1棵树,给出起点和终点,每次可以跳k及以内距离,求权值和最小值。

发现k只有123,首先考虑k=1,那么这题就是树上最短路,求出1到每个点的路径上权值和 g [ i ] g[i] g[i],两点之间最短路径上权值和即为 g [ a ] + g [ b ] − 2 ∗ g [ l c a ] + v [ l c a ] g[a] + g[b] - 2 * g[lca] + v[lca] g[a]+g[b]2g[lca]+v[lca]

k=2时也不难,在最短路的基础上,可以选择跳过某个点,但不可能跳出最短路——由于跳跃距离只有2,跳出去再跳回来相当于在原来的基础上多了最短路外的那个点,权值不可能最小。那么跳过哪些点呢?不知道,考虑动规。首先把最短路拉出来,放进一个数组里,同时标记 w a y [ i ] way[i] way[i]为路径上第 i i i个点对应的点的原标号,定义 d p [ i ] dp[i] dp[i]为从起点到达最短路上第i个点的最短处理时间,那么
d p [ i ] = m i n ( d p [ i − 1 ] , d p [ i − 2 ] ) + v [ w a y [ i ] ] dp[i] = min(dp[i - 1], dp[i - 2])+v[way[i]] dp[i]=min(dp[i1],dp[i2])v[way[i]]。看到特殊性质保证 b i b_i bi 1 , 2 , … , i 1, 2, \ldots, i 1,2,,i 中等概率选取,说明此树期望深度只有log n,那么此过程复杂度只有O(n log n)。

k=3时,同样先找到最短路,由于跳跃距离只有3,也就是说,如果选择跳到最短路两点距离以外的点再跳回来一定不是最小值,那么最多跳出最短路一个点。根据贪心原则,这个点首选权值最小点,这个可以提前处理出来,定义 f [ i ] f[i] f[i]为一个点邻点的最小权值。我们重新定义 d p [ i ] [ 0 ] dp[i][0] dp[i][0]为顶点到第i个点的最少处理时间, d p [ i ] [ 1 ] dp[i][1] dp[i][1]为顶点到第i个点的权值最小的邻点的最少处理时间。那么
d p [ i ] [ 0 ] = m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 2 ] [ 0 ] , d p [ i − 3 ] [ 0 ] , d p [ i − 2 ] [ 1 ] ) + v [ w a y [ i ] ] dp[i][0] = min(dp[i-1][0],dp[i-2][0],dp[i-3][0],dp[i-2][1])+v[way[i]] dp[i][0]=min(dp[i1][0],dp[i2][0],dp[i3][0],dp[i2][1])+v[way[i]]
d p [ i ] [ 1 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ 0 ] , d p [ i − 2 ] [ 0 ] , d p [ i − 1 ] [ 1 ] ) + f [ i ] dp[i][1] =min(dp[i][0],dp[i-1][0],dp[i-2][0],dp[i-1][1])+f[i] dp[i][1]=min(dp[i][0],dp[i1][0],dp[i2][0],dp[i1][1])+f[i]

k=1时 O(n logn)
k=2或3时 对于特殊性质为O(n logn),其余为O(n2)
共76分

code

#include <bits/stdc++.h>
using namespace std;
long long n, Q, k, a, b, v[200010];
long long fa[200010][25], dep[200010], g[200010], f[200010], way[200010], t, way2[200010], t2, dp[200010], dp2[200010][2];
vector <long long> an[200010];
void dfs(long long now, long long fat){
	g[now] = g[fat] + v[now];
	fa[now][0] = fat;
	dep[now] = dep[fat] + 1;
	for (long long i = 1; i <= 21; ++i){
		fa[now][i] = fa[fa[now][i - 1]][i - 1];
	}
	for (long long i = 0; i < an[now].size(); ++i){
		long long vt = an[now][i];
		f[now] = min(f[now], v[vt]);
		if (vt == fat) continue;
		dfs(vt, now);
	}
}
long long LCA(long long a, long long b){
	for (long long i = 21; i >= 0; --i){
		if (dep[fa[a][i]] >= dep[b]) a = fa[a][i];
	}
	if (a == b) return a;
	for (long long i = 21; i >= 0; --i){
		if (fa[a][i] != fa[b][i]){
			a = fa[a][i];
			b = fa[b][i];
		}
	}
	return fa[a][0];
}
int main(){
	memset(f, 0x3f, sizeof(f));
	memset(g, 0x3f, sizeof(g));
	cin >> n >> Q >> k;
	for (long long i = 1; i <= n; ++i) cin >> v[i];
	for (long long i = 2; i <= n; ++i){
		cin >> a >> b;
		an[a].push_back(b);
		an[b].push_back(a);
	}
	dfs(1, 0);
	while(Q--){
		cin >> a >> b;
		if (dep[a] < dep[b]) swap(a, b);
		long long lca = LCA(a, b);
		if (k == 1) cout << g[a] + g[b] - 2 * g[lca] + v[lca] << endl;
		else{
			t = t2 = 0;
			while (a != lca){
				way[++t] = a;
				a = fa[a][0];
			}
			way[++t] = a;
			while (b != lca){
				way2[++t2] = b;
				b = fa[b][0];
			}
			for (int i = t2; i >= 1; --i){
				way[++t] = way2[i];
			}
			if (k == 2){
				dp[1] = v[way[1]];
				dp[2] = v[way[1]] + v[way[2]];
				for (int i = 3; i <= t; ++i){
					dp[i] = min(dp[i - 1], dp[i - 2]) + v[way[i]]; 
				}
				cout << dp[t] << endl;
			}
			if (k == 3){
				dp2[1][0] = v[way[1]];
				for (int i = 2; i <= 4; ++i) dp2[i][0] = v[way[1]] + v[way[i]];
				for (int i = 1; i <= 3; ++i) dp2[i][1] = v[way[1]] + f[way[i]];
				for (int i = 5; i <= t; ++i){
					long long mi = 2e15;
					for (int j = 1; j <= 3; ++j) mi = min(mi, dp2[i - j][0]);
					mi = min(mi, dp2[i - 2][1]);
					dp2[i - 1][1] = mi + f[way[i - 1]];
					dp2[i][0] = mi + v[way[i]];
				}
				cout << dp2[t][0] << endl;
			}	
		}
		
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值