2020ICPC上海区域赛题解BDEGHILM

本文详细解析了2020年ICPC上海区域赛中涉及的数学和算法问题,包括Fibonacci数列、扫雷游戏、几何与递推等。通过分析题目的思路和解决方案,展示了数学在解决复杂计算问题中的关键作用,并提供了高效算法的实现。
摘要由CSDN通过智能技术生成

2020ICPC上海区域赛题解

题目链接:
https://codeforces.com/gym/102900

讲义、代码链接:
https://hytidel.lanzoub.com/b031byqcd
密码:1ob5

视频讲解链接:
https://www.bilibili.com/video/BV1SG411b7uF



G. Fibonacci

题意

Fibonacci数列 f n f_n fn满足: f 1 = f 2 = 1 , f n = f n − 2 + f n − 1    ( n ≥ 3 ) f_1=f_2=1,f_n=f_{n-2}+f_{n-1}\ \ (n\geq 3) f1=f2=1,fn=fn2+fn1  (n3),它的前几项是 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , ⋯ 1,1,2,3,5,8,13,21,\cdots 1,1,2,3,5,8,13,21,.定义函数 g ( x , y ) = [ x ⋅ y 是偶数 ] g(x,y)=[x\cdot y是偶数] g(x,y)=[xy是偶数].给定 n    ( 1 ≤ n ≤ 1 e 9 ) n\ \ (1\leq n\leq 1\mathrm{e}9) n  (1n1e9),求 ∑ i = 1 n ∑ j = i + 1 n g ( f i , f j ) \displaystyle \sum_{i=1}^n\sum_{j=i+1}^n g(f_i,f_j) i=1nj=i+1ng(fi,fj).

思路I

观察知:Fibonacci数列的项有奇、奇、偶、奇、奇、偶、 ⋯ \cdots 的规律,这可用数学归纳法证明.

显然 f n f_n fn为偶数当且仅当 n   %   3 = 0 n\ \%\ 3=0 n % 3=0.

奇数 × 偶数 = 偶数 , 偶数 × 奇数 = 偶数 , 偶数 × 偶数 = 偶数 奇数\times 偶数=偶数,偶数\times奇数=偶数,偶数\times 偶数=偶数 奇数×偶数=偶数,偶数×奇数=偶数,偶数×偶数=偶数,则对每个值为奇数的项,考察其后面有几个值为偶数的项;对每个值为偶数的项,因它能与后面的任意项配对,只需考察其后面有几项.

①值为奇数的项:将Fibonacci数列每三项作为一个block,即 b l o c k 1 = { f 1 , f 2 , f 3 } , b l o c k 2 = { f 4 , f 5 , f 6 } , ⋯ block_1=\{f_1,f_2,f_3\},block_2=\{f_4,f_5,f_6\},\cdots block1={f1,f2,f3},block2={f4,f5,f6},,显然这样的block的个数等于 f 1 ∼ f n f_1\sim f_n f1fn中值为偶数的项的个数,即 c n t = ⌊ n 3 ⌋ cnt=\left\lfloor\dfrac{n}{3}\right\rfloor cnt=3n,余下 r e s t = n   %   3 rest=n\ \%\ 3 rest=n % 3个单独的值为奇数的项.对 b l o c k 1 block_1 block1中的两个奇数,它们都能与 b l o c k 1 ∼ b l o c k c n t block_1\sim block_{cnt} block1blockcnt中的偶数配对,它们对答案的贡献即 b l o c k 1 ∼ b l o c k c n t block_1\sim block_{cnt} block1blockcnt中值为偶数的项的个数,即 c n t cnt cnt;对 b l o c k 2 block_2 block2中的两个奇数,它们都能与 b l o c k 2 ∼ b l o c k c n t block_2\sim block_{cnt} block2blockcnt中的偶数配对,它们对答案的贡献即 b l o c k 2 ∼ b l o c k c n t block_2\sim block_{cnt} block2blockcnt中值为偶数的项的个数,即 c n t − 1 cnt-1 cnt1; ⋯ \cdots ,故所有block中的奇数项对答案的贡献为 2 [ c n t + ( c n t − 1 ) + ⋯ + 1 ] = c n t ( c n t + 1 ) 2[cnt+(cnt-1)+\cdots+1]=cnt(cnt+1) 2[cnt+(cnt1)++1]=cnt(cnt+1).显然余下的 r e s t rest rest个单独的值为奇数的项对答案无贡献.

②值为偶数的项:显然每个值为偶数的项只能是每个block的第三个元素.对 b l o c k 1 block_1 block1中的值为偶数的项,能与其后面的 ( n − 3 ) (n-3) (n3)项配对;对 b l o c k 2 block_2 block2中的值为偶数的项,能与其后面的 ( n − 6 ) (n-6) (n6)项配对; ⋯ \cdots ;对 b l o c k c n t block_{cnt} blockcnt中值为偶数的项,能与其后面的 r e s t rest rest项配对,故所有值为偶数的项对答案的贡献为 ( n − 3 ) + ( n − 6 ) + ⋯ + ( n − 3 ⋅ c n t ) = c n t ⋅ ( n − 3 + n − 3 ⋅ c n t ) 2 (n-3)+(n-6)+\cdots+(n-3\cdot cnt)=\dfrac{cnt\cdot (n-3+n-3\cdot cnt)}{2} (n3)+(n6)++(n3cnt)=2cnt(n3+n3cnt).

考察答案的数据范围.最坏的情况每个 g ( f i , f j ) g(f_i,f_j) g(fi,fj)都为 1 1 1,则 a n s = ( n − 1 ) + ( n − 2 ) + ⋯ + 1 = n ( n − 1 ) 2 ans=(n-1)+(n-2)+\cdots+1=\dfrac{n(n-1)}{2} ans=(n1)+(n2)++1=2n(n1), n n n最大为 1 e 9 1\mathrm{e}9 1e9,则真实答案远小于 1 e 18 1\mathrm{e}18 1e18.

