Codeforces Round #600 (Div. 2) 解题报告

相关链接

A. Single Push

t t t 组数据。每组数据给定两个长为 n n n 的数组 A A A B B B,可以选择 A A A 的一个子序列 A [ l , r ] A[l,r] A[l,r],将其中的每个数增加 x ( x > 0 ) x(x > 0) x(x>0),该操作至多进行一次。判断 A A A 能否转化成 B B B

t ≤ 20 , n ≤ 100000 t\leq 20,n\leq 100000 t20,n100000

签到题。初始化 C i = A i − B i C_i=A_i-B_i Ci=AiBi,若 C i ≠ 0 C_i\not=0 Ci=0 C i ≠ C i − 1 C_i\not = C_{i-1} Ci=Ci1,说明这是一个需要操作的新子序列,判断这样的 i i i 的个数是否小于 2 2 2 个。如果出现 C i < 0 C_i < 0 Ci<0,则一定不行。

复杂度 O ( t ⋅ n ) O(t\cdot n) O(tn)

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-24 20:54:20
**/

#include <bits/stdc++.h>
 
const int MAXN = 1e5 + 5;
 
int T, N, A[MAXN];
 
bool solve() {
	int cnt = 0, tmp;
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i)
		scanf("%d", &A[i]);
	for (int i = 1; i <= N; ++i) {
		scanf("%d", &tmp);
		A[i] -= tmp;
	}
	for (int i = 1; i <= N; ++i) {
		if (A[i] > 0) return false;
		if (A[i] != A[i - 1] && A[i]) ++cnt;
	}
	return cnt <= 1;
}
 
int main() {
	scanf("%d", &T);
	while (T--)
		puts(solve() ? "YES" : "NO");
	return 0;
}

B. Silly Mistake

给定一个序列 A [ 1 ⋯ n ] A[1\cdots n] A[1n],判断能否将其划分成若干区间,使每个区间 A [ l i , r i ] A[l_i,r_i] A[li,ri]

  • 对于一个正数 x x x,将 x x x 插入集合 S S S
  • 对于一个负数 x x x,将 − x -x x S S S 中取出

要求每个正数至多被插入一次(在该区间内),且取出时存在于 S S S,最后 S S S 为空。

如果存在满足条件的划分方法,输出每一段的长度,不存在输出-1

n ≤ 1 0 5 n\leq 10^5 n105

还是签到题。考虑贪心,每次 S S S 为空时将其划分成一个新区间,如此可以尽最大可能保证每个正整数 x x x 只被插入一次。

然后用std::set或者直接哈希表维护区间即可。

复杂度 O ( n ) O(n) O(n) O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-26 09:59:19
**/

#include <bits/stdc++.h>

const int MAXN = 1e5 + 5;

int N, A[MAXN];
std::set<int> S, T;
std::vector<int> v;

void Solve() {
	int total = 0, cnt = 0, lst = 0;
	for (int i = 1; i <= N; ++i) {
		if (A[i] > 0) {
			if (T.find(A[i]) != T.end()) {
				puts("-1");
				return ;
			}
			S.insert(A[i]);
			T.insert(A[i]);
			total += A[i];
		} else {
			A[i] = -A[i];
			if (S.find(A[i]) == S.end()) {
				puts("-1");
				return ;
			}
			S.erase(A[i]);
			total -= A[i];
			if (total == 0) {
				++cnt;
				v.push_back(i - lst);
				lst = i;
				T.clear();
			}
		}
	}
	if (total != 0) {
		puts("-1");
		return ;
	}
	printf("%d\n", cnt);
	for (auto i : v) 
		printf("%d ", i);
}

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i)
		scanf("%d", &A[i]);
	Solve();
	return 0;
}

C. Sweets Eating

n n n 颗糖果,其糖度值分别为 A [ 1 ⋯ n ] A[1\cdots n] A[1n],每天最多吃 m m m 颗糖果,且每颗糖果只能被吃一次。

