[题解][Codeforces 1172A~1172D]Codeforces Round #564 (Div. 1) 前四题题解

  • 中国场,最后两题为毒瘤数据结构题,还未写,见谅

Address

洛谷 RemoteJudge

Codeforces

A

Meaning

  • 两个序列 a a a b b b ,长度为 n n n ,每个数均为 0 0 0 或者 1 1 1 n n n 之间的整数,且 1 1 1 n n n 的每个数出现且仅出现一次
  • 每次操作可以在第一个序列里抽走一个数放到第二个序列的末尾,并把第二个序列的开头元素拿到第一个序列内
  • (从第一个序列中抽走的数的位置是任意的)
  • 求把第二个序列变成依次从 1 1 1 n n n 的最少步数
  • 1 ≤ n ≤ 2 × 1 0 5 1\le n\le2\times10^5 1n2×105

Solution

  • 先判定 b b b 是否存在形如 1   2   3   …   x 1\text{ }2\text{ }3\text{ }\dots\text{ }x 1 2 3  x 的后缀
  • 如果存在,则尝试在第 i i i 次操作时把数 x + i x+i x+i 放到序列末尾
  • 如果尝试成功(操作时对于每个 i i i 都满足数 x + i x+i x+i 存在于序列 a a a 内)那么这就是最优方案,操作次数 n − x n-x nx
  • 否则回到开始,每一次操作都把 0 0 0 放到序列末尾,直到可以放 1 1 1 为止
  • 当然,在何时放 1 1 1 需要满足一定的条件
  • 可以发现,如果 b b b x x x 在位置 y y y x ≠ 0 x\ne 0 x̸=0 ),那么 1 1 1 至少需要在第 y − x + 2 y-x+2 yx+2 次操作时被放入序列,否则数 x x x 需要被放入序列时 x x x 不在序列 a a a
  • 放了 1 1 1 之后,就可以依次把 2 2 2 n n n 的数放到末尾了
  • 于是这时答案为
  • n − 1 + max ⁡ b i ≠ 0 { i − b i + 2 } n-1+\max_{b_i\ne 0}\{i-b_i+2\} n1+bi̸=0max{ibi+2}
  • 注意 b b b 0 0 0 的情况
  • O ( n ) O(n) O(n)

Code

#include <bits/stdc++.h>

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 = 2e5 + 5;

int n, a[N], pos1, ans, lst;

int main()
{
	n = read();
	for (int i = 1; i <= n; i++) read();
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i <= n; i++)
	{
		if (a[i] == 1) pos1 = i;
		if (a[i]) lst = i;
	}
	bool is = 1;
	for (int i = pos1; i <= n; i++)
		if (a[i] != i - pos1 + 1) is = 0;
	if (is)
	{
		bool can = 1;
		for (int i = 1; i < pos1; i++) if (a[i] &&
			i - (a[i] - (n - pos1 + 1) - 1) >= 1) can = 0;
		if (can) return std::cout << pos1 - 1 << std::endl, 0;
	}
	pos1 = 2;
	for (int i = 1; i <= n; i++) if (a[i])
		pos1 = std::max(pos1, i - (a[i] - 3));
	std::cout << pos1 + n - 2 << std::endl;
	return 0;
}

B

Meaning

  • 给定一棵树
  • 求有多少个不同的 1 1 1 n n n 排列 p p p
  • 使得对于任意两个满足树上存在边 ( p i , p j ) (p_i,p_j) (pi,pj) ( p x , p y ) (p_x,p_y) (px,py) 的二元组 ( i , j ) ( x , y ) (i,j)(x,y) (i,j)(x,y) 都满足圆周上有 n n n 个点的圆环上点 i i i j j j 连成的边以及点 x x x y y y 连成的边不在除端点之外的点相交
  • 求方案数对 998244353 998244353 998244353 取模
  • 2 ≤ n ≤ 2 × 1 0 5 2\le n\le2\times10^5 2n2×105

Solution

  • 转化一下
  • 1 1 1 为根,先把 1 1 1 在圆环上定一个位置,有 n n n 种方案
  • 然后对每个子树分配环上的一个连续段
  • 然后对每个子树(连续段)分别处理
  • 定义 f [ u ] f[u] f[u] 表示为子树 u u u 分配位置的方案数(不包括为 1 1 1 定位)
  • u = 1 u=1 u=1 时显然有 d u ! d_u! du! 种方案( d u d_u du u u u 的子节点数)
  • 否则在子树 u u u 所属的连续段上,相当于需要为 u u u 的所有子树,包括上 u u u 分别分配一个连续段,共 d u + 1 d_u+1 du+1 个连续段,方案数 ( d u + 1 ) ! (d_u+1)! (du+1)!
  • f [ u ] = d u ! × ( d u + 1 ) [ u &gt; 1 ] ∏ v ∈ s o n ( u ) f [ v ] f[u]=d_u!\times(d_u+1)^{[u&gt;1]}\prod_{v\in son(u)}f[v] f[u]=du!×(du+1)[u>1]vson(u)f[v]
  • 这样答案就是所有点的度数(无根树)阶乘之积乘上 n n n
  • O ( n ) O(n) O(n)

