[题解][Codeforces 1137A~1137F]Codeforces Round #545 (Div. 1) 简要题解

这篇博客详细介绍了Codeforces Round #545 (Div. 1)比赛中的六道题目,包括题意、算法思路和代码实现。涉及的算法包括KMP、Tarjan、Floyd判圈、维护凸壳和LCT等,适合程序员进阶学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 第一次打 Div. 1 ,感觉题目质量还是比较好的
  • 然后就把我这个菜鸡选手区分到了榜末

题目

洛谷 RemoteJudge

Codeforces

A

题意

  • 一个 n × m n\times m n×m 矩阵 a a a
  • 定义 f ( x , y ) f(x,y) f(x,y) 表示在矩阵中取出第 x x x 行和第 y y y
  • 设一个新矩阵 b b b ,这个矩阵只有第 x x x 行和第 y y y
  • 对于 b b b 中的任意两个元素 b i , j b_{i,j} bi,j b u , v b_{u,v} bu,v
  • 如果 b i , j &lt; b u , v b_{i,j}&lt;b_{u,v} bi,j<bu,v a i , j a_{i,j} ai,j 必须小于 a u , v a_{u,v} au,v
  • 如果 b i , j = b u , v b_{i,j}=b_{u,v} bi,j=bu,v a i , j a_{i,j} ai,j 必须等于 a u , v a_{u,v} au,v
  • &gt; &gt; > 同理
  • f ( x , y ) f(x,y) f(x,y) 等于矩阵 b b b 所有合法的方案中, b b b 中最大元素的最小值
  • 对于每个 1 ≤ x ≤ n , 1 ≤ y ≤ m 1\le x\le n,1\le y\le m 1xn,1ym f ( x , y ) f(x,y) f(x,y)
  • 1 ≤ n , m ≤ 1000 1\le n,m\le 1000 1n,m1000

题解

  • 显然,我们要求的是
  • max ⁡ ( 第 x 行 比 a x , y 小 的 数 个 数 , 第 y 列 比 a x , y 小 的 数 个 数 ) + 1 \max(第x行比a_{x,y}小的数个数,第y列比a_{x,y}小的数个数)+1 max(xax,y,yax,y)+1
  • + max ⁡ ( 第 x 行 比 a x , y 大 的 数 个 数 , 第 y 列 比 a x , y 大 的 数 个 数 ) +\max(第x行比a_{x,y}大的数个数,第y列比a_{x,y}大的数个数) +max(xax,y,yax,y)
  • max ⁡ \max max 里面的四个东西都可以把矩阵的每行每列离散化之后求出
  • O ( n m ( log ⁡ n + log ⁡ m ) ) O(nm(\log n+\log m)) O(nm(logn+logm))

代码

#include <bits/stdc++.h>

// 20030830

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 1005;

int n, m, a[N][N], cntx[N], cnty[N], thx[N][N], thy[N][N], w[N];

int main()
{
	n = read(); m = read();
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			a[i][j] = read();
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++) w[j] = a[i][j];
		std::sort(w + 1, w + m + 1);
		cntx[i] = std::unique(w + 1, w + m + 1) - w - 1;
		for (int j = 1; j <= m; j++)
			thx[i][j] = std::lower_bound(w + 1, w + cntx[i] + 1, a[i][j]) - w;
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++) w[j] = a[j][i];
		std::sort(w + 1, w + n + 1);
		cnty[i] = std::unique(w + 1, w + n + 1) - w - 1;
		for (int j = 1; j <= n; j++)
			thy[j][i] = std::lower_bound(w + 1, w + cnty[i] + 1, a[j][i]) - w;
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
			printf("%d ", std::max(thx[i][j], thy[i][j])
				+ std::max(cntx[i] - thx[i][j], cnty[j] - thy[i][j]));
		puts("");
	}
	return 0;
}

B

题意

  • 两个 01 01 01 s s s t t t
  • 构造一个串,满足
  • (1) 该串中 0 0 0 的个数和 1 1 1 的个数都与 s s s 一样多
  • (2) t t t 在该串中出现的次数最多
  • 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 500000 1\le|s|,|t|\le500000 1s,t500000