若在第 i i i 天吃了糖果 j j j,则获得的甜度为 i × A j i\times A_j i×Aj,吃多个糖获得的甜度叠加。

依次求出如果一共吃 i ( 1 ≤ i ≤ n ) i(1\leq i\leq n) i(1in) 颗糖果,获得的甜度最小值是多少。

n ≤ 200000 n\leq 200000 n200000

先考虑对于 t t t 颗糖果,如何吃使得甜度最小。显然吃甜度最小的 t t t 颗,且将甜度大的放在前面吃。

具体而言,设 C C C A A A 从小到大的有序排列。则吃法如下: ( C t , ⋯   , C t − m + 1 ) , ( C t − m , ⋯   , C t − 2 m + 1 ) ⋯ ( ⋯   , C 1 ) (C_t,\cdots,C_{t-m+1}),(C_{t-m},\cdots,C_{t-2m+1}) \cdots (\cdots,C_1) (Ct,,Ctm+1),(Ctm,,Ct2m+1)(,C1)

当增加一颗糖果 C t + 1 C_{t+1} Ct+1 时,将 C t + 1 C_{t+1} Ct+1 放在最前面,然后其余的糖果依次往后挪是最优解。

可以意会出对于大部分糖果而言,增加一颗糖果后甜度不变,而位于每天尾端的糖果则会挪到下一天,甜度增加 C i C_i Ci

那么只要维护位于每天尾端糖果的甜度和 s s s,则可以快速计算出下一天的甜度 T i T_i Ti,即 T i = T i − 1 + C i + s T_i=T_{i-1}+C_i+s Ti=Ti1+Ci+s

现在考虑如何维护 s s s,不难想到它是 m m m 个一循环,因为位于某一天尾端的糖果挪动 m m m 次后又会到下一天的尾端,因而可以用一个数组 s [ 0 ⋯ m − 1 ] s[0\cdots m-1] s[0m1] 来维护。

注意答案会超过int范围。

复杂度 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-26 11:30:12
**/

#include <bits/stdc++.h>

const int MAXN = 200000 + 5;

int N, M;
long long A[MAXN], B[MAXN];

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	std::sort(A + 1, A + N + 1);
	long long total = 0;
	for (int i = 1, cur = 0; i <= N; ++i, (cur += 1) %= M) {
		total += A[i] + B[cur];
		printf("%lld ", total);
		B[cur] += A[i];
	}
	return 0;
}

D. Harmonious Graph

给定 n n n 个点 m m m 条边的无向图,增加最少的边数,使得图满足:若点 l , r ( l < r ) l,r(l < r) l,r(l<r) 连通,则 l l l i ( l < i < r ) i(l < i < r) i(l<i<r) 都连通。

n , m ≤ 200000 n,m\leq 200000 n,m200000

先求出无向图的连通块,从小到大,对于 i i i 所在的连通块,编号最小的点为 i i i(若有更小的点 j j j,则在 j j j 时已经处理完该连通块,不会等到 i i i),最大的点 b i b_i bi。若 i , j i,j i,j 不连通,将 i i i j ( i   < j < b i ) j(i\ < j < b_i) j(i <j<bi) 所在的连通块相连,并维护连通块的最大点编号 b i b_i bi。连通性用并查集维护。

复杂度 O ( n α ( n ) ) O(n\alpha(n)) O(nα(n))

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-26 20:23:39
**/

#include <bits/stdc++.h>

const int MAXN = 200000 + 5;

int N, M;
std::vector<int> E[MAXN];

void addEdge(int u, int v) {
	E[u].push_back(v);
	E[v].push_back(u);
}

int ans;
int Fa[MAXN];
bool Vis[MAXN];
int Mx[MAXN];

int findFa(int x) {
	return Fa[x] == x ? x : Fa[x] = findFa(Fa[x]);
}