∑ i = 1 n ∑ j = i + 1 n g ( f i , f j ) \displaystyle \sum_{i=1}^n\sum_{j=i+1}^n g(f_i,f_j) i=1nj=i+1ng(fi,fj)中匹配的项有如下形式:

f 1 f_1 f1 f 2 f_2 f2 f 3 f_3 f3 f 4 f_4 f4 f 5 f_5 f5 f 6 f_6 f6 ⋯ \cdots
f 2 f_2 f2 f 3 f_3 f3 f 4 f_4 f4 f 5 f_5 f5 f 6 f_6 f6 ⋯ \cdots
f 3 f_3 f3 f 4 f_4 f4 f 5 f_5 f5 f 6 f_6 f6 ⋯ \cdots
f 4 f_4 f4 f 5 f_5 f5 f 6 f_6 f6 ⋯ \cdots

考察 n ∑ j = 2 n g ( f 1 , f j ) \displaystyle n\sum_{j=2}^n g(f_1,f_j) nj=2ng(f1,fj),即每一行都补为第一行的情况,则 a n s ans ans约为此时的答案的 1 2 \dfrac{1}{2} 21.第一行的答案为 2 ⋅ ⌊ n 3 ⌋ + ⌊ n 3 ⌋ ⋅ ( 2 n − 3 ⋅ ⌊ n 3 ⌋ − 3 ) 2 ∼ 1 2 n 2 + 2 3 n 2\cdot\left\lfloor\dfrac{n}{3}\right\rfloor+\dfrac{\left\lfloor\dfrac{n}{3}\right\rfloor\cdot \left(2n-3\cdot \left\lfloor\dfrac{n}{3}\right\rfloor-3\right)}{2}\sim \dfrac{1}{2}n^2+\dfrac{2}{3}n 23n+23n(2n33n3)21n2+32n,令 n = 1 e 9 n=1\mathrm{e}9 n=1e9时该值约 5 e 17 + 7 e 8 5\mathrm{e}17+7\mathrm{e}8 5e17+7e8,则真实答案约为 2.5 e 17 + 3.5 e 8 2.5\mathrm{e}17+3.5\mathrm{e}8 2.5e17+3.5e8,要开ll.实际 n = 1 e 9 n=1\mathrm{e}9 n=1e9时的答案为 277 , 777 , 777 , 388 , 888 , 889 277,777,777,388,888,889 277,777,777,388,888,889,与估计值接近.

代码I -> 2020ICPC上海-G(推公式I)

int main() {
	int n; cin >> n;

	int cnt = n / 3;
	ll ans = (ll)cnt * (cnt + 1);
	ans += (ll)cnt * (2 * n - 3 - 3 * cnt) / 2;
	cout << ans;
}

思路II

注意到Fibonacci数列的前 n n n项中偶数的个数 c n t = ⌊ n 3 ⌋ cnt=\left\lfloor\dfrac{n}{3}\right\rfloor cnt=3n,则奇数的个数为 ( n − c n t ) (n-cnt) (ncnt).奇数与偶数的匹配数为 ( n − c n t ) ⋅ c n t (n-cnt)\cdot cnt (ncnt)cnt,偶数与偶数的匹配数为 ( c n t − 1 ) + ( c n t − 2 ) + ⋯ + 1 = ( c n t − 1 ) ⋅ ( c n t − 1 + 1 ) 2 = c n t ⋅ ( c n t − 1 ) 2 (cnt-1)+(cnt-2)+\cdots+1=\dfrac{(cnt-1)\cdot (cnt-1+1)}{2}=\dfrac{cnt\cdot(cnt-1)}{2} (cnt1)+(cnt2)++1=2(cnt1)(cnt1+1)=2cnt(cnt1).

代码II -> 2020ICPC上海-G(推公式II)

int main() {
	int n; cin >> n;

	int cnt = n / 3;
	ll ans = (ll)cnt * (n - cnt) + (ll)cnt * (cnt - 1) / 2;
	cout << ans;
}


B. Mine Sweeper II

题意

给定一个 n × m n\times m n×m的网格作为扫雷的棋盘,每个格子是空格或雷之一,其中空格上有一个数字,表示其周围的 8 8 8个格子中雷的数量.记一个棋盘中所有空格的数字之和为 s u m sum sum.给定 n × m n\times m n×m的棋盘 A A A和棋盘 B B B,现有操作:选取 B B B中的一个格子,将其反转,即空格变为雷,雷变为空格,同时更新空格上的数字.问经过不超过 ⌊ n m 2 ⌋ \left\lfloor\dfrac{nm}{2}\right\rfloor 2nm次操作后能否使得棋盘 A A A和棋盘 B B B s u m sum sum相等.

第一行输入整数 n , m    ( 1 ≤ n , m ≤ 1000 ) n,m\ \ (1\leq n,m\leq 1000) n,m  (1n,m1000).接下来输入一个 n × m n\times m n×m的字符矩阵表示棋盘 A A A,其中字符’X’表示雷,字符’.'表示空格.接下来输入一个 n × m n\times m n×m的字符矩阵表示棋盘 B B B.

若能经过不超过 ⌊ n m 2 ⌋ \left\lfloor\dfrac{nm}{2}\right\rfloor 2nm次操作后能否使得棋盘 A A A和棋盘 B B B s u m sum sum相等,输出任一个操作后满足要求的棋盘 B B B;否则输出 − 1 -1 1.

思路