算法: KMP + 贪心

  • 问题可以看成一个完整的 t t t 串,后面接上一些 t t t 的后缀(这里的后缀 t [ ∣ t ∣ − x + 1 : ∣ t ∣ ] t[|t|-x+1:|t|] t[tx+1:t] 需要满足对于所有的 1 ≤ i ≤ ∣ t ∣ − x + 1 1\le i\le|t|-x+1 1itx+1 都有 t [ i ] = t [ i + x − 1 ] t[i]=t[i+x-1] t[i]=t[i+x1] ,即 t t t 的一个 period ),使得 0 0 0 1 1 1 的个数在一个上限范围内时,接入的 t t t 的后缀尽可能多
  • 显然我们需要每次都接入短的合法后缀(需要使用的 0 0 0 1 1 1 个数都是最小值)
  • 而如果接上了几个后缀之后再也不能接入任何后缀,则剩下的位置任意取(需要满足 0 0 0 1 1 1 的个数要求)
  • t t t 的最短 period 后缀就是 n n n 减去 t t t 的最长 border 长度,可以使用 KMP 求出
  • O ( n ) O(n) O(n)

代码

#include <bits/stdc++.h>

// 20030830

const int N = 5e5 + 5;

int n, m, tt, cnt1, cnt0, c0, c1, nxt[N], s0[N], s1[N];
char s[N], t[N];

struct node
{
	int id, cst, ccc;
} a[N];

inline bool comp(node a, node b)
{
	return a.cst < b.cst || (a.cst == b.cst && a.ccc < b.ccc);
}

int main()
{
	scanf("%s%s", s + 1, t + 1);
	n = strlen(s + 1);
	m = strlen(t + 1);
	for (int i = 1; i <= n; i++)
		if (s[i] == '1') cnt1++;
		else cnt0++;
	for (int i = 1; i <= m; i++)
		if (t[i] == '1') c1++;
		else c0++;
	for (int i = 2, j = 0; i <= m; i++)
	{
		while (j && t[j + 1] != t[i]) j = nxt[j];
		if (t[j + 1] == t[i]) j++;
		nxt[i] = j;
	}
	if (cnt1 < c1 || cnt0 < c0)
	{
		for (int i = 1; i <= n; i++) putchar(s[i]);
		return puts(""), 0;
	}
	for (int i = m; i >= 1; i--)
		if (t[i] == '1') s1[i] = s1[i + 1] + 1, s0[i] = s0[i + 1];
		else s1[i] = s1[i + 1], s0[i] = s0[i + 1] + 1;
	for (int i = nxt[m]; ; i = nxt[i])
	{
		a[++tt] = (node) {i + 1, s0[i + 1], s1[i + 1]};
		if (!i) break;
	}
	for (int i = 1; i <= m; i++) putchar(t[i]);
	int tql = cnt0 - c0, trl = cnt1 - c1;
	std::sort(a + 1, a + tt + 1, comp);
	while (a[1].cst <= tql && a[1].ccc <= trl)
	{
		tql -= a[1].cst, trl -= a[1].ccc;
		for (int j = a[1].id; j <= m; j++) putchar(t[j]);
	}
	while (tql--) putchar('0');
	while (trl--) putchar('1');
	puts("");
	return 0;
}

C

题意

  • n n n 个城市, m m m 条单向道路
  • 一周有 d d d 天,第 i i i 个城市的博物馆在一周的第 j j j 天开放当且仅当 s i , j = 1 s_{i,j}=1 si,j=1
  • 周一从城市 1 1 1 出发,走一条道路需要 1 1 1 天时间
  • 如果一周的第 j j j 天恰好在城市 i i i 并且 s i , j = 1 s_{i,j}=1 si,j=1 则能参观到城市 i i i 的博物馆
  • 求最多能参观到多少个城市的博物馆
  • 1 ≤ n ≤ 100 , 000 1\le n\le100,000 1n100,000 0 ≤ m ≤ 100 , 000 0\le m\le100,000 0m100,000 1 ≤ d ≤ 50 1\le d\le50 1d50