void Union(int x, int y) {
	++ans;
	y = findFa(y);
	x = findFa(x);
	Fa[y] = x;
	Mx[x] = std::max(Mx[x], Mx[y]);
}

void bfs(int x) {
	Mx[x] = x;
	Fa[x] = x;
	Vis[x] = 1;
	static std::queue<int> Que;
	Que.push(x);
	while (!Que.empty()) {
		int u = Que.front();
		Que.pop();
		for (auto v : E[u]) {
			if (!Vis[v]) {
				Vis[v] = 1;
				Fa[v] = x;
				Mx[x] = std::max(Mx[x], v);
				Que.push(v);
			}
		}
	}
}

void Solve(int x) {
	for (int i = x; i < Mx[x]; ++i)
		if (findFa(i) != x) Union(x, i);
}

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 0, u, v; i < M; ++i) {
		scanf("%d%d", &u, &v);
		addEdge(u, v);
	}
	for (int i = 1; i <= N; ++i)
		if (!Vis[i]) bfs(i);
	for (int i = 1; i <= N; ++i)
		if (findFa(i) == i) Solve(i);
	printf("%d\n", ans);
	return 0;
}

E. Antenna Coverage

在数轴 [ 1 , m ] [1,m] [1,m] 上,有 n n n 盏灯,坐标依次为 p i p_i pi,范围为 s i s_i si,即它可照亮 [ p i − s i , p i + s i ] [p_i-s_i,p_i+s_i] [pisi,pi+si] 。增加任意一盏灯的范围 s i → s i + 1 s_i\rightarrow s_i+1 sisi+1 花费为 1 1 1。求将 [ 1 , m ] [1,m] [1,m] 全部照亮的最小花费(可以照亮 [ 1 , m ] [1,m] [1,m] 以外的地方)。

n ≤ 80 , m ≤ 100000 n\leq 80, m\leq 100000 n80,m100000

f i , j f_{i,j} fi,j 为前 i i i 盏灯照亮 [ 1 , j ] [1,j] [1,j] 的最小花费,初始化 f 0 , 0 = 0 f_{0,0}=0 f0,0=0

f i , j = m i n { f i − 1 , k } ( k ≥ j ) f_{i,j}=min\{f_{i-1,k}\}(k \geq j) fi,j=min{fi1,k}(kj),显然覆盖 [ 1 , k ] [1,k] [1,k]包含了覆盖 [ 1 , j ] [1,j] [1,j] 的情况。

对于 j > p i + s i j > p_i+s_i j>pi+si,第 i i i 盏灯覆盖的范围是 [ 2 p i − j , j ] [2p_i-j,j] [2pij,j],因而前 i − 1 i-1 i1 盏灯需要覆盖 [ 1 , k ] ( 2 p i − j − 1 ≤ k ≤ j − 1 ) [1,k](2p_i-j-1\leq k\leq j-1) [1,k](2pij1kj1) f i , j = j − p i − s i + m i n { f i − 1 , k } f_{i,j}=j-p_i-s_i+min\{f_{i-1,k}\} fi,j=jpisi+min{fi1,k}

注意到 f i , j f_{i,j} fi,j 只与 f i − 1 , k f_{i-1,k} fi1,k 有关,因而可以滚动 i i i 。根据转移方程,发现可以直接用 f j f_j fj 表示覆盖 [ 1 , j ] [1,j] [1,j] 的花费,略去 i i i 这一维。

复杂度 O ( n m ) O(nm) O(nm)

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-26 21:06:29
**/
 
#include <bits/stdc++.h>
 
const int MAXN = 80 + 5;
const int MAXM = 100000 + 5;
 
int N, M;
std::pair<int, int> A[MAXN];
int F[MAXM << 1];
 