对一个棋盘 A A A,称将所有空格变为雷、所有雷变为空格的棋盘为 A A A的反,记作 A ′ A' A.

s u m ( A ) = s u m ( A ′ ) sum(A)=sum(A') sum(A)=sum(A).

[] A ′ A' A中每个空格的数字代表方格周围的雷数,而 A ′ A' A中的空格是 A A A中的雷, A ′ A' A中的雷是 A A A中的空格,

A ′ A' A中空格的数字的定义与 A A A中空格的定义相同,故 s u m ( A ) = s u m ( A ′ ) sum(A)=sum(A') sum(A)=sum(A).

棋盘 A A A和它的反 A ′ A' A中至少有一个与棋盘 B B B不同的字符数不超过 ⌊ n m 2 ⌋ \left\lfloor\dfrac{nm}{2}\right\rfloor 2nm,故经过不超过 ⌊ n m 2 ⌋ \left\lfloor\dfrac{nm}{2}\right\rfloor 2nm次操作能将 B B B变为 A A A A ′ A' A,进而使得 s u n ( B ) = s u m ( A ) sun(B)=sum(A) sun(B)=sum(A),即一定有解.输入 B B B时统计 B B B A A A不同的字符数,若不同的字符数不超过 ⌊ n m 2 ⌋ \left\lfloor\dfrac{nm}{2}\right\rfloor 2nm,则输出 A A A;否则输出 A ′ A' A.

代码 -> 2020ICPC上海-B(思维)

const int MAXN = 1005;
char A[MAXN][MAXN], revA[MAXN][MAXN];  // 棋盘A和A的反
char B[MAXN][MAXN];  // 棋盘B

int main() {
	int n, m; cin >> n >> m;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			cin >> A[i][j];
			revA[i][j] = A[i][j] == '.' ? 'X' : '.';
		}
	}

	int cnt = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			cin >> B[i][j];
			if (B[i][j] != A[i][j]) cnt++;
		}
	}

	if (cnt <= n * m / 2) 
		for (int i = 0; i < n; i++) cout << A[i] << endl;
	else
		for (int i = 0; i < n; i++) cout << revA[i] << endl;
}


M. Gitignore

题意

t t t组测试数据.每组测试数据输入 n n n个要删除的文件的路径和 m    ( 1 ≤ n + m ≤ 100 ) m\ \ (1\leq n+m\leq100) m  (1n+m100)个受保护的文件的路径.每次操作可删除一个不受保护的文件或文件夹,问删除所有要删除的文件至少需几次操作.

思路

以每个文件夹和文件为节点,文件夹与文件的包含关系为边,则所有文件的路径构成森林.

用vector<string> path记录每个要删除的文件的每一级的路径,不含最后的文件名,如路径first/second/third只存first和firstsecond.

显然一定有解,最坏可逐个删除 n n n个文件.考虑 a n s ans ans n n n开始,考察有哪些文件的删除可合并为删除它们所在的文件夹.

用map<string,int> state记录每个文件夹或文件的状态, 0 0 0表示无限制, 1 1 1表示受保护, 2 2 2表示之前 s t a t e = 0 state=0 state=0且被遍历过一次.显然所有受保护的文件的路径上的所有文件夹都不能被删除.第一次遍历到 s t a t e state state 0 0 0的路径时将其 s t a t e state state置为 2 2 2.遍历到 s t a t e state state 2 2 2的路径,即第二次遍历到原 s t a t e = 0 state=0 state=0的路径时,可删除整个文件夹,即路径之后的所有内容,并 a n s − − ans-- ans.

代码 -> 2020ICPC上海-M(思维+map)

const int MAXN = 105;
vector<string> path[MAXN];  // 要删除的文件的路径,不含最后的文件名

int main() {
	CaseT{
		int n,m; cin >> n >> m;
		for (int i = 0; i < n; i++) path[i].clear();

		map<string, int> state;  // 文件夹名或文件名的状态,0表示无限制,1表示受保护,2表示之前为0且遍历过一次

		for (int i = 0; i < n; i++) {
			string line; cin >> line;
			string tmp;
			for (auto item : line) {
				if (item != '/') tmp.push_back(item);
				else {
					path[i].push_back(tmp);
					state[tmp] = 0;
				}
			}
		}

		while (m--) {
			string line; cin >> line;
			string tmp;
			for (auto item : line) {
				if (item != '/') tmp.push_back(item);
				else state[tmp] = 1;
			}
		}

		int ans = n;
		for (int i = 0; i < n; i++) {
			for (auto item : path[i]) {
				if (state[item] == 1) continue;  // 受保护
				else if (state[item] == 0) state[item] = 2;  // state为0且第一次被遍历到
				else {
					ans--;  // state之前为0且第二次被遍历到
					break;  // 删除整个文件夹,即路径之后的所有内容
				}
			}
		}
		cout << ans << endl;
	}
}


D. Walker

题意

两个人在线段 [ 0 , n ] [0,n] [0,n]上,第一个人在位置 p 1 p_1 p1处,速度为 v 1 v_1 v1;第二个人在位置 p 2 p_2 p2处,速度为 v 2 v_2 v2.两人同时出发,直至线段上的每个点至少有一个人走过时停止,求所需的最短时间.

T    ( 1 ≤ T ≤ 1 e 4 ) T\ \ (1\leq T\leq 1\mathrm{e}4) T  (1T1e4)组测试数据.每组测试数据输入五个实数 n , p 1 , v 1 , p 2 , v 2    ( 1 ≤ n ≤ 1 e 4 , 0 ≤ p 1 , p 2 ≤ n , 0.001 ≤ v 1 , v 2 ≤ 1000 ) n,p_1,v_1,p_2,v_2\ \ (1\leq n\leq 1\mathrm{e}4,0\leq p_1,p_2\leq n,0.001\leq v_1,v_2\leq 1000) n,p1,v1,p2,v2  (1n1e4,0p1,p2n,0.001v1,v21000),每个实数至多精确到小数点后三位.