算法: Tarjan 强连通分量缩点 + DP

  • 把一个城市拆成 d d d 个点,点 ( i , j ) (i,j) (i,j) 表示一周第 j j j 天的城市 i i i
  • 对于有向边 ( u , v ) (u,v) (u,v) 和任意 1 ≤ i ≤ d 1\le i\le d 1id ,连边 &lt; ( u , i ) , ( v , i &VeryThinSpace; m o d &VeryThinSpace; d + 1 ) &gt; &lt;(u,i),(v,i\bmod d+1)&gt; <(u,i),(v,imodd+1)>
  • 把这张图强连通分量缩点之后,就是经典的 DAG 最长链 DP 了
  • Q :一个城市的博物馆被多次参观只计算一次,如何处理?
  • A :可以证明,如果 ( u , i ) (u,i) (u,i) 能到达 ( u , j ) (u,j) (u,j) ,那么 ( u , j ) (u,j) (u,j) 也能到达 ( u , i ) (u,i) (u,i) (可以使用数论相关知识证明),换句话说,一个城市拆出的两个点 ( u , i ) (u,i) (u,i) ( u , j ) (u,j) (u,j)要么属于同一个强连通分量,要么两两不可到达。于是任意一条从 ( 1 , 1 ) (1,1) (1,1) 开始的链都不会存在 ( u , i ) (u,i) (u,i) ( u , j ) (u,j) (u,j) 不在同一个强连通分量内,于是这个 DP 的正确性有了保证
  • O ( ( n + m ) d ) O((n+m)d) O((n+m)d)

代码

#include <bits/stdc++.h>

// 20030830

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 1e5 + 5, E = 55, M = 5e6 + 5;

int n, m, d, ecnt, nxt[M], adj[M], go[M], ToT, dfn[M], low[M], top, stk[M],
tot, bel[M], vis[M], typ[M], ecnt2, nxt2[M], adj2[M], go2[M], f[M];
char s[N][E];
bool ins[M];

int which(int x, int y)
{
	return (x - 1) * d + y;
}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}

void add_edge2(int u, int v)
{
	nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
}

void dfs(int u)
{
	dfn[u] = low[u] = ++ToT;
	ins[stk[++top] = u] = 1;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!dfn[v])
		{
			dfs(v);
			low[u] = Min(low[u], low[v]);
		}
		else if (ins[v]) low[u] = Min(low[u], dfn[v]);
	if (dfn[u] == low[u])
	{
		bel[u] = ++tot; ins[u] = 0; int v;
		while (v = stk[top--], v != u) bel[v] = tot, ins[v] = 0;
	}
}

int main()
{
	int x, y;
	n = read(); m = read(); d = read();
	while (m--)
	{
		x = read(); y = read();
		for (int i = 1; i <= d; i++)
			add_edge(which(x, i), which(y, i % d + 1));
	}
	for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
	for (int i = 1; i <= n * d; i++) if (!dfn[i]) dfs(i);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= d; j++)
		{
			int u = which(i, j);
			if (s[i][j] == '1' && vis[bel[u]] < i)
				vis[bel[u]] = i, typ[bel[u]]++;
		}
	for (int u = 1; u <= n * d; u++)
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			if (bel[u] != bel[v]) add_edge2(bel[u], bel[v]);
	for (int u = 1; u <= tot; u++)
	{
		for (int e = adj2[u], v = go2[e]; e; e = nxt2[e], v = go2[e])
			f[u] = Max(f[u], f[v]);
		f[u] += typ[u];
	}
	std::cout << f[bel[which(1, 1)]] << std::endl;
	return 0;
}

D

题意

