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=fn−2+fn−1 (n≥3),它的前几项是 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)=[x⋅y是偶数].给定 n ( 1 ≤ n ≤ 1 e 9 ) n\ \ (1\leq n\leq 1\mathrm{e}9) n (1≤n≤1e9),求 ∑ 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=1∑nj=i+1∑ng(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 f1∼fn中值为偶数的项的个数,即 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} block1∼blockcnt中的偶数配对,它们对答案的贡献即 b l o c k 1 ∼ b l o c k c n t block_1\sim block_{cnt} block1∼blockcnt中值为偶数的项的个数,即 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} block2∼blockcnt中的偶数配对,它们对答案的贡献即 b l o c k 2 ∼ b l o c k c n t block_2\sim block_{cnt} block2∼blockcnt中值为偶数的项的个数,即 c n t − 1 cnt-1 cnt−1; ⋯ \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+(cnt−1)+⋯+1]=cnt(cnt+1).显然余下的 r e s t rest rest个单独的值为奇数的项对答案无贡献.
②值为偶数的项:显然每个值为偶数的项只能是每个block的第三个元素.对 b l o c k 1 block_1 block1中的值为偶数的项,能与其后面的 ( n − 3 ) (n-3) (n−3)项配对;对 b l o c k 2 block_2 block2中的值为偶数的项,能与其后面的 ( n − 6 ) (n-6) (n−6)项配对; ⋯ \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} (n−3)+(n−6)+⋯+(n−3⋅cnt)=2cnt⋅(n−3+n−3⋅cnt).
考察答案的数据范围.最坏的情况每个 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=(n−1)+(n−2)+⋯+1=2n(n−1), 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=1∑nj=i+1∑ng(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=2∑ng(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 2⋅⌊3n⌋+2⌊3n⌋⋅(2n−3⋅⌊3n⌋−3)∼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) (n−cnt).奇数与偶数的匹配数为 ( n − c n t ) ⋅ c n t (n-cnt)\cdot cnt (n−cnt)⋅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} (cnt−1)+(cnt−2)+⋯+1=2(cnt−1)⋅(cnt−1+1)=2cnt⋅(cnt−1).
代码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 (1≤n,m≤1000).接下来输入一个 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 (1≤n+m≤100)个受保护的文件的路径.每次操作可删除一个不受保护的文件或文件夹,问删除所有要删除的文件至少需几次操作.
思路
以每个文件夹和文件为节点,文件夹与文件的包含关系为边,则所有文件的路径构成森林.
用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 (1≤T≤1e4)组测试数据.每组测试数据输入五个实数 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 (1≤n≤1e4,0≤p1,p2≤n,0.001≤v1,v2≤1000),每个实数至多精确到小数点后三位.
对每组测试数据,输出一个实数,表示所需的最短时间,误差不超过 1 e − 6 1\mathrm{e}-6 1e−6.
思路
不妨设 p 1 ≤ p 2 p_1\leq p_2 p1≤p2,否则交换 ( 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{n−p1,p1}+n,v2min{n−p2,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{v1n−p1,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 1e−6,则迭代次数 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<1e−121e4=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,b∈P,定义 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 (1≤n,m≤500),求 P P P中任意两点的距离之和,误差不超过 1 e − 6 1\mathrm{e}-6 1e−6.
思路
不同交点的 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 1∼i个圆上从一个交点出发到其他交点的 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 i≥2时考虑递推.
①对 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∼(i−1)个圆上从一个交点出发到其他交点的 d i s dis dis之和 + + +在第 i i i个圆上从一个交点出发到该圆上其他交点的 d i s dis dis之和 + + +第 i i i个圆上的点到第 1 ∼ ( i − 1 ) 1\sim (i-1) 1∼(i−1)个圆上的交点的距离之和.前两项分别为 d i s 1 [ i − 1 ] dis1[i-1] dis1[i−1]和 d i s 2 [ i ] dis2[i] dis2[i],第三项只需将第 2 ∼ i 2\sim i 2∼i个圆上的点沿半径退到第 1 ∼ ( i − 1 ) 1\sim (i-1) 1∼(i−1)个圆即可.
对每一层统计答案.第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)层对答案的贡献为第 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} 22m⋅dis2[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 m≥2时,圆心处有交点,它对答案的贡献是 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∼(n−1).初始时,第 i ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i (0≤i≤n−1)号碗在桌子上的第 b i ( 0 ≤ b i ≤ n − 1 ) b_i\ \ (0\leq b_i\leq n-1) bi (0≤bi≤n−1)个位置,第 i ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i (0≤i≤n−1)个客人坐在第 a i ( 0 ≤ a i ≤ n ) a_i\ \ (0\leq a_i\leq n) ai (0≤ai≤n)个位置.现有两种操作:①顺时针旋转桌子:第 b i ( 1 ≤ i ≤ k ) b_i\ \ (1\leq i\leq k) bi (1≤i≤k)个碗会被转到第 ( 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 (1≤i≤k)个碗会被转到第 ( b i − 1 ) m o d n (b_i-1)\ \mathrm{mod}\ n (bi−1) 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 (1≤n≤1e9,1≤k≤min{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 (0≤ai≤n−1),表示每个客人的初始位置.第三行输入 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 (0≤bi≤n−1),表示每个碗的初始位置.数据保证所有测试样例的 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[k−1],n−match[0]},即看逆时针转的次数少还是顺时针转的次数少.对情况③④,顺时针转 m a t c h [ i − 1 ] match[i-1] match[i−1]次可使得 0 ∼ ( i − 1 ) 0\sim (i-1) 0∼(i−1)号客人拿到对应的碗,逆时针转 ( n − m a t c h [ i ] ) (n-match[i]) (n−match[i])次可使得 i ∼ ( k − 1 ) i\sim (k-1) i∼(k−1)号客人拿到对应的碗,只需看先顺时针转还是先逆时针转,即 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[i−1],n−match[i]}+match[i−1]+n−match[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 1∼n的全排列是好的,如果对 ∀ 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∈[i−k,i−1]minaj.给定整数 n , k ( 1 ≤ n , k ≤ 1 e 7 ) n,k\ \ (1\leq n,k\leq 1\mathrm{e}7) n,k (1≤n,k≤1e7),求好的排列的个数,答案对 998244353 998244353 998244353取模.
思路
显然 1 ∼ n 1\sim n 1∼n的最小数只能放在 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,⋯,apos−1可随便放,其方案数是先从剩下的 ( n − 1 ) (n-1) (n−1)个数中选 ( p o s − 1 ) (pos-1) (pos−1)个数的全排列的个数,即 A n − 1 p o s − 1 A_{n-1}^{pos-1} An−1pos−1.设 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[n−pos],则 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,⋯,apos−1的讨论类似,易得 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=1∑min{n,k}An−1j−1⋅dp[n−j].状态 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(n⋅min{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]=(i−1)!j=1∑k(i−j)!dp[i−j],显然求和部分为前缀和.令 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=1∑ii!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]=(i−1)!(pre[i−1]−pre[i−k−1]),在转移时同时维护 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 (1≤n,m≤1e6).数据保证所有测试数据的 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 1e−9.
思路 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=nm⋅x上.若 y y y是整数,则 m x n \dfrac{mx}{n} nmx是整数,即 n ∣ m x n\mid mx n∣mx.
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} S△ABC=0+2gcd(n,m)+2−1=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=a⋅gcd(n,m),m=b⋅gcd(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) bx−ay=±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)=bx−ay,则方程仍成立.
故可从最小的 ( 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+m≤2e6.
代码 -> 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;
}
}