对每组测试数据,输出一个实数,表示所需的最短时间,误差不超过 1 e − 6 1\mathrm{e}-6 1e6.

思路

不妨设 p 1 ≤ p 2 p_1\leq p_2 p1p2,否则交换 ( p 1 , p 2 ) (p_1,p_2) (p1,p2) ( v 1 , v 2 ) (v_1,v_2) (v1,v2).

有如下三种情况:

①其中一个人走得很快,线段可视为都由他遍历,此时他应先走到离他最近的线段端点处,再转身走完整条线段.

​ 此时 a n s = min ⁡ { min ⁡ { n − p 1 , p 1 } + n v 1 , min ⁡ { n − p 2 , p 2 } + n v 2 } ans=\min\left\{\dfrac{\min\{n-p_1,p_1\}+n}{v_1},\dfrac{\min\{n-p_2,p_2\}+n}{v_2}\right\} ans=min{v1min{np1,p1}+n,v2min{np2,p2}+n}.

②两人相向出发,各自走到自己所面向的线段的端点处.此时 a n s = max ⁡ { n − p 1 v 1 , p 2 v 2 } ans=\max\left\{\dfrac{n-p_1}{v_1},\dfrac{p_2}{v_2}\right\} ans=max{v1np1,v2p2}.

③以点 x = m i d ∈ [ p 1 , p 2 ] x=mid\in[p_1,p_2] x=mid[p1,p2]为分界,第一个人负责走 [ 0 , m i d ] [0,mid] [0,mid],第二个人负责走 [ m i d , n ] [mid,n] [mid,n].设他们所需的时间分别为 t 1 , t 2 t_1,t_2 t1,t2,则 a n s = max ⁡ { t 1 , t 2 } ans=\max\{t_1,t_2\} ans=max{t1,t2}.二分 m i d mid mid,找到 a n s ans ans的最小值.

​ 考虑二分的迭代次数.最坏的情况一人在线段的左端点,一人在线段的右端点,此时二分的初始区间长度为 1 e 4 1\mathrm{e}4 1e4.为保证误差不超过 1 e − 6 1\mathrm{e}-6 1e6,则迭代次数 m m m满足 2 m < 1 e 4 1 e − 12 = 1 e 16 2^m<\dfrac{1\mathrm{e}4}{1\mathrm{e}-12}=1\mathrm{e}16 2m<1e121e4=1e16,则 m > 53 m>53 m>53,取 m = 54 m=54 m=54即可.

代码 -> 2020ICPC上海-D(二分) By : 狙击美佐

double cal(double length, double pos, double speed) {  // 初始时在pos处的人以速率speed走完length所需的时间
	return (min(length - pos, pos) + length) / speed;
}

int main() {
	CaseT{
		double n,p1,v1,p2,v2; cin >> n >> p1 >> v1 >> p2 >> v2;
		if (p1 > p2) swap(p1, p2), swap(v1, v2);  // 保证p1在左边

		double ans = min(cal(n, p1, v1), cal(n, p2, v2));  // 一个人走完
		ans = min(ans, max((n - p1) / v1, p2 / v2));  // 两人相向走完

		double l = p1, r = p2;
		for (int i = 1; i <= 54; i++) {
			double mid = (l + r) / 2;
			double t1 = cal(mid, p1, v1), t2 = cal(n - mid, p2 - mid, v2);  // 两人所需的时间
			ans = min(ans, max(t1, t2));
			if (cmp(t1, t2) <= 0) l = mid;
			else r = mid;
		}
		cout << fixed << setprecision(12) << ans << endl;
	}
}


I. Sky Garden

题意

有圆心都在 ( 0 , 0 ) (0,0) (0,0)处的、半径分别为 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n n n n个同心圆.有 m m m条穿过圆心的直线将每个圆等分为 2 m 2m 2m段弧.每个圆和直线都是道路.设 P P P是所有不同道路的交点构成的集合.对两点 a , b ∈ P a,b\in P a,bP,定义 d i s ( a , b ) dis(a,b) dis(a,b)为从 a a a点沿道路走到 b b b点的最短距离.给定 n , m    ( 1 ≤ n , m ≤ 500 ) n,m\ \ (1\leq n,m\leq 500) n,m  (1n,m500),求 P P P中任意两点的距离之和,误差不超过 1 e − 6 1\mathrm{e}-6 1e6.

思路

在这里插入图片描述

不同交点的 d i s dis dis分为如下两种情况:

①两点在同一圆上时(如上图中红色和蓝色的点),设两点对应的圆心角为 α \alpha α,则走弧长为 r α r\alpha rα,走半径为 2 r 2r 2r.

​ 令 r α ≤ 2 r ⇒ α ≤ 2 r\alpha\leq 2r\Rightarrow\alpha\leq 2 rα2rα2,即当且仅当圆心角 α ≤ 2 \alpha\leq 2 α2时走弧长更优(红色),否则走半径更优(蓝色).

②两点在不同圆上时(如上图中绿色的点),在外圆上的点需先经半径到达内圆上的点所在的圆,此时化为①的情况.

考虑如何用内圆的信息更新出外圆的信息. d i s 1 [ i ] dis1[i] dis1[i]表示在第 1 ∼ i 1\sim i 1i个圆上从一个交点出发到其他交点的 d i s dis dis之和, d i s 2 [ i ] dis2[i] dis2[i]表示在第 i i i个圆上从一个交点出发到该圆上的其他交点的 d i s dis dis之和.