算法: Floyd 判圈

  • Floyd 判圈了解一下
  • 建立两个指针 u , v u,v u,v ,初始都在起点
  • 每次把 u u u 向出边移动两步, v v v 移动一步(实现时只需要移动步数为偶数时 u , v u,v u,v 都移动,移动步数为奇数时只移动 u u u 即可)
  • v v v 到达了环和链的交点时, u u u 走了 2 t 2t 2t 步,相当于在环上走了 t t t
  • 把环上的点编号,与链的交点为 0 0 0 号点,按照出边方向编号为 1 1 1 号, 2 2 2 号,一直到 c − 1 c-1 c1 号点
  • 而这时候 u u u t &VeryThinSpace; m o d &VeryThinSpace; c t\bmod c tmodc 号点, v v v 0 0 0 号点
  • 相当于 u u u 继续走 2 ( ( c − t ) &VeryThinSpace; m o d &VeryThinSpace; c ) 2((c-t)\bmod c) 2((ct)modc) 步, v v v ( c − t ) &VeryThinSpace; m o d &VeryThinSpace; c (c-t)\bmod c (ct)modc 步后 u u u 能追上 v v v
  • 这时候 v v v 的位置为 ( c − t ) &VeryThinSpace; m o d &VeryThinSpace; c (c-t)\bmod c (ct)modc
  • 你发现了什么? u u u v v v 一起再走 t t t 步就到了环和链的交点处!
  • 也就是说,我们这时候把所有的棋子一起移动,直到所有棋子都在一个点上为止
  • 移动步数分析:
  • (1) v v v 到达交点处:耗费 2 t 2t 2t
  • (2) u u u v v v 到达一个点上: 2 ( ( c − t ) m o d &ThinSpace;&ThinSpace; c ) &lt; 2 c 2((c-t)\mod c)&lt;2c 2((ct)modc)<2c
  • (3)所有点到达一个点上: t t t
  • 总步数小于 3 t + 2 c 3t+2c 3t+2c

代码

#include <bits/stdc++.h>

// 20030830

const int N = 105;

int n;
char s[N];

int main()
{
	while (1)
	{
		puts("next 1"); fflush(stdout);
		scanf("%d", &n);
		while (n--) scanf("%*s");
		puts("next 0 1"); fflush(stdout);
		scanf("%d", &n);
		if (n == 2)
		{
			while (n--) scanf("%*s");
			break;
		}
		else while (n--) scanf("%*s");
	}
	while (1)
	{
		puts("next 0 1 2 3 4 5 6 7 8 9"); fflush(stdout);
		scanf("%d", &n);
		if (n == 1)
		{
			scanf("%*s");
			return puts("done"), fflush(stdout), 0;
		}
		else while (n--) scanf("%*s");
	}
	return 0;
}

E

题意

  • 给定一个序列,初始有 n n n 0 0 0
  • m m m 次操作
  • 每次操作可以在开头或末尾加入 k k k 0 0 0 ,或者给定 b b b s s s ,对于每个 i i i ,把序列第 i i i 个数加上 b + s × ( i − 1 ) b+s\times(i-1) b+s×(i1)
  • 你需要在每次操作之后输出序列中的最小值以及最小值的位置
  • 如果有多个则取最小位置
  • 1 ≤ n , k , b , s ≤ 1 0 9 1\le n,k,b,s\le10^9 1n,k,b,s109 1 ≤ m ≤ 300000 1\le m\le300000 1m300000
  • 任何时候序列中任何值不超过 1 0 18 10^{18} 1018