int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i) scanf("%d%d", &A[i].first, &A[i].second);
	std::sort(A + 1, A + N + 1);
	memset(F, 0x3f, sizeof F);
	F[0] = 0;
	for (int i = 1; i <= N; ++i) {
		int p = A[i].first + A[i].second;
		for (int j = 2 * M; j >= 0; --j)
			F[j] = std::min(F[j + 1], F[j]);
		for (int j = p; j <= 2 * M; ++j)
			F[j] = std::min(F[j], j - p + F[std::max(0, 2 * A[i].first - j - 1)]);
	}
	int ans = INT_MAX;
	for (int i = M; i <= M * 2; ++i) ans = std::min(ans, F[i]);
	printf("%d\n", ans);
	return 0;
}

F. Cheap Robot

n n n 个点 m m m 条边的无向图上,每条边有边权 w i w_i wi,编号在 [ 1 , k ] [1,k] [1,k] 的点为“中心点”。现有一辆小车在图上穿梭,其油箱大小为 t t t。若它某时剩余的油为 s s s,则它可以通过所有边权 w i ≤ s w_i\leq s wis 的边,且通过后 s s s 减少 w i w_i wi。若小车到达“中心点”,油箱会加满( s = t s=t s=t)。有 q q q 个询问,每次询问小车从中心点 a i a_i ai 出发到中心点 b i b_i bi 所需的最小油量 t t t

k , n ≤ 1 0 5 , m ≤ 3 × 1 0 5 , w i ≤ 1 0 9 k,n\leq 10^5,m\leq 3\times 10^5,w_i\leq 10^9 k,n105,m3×105,wi109

由于小车的终点为“中心点”,因而小车在路径上的任意点都应该能抵达离它最近的“中心点”。

小车在任意点驶向离它最近的“中心点”不会使结果更劣,因为它可以通过该“中心点”加满油后原路返回,且剩余的油更多(或相等)。

设点 i i i 与最近的中心点的距离为 d i d_i di,可以通过Disjkstra在 O ( m log ⁡ n ) O(m\log{n}) O(mlogn) 求出。具体而言,将 [ 1 , k ] [1,k] [1,k] 的距离设为 0 0 0,求达到其他点的最短距离。

设到达点 i i i 时剩余油量为 s s s

s < d i s < d_i s<di,则它无法到达终点。

s ≥ d i s \geq d_i sdi,则通过“中心点”加油回到点 i i i s = t − d i s=t-d_i s=tdi

如果要通过 ( i , j ) (i,j) (i,j) i i i 走到 j j j,且满足到达 j j j 后仍然能到达离 j j j 最近的中心点,需满足 s − d i − w i , j ≥ d j s-d_i-w_{i,j}\geq d_j sdiwi,jdj,整理一下得 s ≥ d i + d j + w i , j s\geq d_i+d_j+w_{i,j} sdi+dj+wi,j

因而对于一条 a i a_i ai b i b_i bi 的路径,需满足初始油量 t ≥ m a x { d i + d j + w i , j } t\geq max\{d_i+d_j+w_{i,j}\} tmax{di+dj+wi,j}

最小化 t t t,即最小化 d i + d j + w i , j d_i+d_j+w_{i,j} di+dj+wi,j。将原图的所有边 ( u , v ) (u,v) (u,v) 权值重置为 d u + d v + w u , v d_u+d_v+w_{u,v} du+dv+wu,v ,然后构造最小生成树,如此使得任意两点间的最大距离最小。

构造完最小生成树后, t t t 为树上两点边权的最大值,可以用倍增求得。

复杂度 O ( m log ⁡ n + m log ⁡ m ) O(m\log{n}+m\log{m}) O(mlogn+mlogm)

/* Code Information
 * Author: Skqliao
 * Time: 2020-07-27 23:44:52
**/
#include <bits/stdc++.h>

const int MAXN = 1e5 + 5;
const int MAXM = 3e5 + 5;

int N, M, K, Q;