(1) i = 1 i=1 i=1时,以 m = 2 m=2 m=2为例.从半径为 1 1 1的圆上的交点 S S S出发,它到其他交点 A 1 , A 2 , A 3 A_1,A_2,A_3 A1,A2,A3的距离之和等于 S S S到关于圆心的对称点的距离( 2 r 2r 2r) + + +它到半圆上的其他交点的距离之和 × 2 \times 2 ×2.

在这里插入图片描述

(2) i ≥ 2 i\geq 2 i2时考虑递推.

​ ①对 d i s 2 [ i ] dis2[i] dis2[i],第 i i i个圆是第 1 1 1个圆以圆心为位似中心放大 i i i倍的结果,则第 i i i个圆上两点的相对位置与第 1 1 1个圆上两点的相对位置相同,故 d i s 2 [ i ] = d i s 2 [ 1 ] ∗ i dis2[i]=dis2[1]*i dis2[i]=dis2[1]i.

​ ②对 d i s 1 [ i ] dis1[i] dis1[i],它等于第 1 ∼ ( i − 1 ) 1\sim (i-1) 1(i1)个圆上从一个交点出发到其他交点的 d i s dis dis之和 + + +在第 i i i个圆上从一个交点出发到该圆上其他交点的 d i s dis dis之和 + + + i i i个圆上的点到第 1 ∼ ( i − 1 ) 1\sim (i-1) 1(i1)个圆上的交点的距离之和.前两项分别为 d i s 1 [ i − 1 ] dis1[i-1] dis1[i1] d i s 2 [ i ] dis2[i] dis2[i],第三项只需将第 2 ∼ i 2\sim i 2i个圆上的点沿半径退到第 1 ∼ ( i − 1 ) 1\sim (i-1) 1(i1)个圆即可.

对每一层统计答案.第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)层对答案的贡献为第 i i i个圆上的交点到圆内其他交点的距离之和 + + + i i i个圆上任意两交点的距离之和,其中第一项为 2 m ( d i s 1 [ i ] − d i s 2 [ i ] ) 2m(dis1[i]-dis2[i]) 2m(dis1[i]dis2[i]),第二项为 2 m ⋅ d i s 2 [ i ] 2 \dfrac{2m\cdot dis2[i]}{2} 22mdis2[i],其中除以 2 2 2是因为对圆上的点 A A A B B B,有 d i s ( A , B ) = d i s ( B , A ) dis(A,B)=dis(B,A) dis(A,B)=dis(B,A),故距离会多算一倍. m ≥ 2 m\geq 2 m2时,圆心处有交点,它对答案的贡献是 2 m 2m 2m倍的各半径的长度.注意特判 m = 1 m=1 m=1的情况,此时圆心处无交点.

总时间复杂度 O ( n ) O(n) O(n).

代码 -> 2020ICPC上海-I(几何+递推) By : 狙击美佐

const int MAXN = 505;
int n, m;
double dis1[MAXN];  // dis1[i]表示在第1~i个圆上从一个点出发到其他交点的dis之和
double dis2[MAXN];  // dis2[i]表示在第i个圆上从一个点出发到该圆上的其他交点的dis之和

int main() {
	cin >> n >> m;

	double d = 0;  // 在第1个圆上从一个点出发到其他交点的dis之和
	for (int r = 1; r < m; r++) {  // 起点到一个半圆上的交点的距离之和
		if (r * pi < 2 * m) d += r * pi / m;  // 走弧长
		else d += 2;  // 走半径
	}
	d *= 2;  // 起点到另一个半圆上的交点的距离之和
	d += 2;  // 起点到其关于圆心的对称点的距离

	dis1[1] = dis2[1] = d;  // 初始条件
	for (int i = 2; i <= n; i++) {  // 递推出其他圆
		dis2[i] = dis2[1] * i;
		dis1[i] = dis1[i - 1] + dis2[i] + 2 * m * (i - 1);
	}

	double ans = 0;
	for (int i = 1; i <= n; i++) 
		ans += 2 * m * (dis1[i] - dis2[i]) + m * dis2[i] + (m > 1) * (2 * m * i);
	cout << fixed << setprecision(12) << ans;	
}


H. Rice Arrangement

题意

有一个边缘是圆形的可转动的桌子,在该圆的内接正 n n n边形的每个顶点处有一把椅子,逆时针编号 0 ∼ ( n − 1 ) 0\sim (n-1) 0(n1).初始时,第 i    ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i  (0in1)号碗在桌子上的第 b i    ( 0 ≤ b i ≤ n − 1 ) b_i\ \ (0\leq b_i\leq n-1) bi  (0bin1)个位置,第 i    ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i  (0in1)个客人坐在第 a i    ( 0 ≤ a i ≤ n ) a_i\ \ (0\leq a_i\leq n) ai  (0ain)个位置.现有两种操作:①顺时针旋转桌子:第 b i    ( 1 ≤ i ≤ k ) b_i\ \ (1\leq i\leq k) bi  (1ik)个碗会被转到第 ( b i + 1 )   m o d   n (b_i+1)\ \mathrm{mod}\ n (bi+1) mod n个位置;②逆时针旋转桌子:第 b i    ( 1 ≤ i ≤ k ) b_i\ \ (1\leq i\leq k) bi  (1ik)个碗会被转到第 ( b i − 1 )   m o d   n (b_i-1)\ \mathrm{mod}\ n (bi1) mod n个位置.当碗转到某客人面前时,他可选择拿走该碗或等待其他碗.求让所有客人都拿到碗至少需要几次操作.