算法:维护凸壳

  • 特判:如果序列中含 0 0 0 则直接输出
  • 把序列看作无穷长
  • 问题转化成每次区间加一个一次函数,询问全局最小值及位置
  • 并且满足每一次操作的区间一定是后面区间的子区间
  • 易得一点性质
  • 最小值要么是当前序列被操作过(区间加一次函数涉及到)的第一个位置,要么是之前操作的区间 [ l , r ] [l,r] [l,r] 对应的 r + 1 r+1 r+1 (需要保证 r + 1 r+1 r+1 被操作过)
  • 第一个位置直接计算
  • 对于后者,设区间 [ l , r ] [l,r] [l,r] 是加入的第 i i i 个区间(第 i i i 个区间之后的区间的右端点严格大于 r r r
  • s b sb sb s s ss ss 表示已经加入的区间的 b b b s s s 的前缀和,且当前已经加入了 t o t tot tot 个区间
  • 那么我们需要最小化的是
  • s b t o t − s b i + ( s s t o t − s s i ) × r i sb_{tot}-sb_i+(ss_{tot}-ss_i)\times r_i sbtotsbi+(sstotssi)×ri
  • 化下式子
  • s b t o t − ( ( s s i × r i + s b i ) − s s t o t × r i ) sb_{tot}-((ss_i\times r_i+sb_i)-ss_{tot}\times r_i) sbtot((ssi×ri+sbi)sstot×ri)
  • s b t o t sb_{tot} sbtot 减去一条斜率为 s s t o t ss_{tot} sstot ,过点 ( r i , s s i × r i + s b i ) (r_i,ss_i\times r_i+sb_i) (ri,ssi×ri+sbi) 的直线的截距
  • 我们需要最大化这个截距
  • 也就是我们需要维护点集 { i ∣ ( r i , s s i × r i + s b i ) } \{i|(r_i,ss_i\times r_i+sb_i)\} {i(ri,ssi×ri+sbi)}上凸壳
  • 由于凸壳斜率的变化方向和 s s t o t ss_{tot} sstot 的变化方向相反
  • 所以我们需要用单调栈维护在现在以及之后可能成为最优决策的凸壳点
  • 每次取最优决策时,从栈顶删除不优的决策
  • O ( m ) O(m) O(m)

代码

#include <bits/stdc++.h>

// 20030830

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

typedef long long ll;

const int N = 3e5 + 5;

int n, m, stk[N], top;
ll rsid, suml, sums, sumb, X[N], Y[N], offset, firs;
bool le0 = 1, ri0 = 1;

bool check(int i, int j, int k)
{
	return 1.0 * (Y[i] - Y[j]) / (X[i] - X[j]) <=
		1.0 * (Y[j] - Y[k]) / (X[j] - X[k]);
}

ll calc(int i)
{
	return Y[i] - X[i] * sums;
}

int main()
{
	int op, x, y;
	rsid = read(); m = read();
	while (m--)
	{
		op = read(); x = read();
		if (op == 1) offset += x, le0 = 1;
		else if (op == 2) rsid += x, ri0 = 1;
		else
		{
			if (le0) firs = x;
			else firs += x;
			y = read();
			if (ri0)
			{
				X[++n] = suml; Y[n] = suml * sums + sumb;
				while (top > 1 && check(stk[top - 1], stk[top], n)) top--;
				stk[++top] = n;
				suml = rsid; sums += y; sumb += offset * y + x;
				while (top > 1 && calc(stk[top]) <= calc(stk[top - 1])) top--;
			}
			else
			{
				sums += y; sumb += offset * y + x;
				while (top > 1 && calc(stk[top]) <= calc(stk[top - 1])) top--;
			}
			le0 = ri0 = 0;
		}
		if (le0) {puts("1 0"); continue;}
		if (ri0) {printf("%lld 0\n", offset + suml + 1); continue;}
		ll pos = offset + X[stk[top]] + 1, val = sumb - calc(stk[top]);
		if (firs <= val) pos = 1, val = firs;
		printf("%lld %lld\n", pos, val);
	}
	return 0;
}

F (补于 2019-04-20)

题意

  • 给定一棵 n n n 个节点的树,每个节点有一个优先级,初始时编号为 i i i 的节点的优先级为 i i i
  • 定义一棵树的删除序列为:每次取出优先级最小的叶子节点删除掉,重复 n n n 次把所有点都删掉之后,每次删掉的点的编号形成的序列
  • q q q 次操作,操作分三种
  • (1)把节点 u u u 的优先级改成当前所有节点的优先级的最大值加 1 1 1
  • (2)询问编号为 u u u 的节点在删除序列中所处的位置
  • (3)询问编号为 u u u v v v 的节点在删除序列中哪一者靠前
  • 2 ≤ n ≤ 200000 2\le n\le 200000 2n200000 1 ≤ q ≤ 200000 1\le q\le 200000 1q200000

算法: LCT + 树状数组

  • 操作(3)实际上就是两次操作(2)
  • 如果只有一次询问,那么使用一个优先队列模拟题意即可
  • 接下来,我们考虑把点 u u u 的优先级改成最大,对删除序列的影响
  • 可以发现,设当前优先级最大的点为 v v v ,那么对删除序列的影响就是 v v v u u u 的路径上的所有点都被移到删除序列的末尾,它们在删除序列中的相对顺序为从 v v v 走到 u u u 依次经过点的顺序,其余的点相对顺序保持不变
  • 相当于初始时给每个点各自一个颜色,改优先级相当于把 v v v u u u 的路径上的所有点赋成一种新的颜色
  • 这让我们想到了什么? Access !
  • 维护一个 LCT ,一开始只有虚边,保证任何时候 LCT 的根为优先级最大的节点,一条实链表示一种颜色
  • 这样一次改优先级操作就相当于把 u u u MakeRoot 一下
  • 回到询问,易得我们需要求的是
  • 颜 色 比 u 小 的 点 数 + u 所 在 的 实 链 中 比 u 深 的 点 数 + 1 颜色比u小的点数+u所在的实链中比u深的点数+1 u+uu+1
  • 第一者可以用树状数组维护每种颜色的点数的前缀和
  • 每种颜色的点数的变化可以在 Access 的过程中计算贡献
  • 第二者在 Splay 上查排名即可
  • O ( ( n + q ) log ⁡ 2 n ) O((n+q)\log^2n) O((n+q)log2n)

代码

#include <bits/stdc++.h>

// 20030830

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline void Swap(T &a, T &b) {T t = a; a = b; b = t;}

inline char get()
{
	char c;
	while ((c = getchar()) != 'u' && c != 'w' && c != 'c');
	return c;
}

const int N = 4e5 + 5;

int n, m, q, ecnt, nxt[N], adj[N], go[N], A[N], fa[N], lc[N], rc[N], d[N],
sze[N], col[N], rev[N], len, que[N];

std::priority_queue<int, std::vector<int>, std::greater<int> > pq;

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; d[u]++;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; d[v]++;
}