struct Graph {
	struct Edge {
		int u, v;
		long long w;
		int nxt;
		bool operator < (const Edge &x) const {
			return w < x.w;
		}
	} E[MAXM << 1];
	int H[MAXN], cntE;
	void addEdge(int u, int v, long long w) {
		E[++cntE] = (Edge) {u, v, w, H[u]};
		H[u] = cntE;
	}
} G, T;

std::priority_queue<std::pair<int, long long>> Pq;
long long Dis[MAXN];

void Dijkstra() {
    memset(Dis, 0x3f, sizeof Dis);
    for (int i = 1; i <= K; ++i) {
		Pq.push(std::make_pair(0, i));
		Dis[i] = 0;
	}
    while (!Pq.empty()) {
        while (!Pq.empty() && -Pq.top().first > Dis[Pq.top().second]) {
            Pq.pop();
        }
        if (Pq.empty()) {
            break;
        }
        int u = Pq.top().second; Pq.pop();
        for (int i = G.H[u]; i; i = G.E[i].nxt) {
			int v = G.E[i].v;
			if (Dis[v] > Dis[u] + G.E[i].w) {
				Dis[v] = Dis[u] + G.E[i].w;
				Pq.push(std::make_pair(-Dis[v], v));
			}
		}
    }
}

int Fa[MAXN];

int findFa(int x) {
	return Fa[x] == x ? x : Fa[x] = findFa(Fa[x]);
}

void Kruskal() {
	for (int i = 1; i <= G.cntE; ++i)
		G.E[i].w += Dis[G.E[i].u] + Dis[G.E[i].v];
	std::sort(G.E + 1, G.E + G.cntE + 1);
	for (int i = 1; i <= N; ++i) Fa[i] = i;
	for (int i = 1; i <= G.cntE; ++i) {
		int u = G.E[i].u, v = G.E[i].v;
		if (findFa(u) != findFa(v)) {
			Fa[findFa(v)] = findFa(u);
			T.addEdge(u, v, G.E[i].w);
			T.addEdge(v, u, G.E[i].w);
		}
	}
}

int Depth[MAXN];
long long Mx[MAXN][20];
int Anc[MAXN][20];

void dfs(int x, int fa) {
	Anc[x][0] = fa;
	Depth[x] = Depth[fa] + 1;
	for (int i = 1; (1 << i) <= Depth[x]; ++i) {
		Anc[x][i] = Anc[Anc[x][i - 1]][i - 1];
		Mx[x][i] = std::max(Mx[x][i - 1], Mx[Anc[x][i - 1]][i - 1]);
	}
	for (int i = T.H[x]; i; i = T.E[i].nxt) {
		int v = T.E[i].v;
		if (fa != v) {
			Mx[v][0] = T.E[i].w;
			dfs(v, x);
		}
	}
}

long long lca(int u, int v) {
	long long ans = 0;
	if (Depth[u] > Depth[v]) std::swap(u, v);
	for (int i = 0; (1 << i) <= Depth[v]; ++i) {
		if ((Depth[u] - Depth[v]) >> i & 1) {
			ans = std::max(ans, Mx[v][i]);
			v = Anc[v][i];
		}
	}
	if (u == v) return ans;
	for (int i = 19; i >= 0; --i) {
		if (Anc[u][i] != Anc[v][i]) {
			ans = std::max(ans, std::max(Mx[u][i], Mx[v][i]));
			u = Anc[u][i], v = Anc[v][i];
		}
	}
	return std::max(ans, std::max(Mx[u][0], Mx[v][0]));
}

int main() {
	scanf("%d%d%d%d", &N, &M, &K, &Q);
	for (int i = 1, u, v, w; i <= M; ++i) {
		scanf("%d%d%d", &u, &v, &w);
		G.addEdge(u, v, w);
		G.addEdge(v, u, w);
	}
	Dijkstra();
	Kruskal();
	dfs(1, 0);
	for (int i = 1, u, v; i <= Q; ++i) {
		scanf("%d%d", &u, &v);
		printf("%lld\n", lca(u, v));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值