t t t组测试数据.每组测试数据第一行输入两个整数 n , k    ( 1 ≤ n ≤ 1 e 9 , 1 ≤ k ≤ min ⁡ { n , 1000 } ) n,k\ \ (1\leq n\leq 1\mathrm{e}9,1\leq k\leq\min\{n,1000\}) n,k  (1n1e9,1kmin{n,1000}),分别表示椅子数和客人数(也是碗数).第二行输入 k k k个相异的整数 a 1 , ⋯   , a k    ( 0 ≤ a i ≤ n − 1 ) a_1,\cdots,a_k\ \ (0\leq a_i\leq n-1) a1,,ak  (0ain1),表示每个客人的初始位置.第三行输入 k k k个相异的整数 b 1 , ⋯   , b k    ( 0 ≤ b i ≤ n − 1 ) b_1,\cdots,b_k\ \ (0\leq b_i\leq n-1) b1,,bk  (0bin1),表示每个碗的初始位置.数据保证所有测试样例的 k k k之和不超过 5000 5000 5000.

对每组测试数据,输出让所有客人都拿到碗至少需要几次操作.

思路

显然一定有解,最坏可转 k 2 k^2 k2次,每次让一个客人拿到碗.最终答案不超过 1 e 6 1\mathrm{e}6 1e6.

将客人和碗排序后,若确定第一个客人拿的碗的编号,则后面的客人按顺序拿碗是最优策略.

[] 若存在一对客人不按顺序拿碗,则客人与其对应的碗的连线交叉,此时会出现 a a a号客人遇到 b b b号客人应拿的碗,而 b b b号客人遇到 a a a号客人应拿的碗的情况,此时要多进行操作才能保证这一对客人都拿到碗.显然这不是最优策略.

每个客人按顺序拿碗,则客人与其对应的碗的连线都不交叉,此时桌子要么①一直逆时针转,要么②一直顺时针转,要么③在逆时针转中有至多一次顺时针转,要么④在顺时针转中有至多一次逆时针转.设第 i i i个客人对应 m a t c h [ i ] match[i] match[i]号碗.对情况①②, a n s = min ⁡ { m a t c h [ k − 1 ] , n − m a t c h [ 0 ] } ans=\min\{match[k-1],n-match[0]\} ans=min{match[k1],nmatch[0]},即看逆时针转的次数少还是顺时针转的次数少.对情况③④,顺时针转 m a t c h [ i − 1 ] match[i-1] match[i1]次可使得 0 ∼ ( i − 1 ) 0\sim (i-1) 0(i1)号客人拿到对应的碗,逆时针转 ( n − m a t c h [ i ] ) (n-match[i]) (nmatch[i])次可使得 i ∼ ( k − 1 ) i\sim (k-1) i(k1)号客人拿到对应的碗,只需看先顺时针转还是先逆时针转,即 a n s = min ⁡ { m a t c h [ i − 1 ] , n − m a t c h [ i ] } + m a t c h [ i − 1 ] + n − m a t c h [ i ] ans=\min\{match[i-1],n-match[i]\}+match[i-1]+n-match[i] ans=min{match[i1],nmatch[i]}+match[i1]+nmatch[i].

m a t c h [ ] match[] match[]的求法:设第 i i i个客人拿到第 ( i + j ) % k (i+j)\%k (i+j)%k个碗,则 m a t c h [ i ] match[i] match[i]取决于 a [ ( i + j ) % k ] a[(i+j)\%k] a[(i+j)%k] b [ i ] b[i] b[i]在模 n n n下的距离,即 m a t c h [ i ] = ( a [ ( i + j ) % k ] − b [ i ] + n ) % n match[i]=(a[(i+j)\%k]-b[i]+n)\%n match[i]=(a[(i+j)%k]b[i]+n)%n.

枚举 0 0 0号客人拿到的碗的编号 j j j,求出对应的 m a t c h [ ] match[] match[]后排序,更新 a n s ans ans.总时间复杂度 O ( k 2 log ⁡ k ) O(k^2\log k) O(k2logk).

代码 -> 2020ICPC上海-H(思维)

int main() {
	CaseT{
		int n, k; cin >> n >> k;
		vi a(k), b(k);
		for (int i = 0; i < k; i++) cin >> a[i];
		for (int i = 0; i < k; i++) cin >> b[i];

		sort(all(a)), sort(all(b));  // 将客人和碗分别排序

		int ans = INF;
		vi match(k);
		for (int j = 0; j < k; j++) {  // 枚举0号客人拿到的碗
			for (int i = 0; i < k; i++) match[i] = (a[(i + j) % k] - b[i] + n) % n;
			sort(all(match));

			ans = min({ ans,match[k - 1],n - match[0] });
			for (int i = 1; i < k; i++) ans = min(ans, min(match[i - 1], n - match[i]) + match[i - 1] + n - match[i]);
		}
		cout << ans << endl;
	}
}


E. The Journey of Geor Autumn

题意

称一个 1 ∼ n 1\sim n 1n的全排列是好的,如果对 ∀ i ∈ ( k , n ] \forall i\in(k,n] i(k,n],有 a i > min ⁡ j ∈ [ i − k , i − 1 ] a j \displaystyle a_i>\min_{j\in[i-k,i-1]}a_j ai>j[ik,i1]minaj.给定整数 n , k    ( 1 ≤ n , k ≤ 1 e 7 ) n,k\ \ (1\leq n,k\leq 1\mathrm{e}7) n,k  (1n,k1e7),求好的排列的个数,答案对 998244353 998244353 998244353取模.

思路