void change(int x, int v)
{
	for (; x <= n + q; x += x & -x)
		A[x] += v;
}

int ask(int x)
{
	int res = 0;
	for (; x; x -= x & -x)
		res += A[x];
	return res;
}

int which(int x) {return rc[fa[x]] == x;}

bool isroot(int x)
{
	return !fa[x] || (lc[fa[x]] != x && rc[fa[x]] != x);
}

void down(int x)
{
	if (rev[x])
		Swap(lc[x], rc[x]), rev[x] = 0,
		rev[lc[x]] ^= 1, rev[rc[x]] ^= 1;
}

void upt(int x)
{
	sze[x] = sze[lc[x]] + sze[rc[x]] + 1;
}

void init(int u, int fu)
{
	fa[u] = fu;
	for (int e = adj[u], v; e; e = nxt[e])
		if ((v = go[e]) != fu) init(v, u);
}

void rotate(int x)
{
	int y = fa[x], z = fa[y], b = lc[y] == x ? rc[x] : lc[x];
	col[x] = col[y]; col[y] = 0;
	if (z && !isroot(y)) (lc[z] == y ? lc[z] : rc[z]) = x;
	fa[x] = z; fa[y] = x; if (b) fa[b] = y;
	if (lc[y] == x) rc[x] = y, lc[y] = b;
	else lc[x] = y, rc[y] = b; upt(y); upt(x);
}

void splay(int x)
{
	que[len = 1] = x;
	for (int y = x; !isroot(y); y = fa[y]) que[++len] = fa[y];
	for (int i = len; i >= 1; i--) down(que[i]);
	while (!isroot(x))
	{
		if (!isroot(fa[x]))
		{
			if (which(x) == which(fa[x])) rotate(fa[x]);
			else rotate(x);
		}
		rotate(x);
	}
}

void access(int x)
{
	int c = ++m;
	for (int y = 0; x; y = x, x = fa[x])
	{
		splay(x);
		if (rc[x]) col[rc[x]] = col[x];
		change(col[x], sze[rc[x]] - sze[x]);
		change(c, sze[x] - sze[rc[x]]);
		rc[x] = y; col[x] = c; col[y] = 0;
		if (y) fa[y] = x; upt(x);
	}
}