Code

#include <bits/stdc++.h>

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 = 2e5 + 5, M = N << 1, ZZQ = 998244353;

int n, ecnt, nxt[M], adj[N], go[M], f[N];

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

void dfs(int u, int fu)
{
	int cnt = 0, fc = 1, fac = 1;
	f[u] = 1;
	for (int e = adj[u], v; e; e = nxt[e])
		if ((v = go[e]) != fu) dfs(v, u), cnt++;
	for (int i = 1; i <= cnt; i++)
		fac = 1ll * fac * i % ZZQ;
	f[u] = u == 1 ? fac : 1ll * fac * (cnt + 1) % ZZQ;
	for (int e = adj[u], v; e; e = nxt[e])
		if ((v = go[e]) != fu)
			f[u] = 1ll * f[u] * f[v] % ZZQ;
}

int main()
{
	int x, y;
	n = read();
	for (int i = 1; i < n; i++)
		x = read(), y = read(), add_edge(x, y);
	dfs(1, 0);
	std::cout << 1ll * n * f[1] % ZZQ << std::endl;
	return 0;
}

C

Meaning

  • n n n 个二元组,第 i i i 个形如 ( a i , w i ) (a_i,w_i) (ai,wi)
  • a i a_i ai 是一个 0 / 1 0/1 0/1
  • w i w_i wi 是一个正整数
  • 接下来有 m m m 次操作
  • 每次操作会随机地选择一个二元组,其中选出第 i i i 个二元组的概率为
  • w i ∑ j = 1 n w j \frac{w_i}{\sum_{j=1}^nw_j} j=1nwjwi
  • 如果选出的二元组的 a a a 0 0 0 ,把这个二元组的 w w w 减一
  • 否则选出的二元组的 a a a 1 1 1 ,把这个二元组的 w w w 加一
  • m m m 次操作后每个二元组 w w w 的期望值
  • 1 ≤ n ≤ 2 × 1 0 5 1\le n\le2\times10^5 1n2×105 1 ≤ m ≤ 3000 1\le m\le3000 1m3000