显然 1 ∼ n 1\sim n 1n的最小数只能放在 a 1 , ⋯   , a k a_1,\cdots,a_k a1,,ak中,不妨设放在 a p o s a_{pos} apos,则 a 1 , ⋯   , a p o s − 1 a_1,\cdots,a_{pos-1} a1,,apos1可随便放,其方案数是先从剩下的 ( n − 1 ) (n-1) (n1)个数中选 ( p o s − 1 ) (pos-1) (pos1)个数的全排列的个数,即 A n − 1 p o s − 1 A_{n-1}^{pos-1} An1pos1.设 a p o s + 1 , ⋯   , a n a_{pos+1},\cdots,a_n apos+1,,an的方案数为 d p [ n − p o s ] dp[n-pos] dp[npos],则 a 1 , ⋯   , a n a_1,\cdots,a_n a1,,an的方案数为 d p [ n ] dp[n] dp[n].按 a n a_n an前面的 k k k个数中的最小值在 a j a_j aj分类,与 a 1 , ⋯   , a p o s − 1 a_1,\cdots,a_{pos-1} a1,,apos1的讨论类似,易得 d p [ n ] = ∑ j = 1 m i n { n , k } A n − 1 j − 1 ⋅ d p [ n − j ] \displaystyle dp[n]=\sum_{j=1}^{min\{n,k\}}A_{n-1}^{j-1}\cdot dp[n-j] dp[n]=j=1min{n,k}An1j1dp[nj].状态 O ( n ) O(n) O(n),转移 O ( min ⁡ { n , k } ) O(\min\{n,k\}) O(min{n,k}),总时间复杂度 O ( n ⋅ min ⁡ { n , k } ) O(n\cdot\min\{n,k\}) O(nmin{n,k}),会TLE.

考虑优化,注意到 d p [ i ] = ( i − 1 ) ! ∑ j = 1 k d p [ i − j ] ( i − j ) ! \displaystyle dp[i]=(i-1)!\sum_{j=1}^k \dfrac{dp[i-j]}{(i-j)!} dp[i]=(i1)!j=1k(ij)!dp[ij],显然求和部分为前缀和.令 p r e [ i ] = ∑ j = 1 i d p [ i ] i ! \displaystyle pre[i]=\sum_{j=1}^i \dfrac{dp[i]}{i!} pre[i]=j=1ii!dp[i],则 d p [ i ] = ( i − 1 ) ! ( p r e [ i − 1 ] − p r e [ i − k − 1 ] ) dp[i]=(i-1)!(pre[i-1]-pre[i-k-1]) dp[i]=(i1)!(pre[i1]pre[ik1]),在转移时同时维护 p r e [ ] pre[] pre[]即可.总时间复杂度 O ( n ) O(n) O(n).

代码 -> 2020ICPC上海-E(组合计数+DP+前缀和) By : 狙击美佐

const int MAXN = 1e7 + 5;
const int MOD = 998244353;
int n, k;
int fac[MAXN], ifac[MAXN];
int dp[MAXN];  // dp[i]表示长度为i的排列中好的排列的个数
int pre[MAXN];  // pre[i]是dp[i]/i!的前缀和

void init() {  // 预处理阶乘及其逆元
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;

	ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
	for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}

int main() {
	init();

	cin >> n >> k;

	dp[0] = pre[0] = 1;  // 初始条件
	for (int i = 1; i <= n; i++) {
		dp[i] = (((ll)pre[i - 1] - (i - k - 1 >= 0 ? pre[i - k - 1] : 0)) * fac[i - 1] % MOD + MOD) % MOD;  // 注意结果可能为负数
		pre[i] = ((ll)pre[i - 1] + (ll)dp[i] * ifac[i] % MOD) % MOD;
	}
	cout << dp[n];
}


L. Traveling in the Grid World

题意

n × m n\times m n×m的网格,其上有 ( n + 1 ) × ( m + 1 ) (n+1)\times(m+1) (n+1)×(m+1)个格点.现要从左上角的格点 ( 0 , 0 ) (0,0) (0,0)走到右下角的格点 ( n , m ) (n,m) (n,m),每一步可选择一个格点,要求当前位置与格点所连线段上除线段端点外不能有其他格点.两格点间的距离定义为它们的Euclid距离,求从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , m ) (n,m) (n,m)的最短距离.

t t t组测试数据.每组测试数据输入两整数 n , m    ( 1 ≤ n , m ≤ 1 e 6 ) n,m\ \ (1\leq n,m\leq 1\mathrm{e}6) n,m  (1n,m1e6).数据保证所有测试数据的 max ⁡ { n , m } \max\{n,m\} max{n,m}之和不超过 1 e 6 1\mathrm{e}6 1e6.

对每组测试数据,输出从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , m ) (n,m) (n,m)的最短距离,误差不超过 1 e − 9 1\mathrm{e}-9 1e9.

思路 By : DD(XYX) , 偶耶XJX

( 0 , 0 ) (0,0) (0,0)与点 ( n , m ) (n,m) (n,m)所连线段上的整点数为 gcd ⁡ ( n , m ) + 1 \gcd(n,m)+1 gcd(n,m)+1.

[] 点 ( 0 , 0 ) (0,0) (0,0)与点 ( n , m ) (n,m) (n,m)所连线段经过的点都在直线 y = m n ⋅ x y=\dfrac{m}{n}\cdot x y=nmx上.若 y y y是整数,则 m x n \dfrac{mx}{n} nmx是整数,即 n ∣ m x n\mid mx nmx.

x x x只能取 n gcd ⁡ ( n , m ) , 2 n gcd ⁡ ( n , m ) , 3 n gcd ⁡ ( n , m ) , ⋯   , n \dfrac{n}{\gcd(n,m)},2\dfrac{n}{\gcd(n,m)},3\dfrac{n}{\gcd(n,m)},\cdots,n gcd(n,m)n,2gcd(n,m)n,3gcd(n,m)n,,n gcd ⁡ ( n , m ) \gcd(n,m) gcd(n,m)个数,再加上 ( 0 , 0 ) (0,0) (0,0) gcd ⁡ ( n , m ) + 1 \gcd(n,m)+1 gcd(n,m)+1个数.