void makeroot(int x)
{
	access(x); splay(x); rev[x] ^= 1;
}

int rk(int x)
{
	splay(x);
	return ask(col[x] - 1) + sze[rc[x]] + 1;
}

int main()
{
	char c;
	int x, y;
	m = n = read(); q = read();
	for (int i = 1; i < n; i++)
		x = read(), y = read(), add_edge(x, y);
	for (int i = 1; i <= n; i++) if (d[i] <= 1) pq.push(i);
	for (int i = 1; i <= n; i++)
	{
		sze[i] = 1;
		int u = pq.top(); pq.pop();
		col[u] = i; change(i, 1);
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			if (!col[v] && (--d[v]) <= 1) pq.push(v);
	}
	init(n, 0);
	for (int T = 1; T <= q; T++)
	{
		c = get(); x = read();
		if (c == 'u') makeroot(x);
		else if (c == 'w') printf("%d\n", rk(x));
		else y = read(), printf("%d\n", rk(x) < rk(y) ? x : y);
	}
	return 0;
}
### Codeforces Round 260 Div. 1 题目及题解 #### A. Vasya and Multisets 在这道题目中,Vasya有一个由n个整数组成的序列。目标是通过将这些数分成若干组,使得每组中的所有数都相同,并且尽可能减少分组的数量。 为了实现这一目的,可以利用贪心算法来解决这个问题。具体来说,在遍历输入数据的同时维护当前最大频率计数器,对于每一个新遇到的不同数值增加一个新的集合[^1]。 ```cpp #include <bits/stdc++..h> using namespace std; void solve() { int n; cin >> n; unordered_map<int, int> freq; for (int i = 0; i < n; ++i) { int x; cin >> x; freq[x]++; } int maxFreq = 0; for (auto& p : freq) { maxFreq = max(maxFreq, p.second); } cout << maxFreq << "\n"; } ``` #### B. Pashmak and Graph 此问题涉及图论领域的一个经典最短路径计算案例。给定一张带权无向图以及起点S和终点T,要求求出从S到T经过至少一条边后的最小花费总和。 Dijkstra算法适用于此类场景下的单源最短路径查询任务。初始化距离表dist[]为无穷大(INF),仅设置起始节点的距离为零;随后借助优先队列选取未访问过的最近邻接顶点u更新其相邻结点v至目前为止所知的最佳到达成本min{dist[u]+w(u,v)}直至找到终止条件即抵达目的地t或处理完毕所有可达区域内的候选者为止。 ```cpp typedef pair<long long,int> pli; const long long INF = LLONG_MAX / 3; struct Edge { int to, cost; }; vector<Edge> G[MAX_V]; long long d[MAX_V]; bool dijkstra(int s, int t){ priority_queue<pli,vector<pl>,greater<pl>> que; fill(d,d+MAX_V,INF); d[s]=0; que.push(pli(0,s)); while(!que.empty()){ pli p=que.top();que.pop(); int v=p.second; if(d[v]<p.first) continue; for(auto e:G[v]){ if(d[e.to]>d[v]+e.cost){ d[e.to]=d[v]+e.cost; que.push(pli(d[e.to],e.to)); } } } return d[t]!=INF; } ``` #### C. DZY Loves Colors 这是一道关于颜色染色的问题。给出长度为N的一维网格,初始状态下每个格子都有一个默认的颜色编号。现在有M次操作机会改变某些位置上的色彩值,最终目的是统计整个条带上共有几种不同的色调存在。 采用离散化技术预处理原始输入并记录下各段连续同色区间的端点坐标范围,之后针对每一次修改请求动态调整受影响部分的信息结构体(如线段树),最后依据累积的结果得出答案。 ```cpp // 假设已经实现了上述提到的数据结构 SegmentTree 和 update 函数 SegmentTree st; for(int i=1;i<=m;++i){ int l,r,c; scanf("%d%d%d",&l,&r,&c); update(l,r,c); } printf("%lld\n",st.query()); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值