Solution

  • 介绍一种做法,(可能)比原题解好推一些?
  • 先定义状态 f [ i ] [ j ] f[i][j] f[i][j] 表示 i i i 次操作中选出 a a a 0 0 0 的二元组恰好 j j j 次的概率
  • f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
  • f [ i + 1 ] [ j ] + = s 1 + i − j s 0 + s 1 + i − 2 j f [ i ] [ j ] f[i+1][j]+=\frac{s_1+i-j}{s_0+s_1+i-2j}f[i][j] f[i+1][j]+=s0+s1+i2js1+ijf[i][j]
  • f [ i + 1 ] [ j + 1 ] + = s 0 − j s 0 + s 1 + i − 2 j f [ i ] [ j ] f[i+1][j+1]+=\frac{s_0-j}{s_0+s_1+i-2j}f[i][j] f[i+1][j+1]+=s0+s1+i2js0jf[i][j]
  • 其中 s 0 = ∑ a i = 0 w i s_0=\sum_{a_i=0}w_i s0=ai=0wi s 1 = ∑ a i = 1 w i s_1=\sum_{a_i=1}w_i s1=ai=1wi
  • 乍一看这个 DP 好像没什么用,不妨分析一下性质
  • a i = 1 a_i=1 ai=1 为例,可以发现一个很优秀的性质
  • 如果某一次选出了的二元组的 a a a 0 0 0 ,那么在下一次选出的 a 为 1 的条件下,选到的是第 i i i 个二元组的条件概率不变,仍然为现在的 w i w_i wi 除以现在 a = 1 a=1 a=1 w w w 之和
  • a i = 0 a_i=0 ai=0 同理
  • 定义一次操作「对 x x x 有效」,当且仅当这次操作选出的二元组的 a a a 和第 x x x 个二元组的 a a a 一样
  • 再设状态 g x [ i ] g_x[i] gx[i] 表示第 x x x 个二元组,在 i i i 次对 x x x 有效的操作之后 w x w_x wx 的期望
  • 答案就很好算了
  • 如果 a x = 0 a_x=0 ax=0 则最后 w x w_x wx 的期望为
  • ∑ i = 0 m f [ m ] [ i ] × g x [ i ] \sum_{i=0}^mf[m][i]\times g_x[i] i=0mf[m][i]×gx[i]
  • 否则 a x = 1 a_x=1 ax=1
  • ∑ i = 0 m f [ m ] [ i ] × g x [ m − i ] \sum_{i=0}^mf[m][i]\times g_x[m-i] i=0mf[m][i]×gx[mi]
  • 考虑怎么求这个 g x [ i ] g_x[i] gx[i] ,直接求和算答案是 O ( n m ) O(nm) O(nm) ,无法通过 C2
  • a x = 1 a_x=1 ax=1 为例
  • g x [ 0 ] = w x g_x[0]=w_x gx[0]=wx
  • g x [ i ] = g x [ i − 1 ] + g x [ i − 1 ] s 1 + i − 1 g_x[i]=g_x[i-1]+\frac{g_x[i-1]}{s_1+i-1} gx[i]=gx[i1]+s1+i1gx[i1]
  • a x = 0 a_x=0 ax=0
  • g x [ i ] = g x [ i − 1 ] − g x [ i − 1 ] s 0 − i + 1 g_x[i]=g_x[i-1]-\frac{g_x[i-1]}{s_0-i+1} gx[i]=gx[i1]s0i+1gx[i1]
  • i − 1 i-1 i1 次操作之后的期望再加上第 i i i 次选出第 i i i 个二元组的概率
  • 看上去还是 O ( n m ) O(nm) O(nm) 的,所以我们需要找性质
  • 把两个式子整理一下
  • g x [ i ] = g x [ i − 1 ] × { s 0 − i s 0 − i + 1 a x = 0 s 1 + i s 1 + i − 1 a x = 1 g_x[i]=g_x[i-1]\times\begin{cases}\frac{s_0-i}{s_0-i+1}&amp;a_x=0\\\frac{s_1+i}{s_1+i-1}&amp;a_x=1\end{cases} gx[i]=gx[i1]×{s0i+1s0is1+i1s1+iax=0ax=1
  • 根据常识
  • g x [ i ] = w x × { s 0 − i s 0 a x = 0 s 1 + i s 1 a x = 1 g_x[i]=w_x\times\begin{cases}\frac{s_0-i}{s_0}&amp;a_x=0\\\frac{s_1+i}{s_1}&amp;a_x=1\end{cases} gx[i]=wx×{s0s0is1s1+iax=0ax=1
  • 代回答案,当 a x = 0 a_x=0 ax=0 时答案为
  • ∑ i = 0 m f [ m ] [ i ] × w x × s 0 − i s 0 = w x × ( ∑ i = 0 m f [ m ] [ i ] − ∑ i = 0 m i × f [ m ] [ i ] s 0 ) \sum_{i=0}^mf[m][i]\times w_x\times\frac{s_0-i}{s_0}=w_x\times(\sum_{i=0}^mf[m][i]-\frac{\sum_{i=0}^mi\times f[m][i]}{s_0}) i=0mf[m][i]×wx×s0s0i=wx×(i=0mf[m][i]s0i=0mi×f[m][i])
  • 同理 a x = 1 a_x=1 ax=1
  • ∑ i = 0 m f [ m ] [ i ] × w x × s 1 + m − i s 1 = w x × ( ∑ i = 0 m f [ m ] [ i ] + ∑ i = 0 m ( m − i ) × f [ m ] [ i ] s 1 ) \sum_{i=0}^mf[m][i]\times w_x\times\frac{s_1+m-i}{s_1}=w_x\times(\sum_{i=0}^mf[m][i]+\frac{\sum_{i=0}^m(m-i)\times f[m][i]}{s_1}) i=0mf[m][i]×wx×s1s1+mi=wx×(i=0mf[m][i]+s1i=0m(mi)×f[m][i])
  • ∑ i = 0 m f [ m ] [ i ] \sum_{i=0}^mf[m][i] i=0mf[m][i] ∑ i = 0 m i × f [ m ] [ i ] \sum_{i=0}^mi\times f[m][i] i=0mi×f[m][i] ∑ i = 0 m ( m − i ) × f [ m ] [ i ] \sum_{i=0}^m(m-i)\times f[m][i] i=0m(mi)×f[m][i] 可以 DP 后预处理直接使用
  • O ( n + m 2 log ⁡ ( s 0 + s 1 + m ) ) O(n+m^2\log(s_0+s_1+m)) O(n+m2log(s0+s1+m)) (DP 时需要求逆元)
  • 如果预处理 s 0 + s 1 ± m s_0+s_1\pm m s0+s1±m 的逆元可以做到 O ( n + m 2 ) O(n+m^2) O(n+m2)

Code

#include <bits/stdc++.h>

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 = 2e5 + 5, M = 3005, ZZQ = 998244353;

int n, m, a[N], b[N], s0, s1, f[M][M], ft, tft, tfmt, i0, i1;

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % ZZQ;
		a = 1ll * a * a % ZZQ;
		b >>= 1;
	}
	return res;
}

int main()
{
	n = read(); m = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i <= n; i++)
	{
		b[i] = read();
		if (a[i]) s1 += b[i];
		else s0 += b[i];
	}
	f[0][0] = 1;
	for (int i = 0; i < m; i++)
		for (int j = 0; j <= i; j++)
		{
			int it = qpow(s0 - j + s1 + i - j, ZZQ - 2);
			f[i + 1][j] = (1ll * (s1 + i - j) * it % ZZQ *
				f[i][j] + f[i + 1][j]) % ZZQ;
			f[i + 1][j + 1] = (1ll * (s0 - j) * it % ZZQ *
				f[i][j] + f[i + 1][j + 1]) % ZZQ;
		}
	for (int i = 0; i <= m; i++)
	{
		ft = (ft + f[m][i]) % ZZQ;
		tft = (1ll * i * f[m][i] + tft) % ZZQ;
		tfmt = (1ll * (m - i) * f[m][i] + tfmt) % ZZQ;
	}
	i0 = qpow(s0, ZZQ - 2); i1 = qpow(s1, ZZQ - 2);
	for (int i = 1; i <= n; i++)
		if (a[i]) printf("%d\n", (1ll * b[i] * ft + 1ll * b[i]
			* i1 % ZZQ * tfmt) % ZZQ);
		else printf("%d\n", (1ll * b[i] * ft - 1ll * b[i]
			* i0 % ZZQ * tft % ZZQ + ZZQ) % ZZQ);
	return 0;
}

D

Meaning

  • 一个 n × n n\times n n×n 的网格,要求在一些格子上放传送门
  • 一对传送门 ( A , B ) (A,B) (A,B) 表示走到 A A A 会传送到 B B B ,走到 B B B 会传送到 A A A
  • 现在需要构造一种放置传送门的方案(必须成对放,一个格子不能放超过一个传送门)
  • 使得对于所有的 i i i ,从第 1 1 1 列第 i i i 个格子出发一直向右走最终达到第 n n n 列第 r i r_i ri 个格子,并且从第 1 1 1 行第 i i i 个格子出发一直向下走最终达到第 n n n 行第 c i c_i ci 个格子
  • 1 ≤ n ≤ 1000 1\le n\le 1000 1n1000
  • r r r c c c 都是 1 1 1 n n n 的排列

Solution

  • 考虑如何缩减问题的规模从 n × n n\times n n×n ( n − 1 ) × ( n − 1 ) (n-1)\times(n-1) (n1)×(n1)
  • 如果 r 1 = c 1 = 1 r_1=c_1=1 r1=c1=1 跳过
  • 否则如果 r x = c y = 1 r_x=c_y=1 rx=cy=1 ,那么放一对传送门,位置为 ( x , 1 ) ( 1 , y ) (x,1)(1,y) (x,1)(1,y)
  • 这样显然从第 ( x , 1 ) (x,1) (x,1) 出发会最终到达 ( 1 , n ) (1,n) (1,n) ( 1 , y ) (1,y) (1,y) 出发最终达到 ( n , 1 ) (n,1) (n,1)
  • ( 1 , 1 ) (1,1) (1,1) 向右出发会穿越到 ( x , 2 ) (x,2) (x,2) ,向下出发会穿越到 ( 2 , y ) (2,y) (2,y)
  • 这就是一个规模小 1 1 1 的问题!!!!!
  • O ( n ) O(n) O(n)
  • 但 SPJ 是 O ( n 2 ) O(n^2) O(n2)

Code

#include <bits/stdc++.h>

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, ir[N], ic[N], a[N], ia[N], b[N], ib[N],
ans1x[N], ans1y[N], ans2x[N], ans2y[N];

int main()
{
	n = read();
	for (int i = 1; i <= n; i++) ir[read()] = a[i] = ia[i] = i;
	for (int i = 1; i <= n; i++) ic[read()] = b[i] = ib[i] = i;
	for (int i = 1; i <= n; i++)
	{
		if (ir[i] == a[i] && ic[i] == b[i]) continue;
		int x = ia[ir[i]], y = ib[ic[i]];
		ans1x[++m] = x; ans1y[m] = ans2x[m] = i;
		ans2y[m] = y;
		a[x] = a[i]; b[y] = b[i];
		ia[a[i]] = x; ib[b[i]] = y;
	}
	std::cout << m << std::endl;
	for (int i = 1; i <= m; i++)
		printf("%d %d %d %d\n", ans1x[i], ans1y[i], ans2x[i], ans2y[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值