( n , m ) (n,m) (n,m)互素,则它们所连线段上的整点只有 2 2 2个,即只有线段端点,则最短距离即它们所连线段的长度.

( n , m ) (n,m) (n,m)不互素,则路径只能为折线.

若一条只有一个拐点的折线路径上的非端点处有整点,则它可调整为一条合法且更优的路径.

在这里插入图片描述

[] 如上图,若折线 A B C ABC ABC通过非端点处的整点 D D D,连接 C D CD CD,则 D B + B C > D C DB+BC>DC DB+BC>DC.

注意到 A D + D C < A D + D B + B C = A B + B C AD+DC<AD+DB+BC=AB+BC AD+DC<AD+DB+BC=AB+BC,故调整后路径更优.

若此时折线 A D C ADC ADC仍经过非端点处的整点,重复上述操作直至路径不经过非端点处的整点.

因不考虑路径是否合法时点 A A A与点 C C C间的最短距离为 A C ‾ \overline{AC} AC的长度,则任一合法路径的长度距离 e q A C ‾ eq \overline{AC} eqAC的长度.

​ 而每次调整会使路径长度严格减小,故在有限次调整后能变为最优的合法路径.

折线路径上至多有一个拐点,否则它可调整为一条至多有一个拐点的合法且更优的路径.

在这里插入图片描述

[] 如上图为点 A A A到点 B B B的一条有多个拐点的路径.因该路径是折线,则其上至少 ∃ \exists 一点 C   s . t .   A C B C\ s.t.\ ACB C s.t. ACB不共线.

连接 A C , C B AC,CB AC,CB,显然 A C + C B < AC+CB< AC+CB<原折线长度.若此时的路径不合法,用上个结论的调整方式调整即可.

对折线路径 A C B ACB ACB,满足条件的拐点可能在 A B ‾ \overline{AB} AB的上方或下方,甚至 A B ‾ \overline{AB} AB的同一侧有多个满足条件的拐点.

注意到这些拐点 C C C与点 A A A和点 B B B围成的三角形有性质:①内部的整点数为 0 0 0(否则取内部的格点会使得路径更优);②三角形的边上有 gcd ⁡ ( n , m ) + 2 \gcd(n,m)+2 gcd(n,m)+2个整点(即 A B ‾ \overline{AB} AB上的整点数加上拐点).

由Pick定理: S △ A B C = 0 + gcd ⁡ ( n , m ) + 2 2 − 1 = gcd ⁡ ( n , m ) 2 S_{\triangle ABC}=0+\dfrac{\gcd(n,m)+2}{2}-1=\dfrac{\gcd(n,m)}{2} SABC=0+2gcd(n,m)+21=2gcd(n,m),它是定值.

​ 而 A B ‾ \overline{AB} AB长度固定,则满足条件的拐点在平行于直线 A B AB AB的直线上.

以网格左下方的格点为原点建系.由等面积法易得拐点在直线 y = m n x ± gcd ⁡ ( n , m ) n y=\dfrac{m}{n}x\pm \dfrac{\gcd(n,m)}{n} y=nmx±ngcd(n,m).

n = a ⋅ gcd ⁡ ( n , m ) , m = b ⋅ gcd ⁡ ( n , m ) n=a\cdot \gcd(n,m),m=b\cdot\gcd(n,m) n=agcd(n,m),m=bgcd(n,m),显然 gcd ⁡ ( a , b ) = 1 \gcd(a,b)=1 gcd(a,b)=1.

​ 代入上式得 b x − a y = ± 1 = ± gcd ⁡ ( a , b ) bx-ay=\pm 1=\pm \gcd(a,b) bxay=±1=±gcd(a,b),用扩欧求出一组特解 ( x , y ) (x,y) (x,y).

注意到 x x x y y y同时加减 a a a b b b时,因 b ( x ± a ) − a ( y ± b ) = b x − a y b(x\pm a)-a(y\pm b)=bx-ay b(x±a)a(y±b)=bxay,则方程仍成立.

​ 故可从最小的 ( x , y ) (x,y) (x,y)开始,每次 x + = a , y + = b x+=a,y+=b x+=a,y+=b,更新最短距离.

显然路径的最大长度 < < < ( 0 , 0 ) (0,0) (0,0)出发沿网格边缘走到 ( n , m ) (n,m) (n,m)的距离 = n + m ≤ 2 e 6 =n+m\leq 2\mathrm{e}6 =n+m2e6.

代码 -> 2020ICPC上海-L(几何+扩欧)

int n, m;

int exgcd(int a, int b, int& x, int& y) {  // 扩欧
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}

double get_dis(int a, int b, int c, int d) { return hypot(a - c, b - d); }
double get_ans(int x, int y) { return get_dis(0, 0, x, y) + get_dis(x, y, n, m); }

int main() {
	CaseT{
		cin >> n >> m;
		int d = gcd(n, m);
		if (d == 1) {
			cout << fixed << setprecision(18) << hypot(n, m) << endl;
			continue;
		}

		int a = n / d, b = m / d;
		int x, y;
		exgcd(b, -a, x, y);  // 解不定方程bx-ay=gcd(a,b)

		double ans = INF;

		y -= x / a * b, x -= x / a * a;  // 变为最小解
		while (x <= n) {
			if (x >= 0) ans = min(ans, get_ans(x, y));
			x += a, y += b;
		}

		x = -x, y = -y;  // 线段的另一侧
		y -= x / a * b, x -= x / a * a;  // 变为最小解
		while (x <= n) {
			if (x >= 0) ans = min(ans, get_ans(x, y));
			x += a, y += b;
		}

		cout << fixed << setprecision(18) << ans << endl;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值