本次赛时只有3题,D题没有写方向数组,八个方向全手搓,导致写了非常久,调试也极其困难,实际上就是个简单的BFS题。而E题作为数论题也是比较简单,是可以尝试解决的。
A
问题陈述
您将获得正整数 N N N 和 M M M 。
如果重复执行以下操作,而 M M M 的值不是 0 0 0 ,请找出执行的操作次数。
设 x x x 为 N N N 除以 M M M 的余数。将 M M M 的值替换为 x x x 。
可以证明,经过有限次数的运算后, M M M 变成了 0 0 0 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 void sol () { int M,N; cin>>N>>M; int cnt=0 ; while (M) { M=N%M; cnt++; } cout<<cnt; }
B
问题陈述
x y xy x y \平面上有两个圆 C 1 C_1 C 1 和 C 2 C_2 C 2 。在这个问题中,圆指的是圆周而非圆面。
圆 C 1 C_1 C 1 的圆心为 ( X 1 , Y 1 ) (X_1, Y_1) ( X 1 , Y 1 ) ,半径为 R 1 R_1 R 1 。
圆 C 2 C_2 C 2 的圆心为 ( X 2 , Y 2 ) (X_2, Y_2) ( X 2 , Y 2 ) ,半径为 R 2 R_2 R 2 。
确定圆 C 1 C_1 C 1 和 C 2 C_2 C 2 是否有公共点。换句话说,判断是否存在至少一个到 ( X 1 , Y 1 ) (X_1, Y_1) ( X 1 , Y 1 ) 的距离为 R 1 R_1 R 1 且到 ( X 2 , Y 2 ) (X_2, Y_2) ( X 2 , Y 2 ) 的距离为 R 2 R_2 R 2 的点。
您获得了 T T T 个测试用例;逐一解决。
思路:
题意为判断两圆是否相交,即不相离 、不内含 。
Code
1 2 3 4 5 6 7 8 9 10 void sol () { int X1,Y1,R1,X2,Y2,R2; cin>>X1>>Y1>>R1>>X2>>Y2>>R2; if ((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)<=(R1+R2)*(R1+R2)&&(X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)>=(R1-R2)*(R1-R2)) { cout<<"Yes\n" ; } else cout<<"No\n" ; }
C
问题陈述
寿司的原料有 N N N 块醋饭和 M M M 块内塔(配料)。
第 i i i 个shari的权重是 A i A_i A i ,第 j j j 个neta的权重是 B j B_j B j 。
您将结合 Shari 和 Neta 制作寿司。
要制作一件寿司,您需要将一份 Shari 和一份 Neta 结合在一起。这里, Neta 的重量最多是 Shari 重量的两倍。此外,相同的 Shari 或 Neta 不能用于多份寿司。
求最多可以制作多少个寿司。
思路:
对于第i i i 个Shari和第j j j 个Neta,满足A i × 2 ≥ B j A_i\times 2\ge B_j A i × 2 ≥ B j 时,才能结合在一起。
因此我们将A A A 和B B B 排序后,利用指针i i i 和j j j ,分别对A A A 和B B B 进行扫描:
如果当前的i i i 和j j j 不满足条件,那么就将i i i 向后移动。
如果当前的i i i 和j j j 满足条件,那么就将他们组合成寿司,a n s ans an s 累加,并将i i i 和j j j 均向后移动,防止重复使用。(注意越界判定)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void sol () { int N,M; cin>>N>>M; for (int i=1 ;i<=N;i++)cin>>A[i]; for (int i=1 ;i<=M;i++)cin>>B[i]; sort (A+1 ,A+N+1 ); sort (B+1 ,B+M+1 ); int j=1 ,ans=0 ; for (int i=1 ;i<=N;i++) { if (B[j]>2 *A[i]&&j<=M)continue ; if (j>M)break ; ans++; j++; } cout<<ans; }
补题
D
问题陈述
有一个包含 H H H 行和 W W W 列的网格。从上数第 i i i 行、从左数第 j j j 列的单元格称为单元格 ( i , j ) (i, j) ( i , j ) 。
每个细胞都是白色或黑色的。网格由 H H H 字符串 S 1 , S 2 , … , S H S_1, S_2, \ldots, S_H S 1 , S 2 , … , S H 描述,每个字符串的长度为 W W W 。如果 S i S_i S i 的第 j j j 个字符是 .,则单元格 ( i , j ) (i, j) ( i , j ) 为白色;如果 S i S_i S i 的第 j j j 个字符是 #,则单元格 ( i , j ) (i, j) ( i , j ) 为黑色。
您执行以下操作 10 100 10^{100} 1 0 100 次。
同时将以下规则应用于所有单元格。
当且仅当存在至少一个与其相邻的黑色单元格时,操作前为白色的单元格才会变为黑色。这里,单元格 ( x , y ) (x, y) ( x , y ) 和 ( x ′ , y ′ ) (x', y') ( x ′ , y ′ ) 是相邻的,当且仅当其中一个单元格位于另一个单元格的 8 8 8 -邻域内,即 max ( ∣ x − x ′ ∣ , ∣ y − y ′ ∣ ) = 1 \max(|x-x'|, |y-y'|) = 1 max ( ∣ x − x ′ ∣ , ∣ y − y ′ ∣ ) = 1 。
操作前为黑色的单元格变为白色。
求操作后每个单元格的颜色。
分析:
每次操作会使黑格子 变白格子,并且将周围有黑格子的白格子 变黑。执行10 100 10^{100} 1 0 100 次操作,显然我们无法模拟这么多次,我们只能找变化规律。
显然,当白格子周围有黑格子时,经过偶数次变化,两者颜色不变;经过奇数次变化,两者颜色交换。(以下简称其为互锁格子 )
我们找到所有互锁格子后,剩下的非互锁格子必定全为黑色或白色 ,且这些互锁格子的边缘一定是相应的黑色或白色。
若全为黑色,则经过一次变换后,就会转变成了全为白色的情况。(后续以全白说明)
由于互锁格子会根据操作次数不断变化,当外侧变为黑色时,下一次变化就会将黑色向外传递 到非互锁格子,而再下一次,会再往外传递(因为外面为白色),如此就会像波一样不断向外扩散,而互锁格子就如同脉冲信号一样,不断间歇地产生波,周期为2,那么对于波经过偶数次的格子,其颜色仍为白色,经过奇数次的格子,其颜色变黑。
而在网格上,实际上就是判断非互锁格子距离其最近 的黑色格子的切比雪夫距离 的奇偶性。
切比雪夫距离:d Chebyshev ( ( x 1 , y 1 ) , ( x 2 , y 2 ) ) = max ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) d_{\text{Chebyshev}}((x_1, y_1), (x_2, y_2)) = \max(|x_1 - x_2|, |y_1 - y_2|) d Chebyshev (( x 1 , y 1 ) , ( x 2 , y 2 )) = max ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ )
思路:
考虑到非互锁格子可能为黑色,而黑色又比较特殊,所以我们需要先模拟进行一次操作。
我们找到所有周围有黑色格子的白色格子并记录下来后,将所有黑色格子变为白色,再将记录下来的白色格子变黑,就实现了一次操作。
求切比雪夫距离 就用多源BFS计算即可。
复杂度O ( H × W ) O(H\times W) O ( H × W )
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 void sol () { int H,W; cin>>H>>W; for (int i=1 ;i<=H;i++) { cin>>S[i]; S[i]=" " +S[i]; } vector<vector<int >>dis (H+5 ,vector <int >(W+5 ,-1 )); queue<pair<int ,int > >q; for (int i=1 ;i<=H;i++) { for (int j=1 ;j<=W;j++) { if (S[i][j]=='#' )continue ; bool fg=0 ; for (int k=0 ;k<8 ;k++) { int sx=i+x[k],sy=j+y[k]; if (sx<1 ||sy<1 ||sx>H||sy>W)continue ; if (S[sx][sy]=='#' ) { fg=1 ; break ; } } if (fg)q.push ({i,j}); } } for (int i=1 ;i<=H;i++) { for (int j=1 ;j<=W;j++) { if (S[i][j]=='#' )S[i][j]='.' ; } } while (q.size ()) { auto [tx,ty]=q.front (); q.pop (); S[tx][ty]='#' ; } for (int i=1 ;i<=H;i++) { for (int j=1 ;j<=W;j++) { if (S[i][j]=='#' ) { dis[i][j]=0 ; q.push ({i,j}); } } } if (q.empty ()) { for (int i=1 ;i<=H;i++) { for (int j=1 ;j<=W;j++) { cout<<'.' ; } cout<<'\n' ; } return ; } while (q.size ()) { auto [tx,ty]=q.front (); q.pop (); for (int i=0 ;i<8 ;i++) { int sx=tx+x[i],sy=ty+y[i]; if (sx<1 ||sy<1 ||sx>H||sy>W||dis[sx][sy]!=-1 )continue ; dis[sx][sy]=dis[tx][ty]+1 ; q.push ({sx,sy}); } } for (int i=1 ;i<=H;i++) { for (int j=1 ;j<=W;j++) { cout<<(dis[i][j]%2 ==0 ?'.' :'#' ); } cout<<'\n' ; } }
E
问题陈述
对于正整数 a a a 和 b b b ,将 c o n c a t ( a , b ) \mathrm{concat}(a, b) concat ( a , b ) 定义为依次写入 a a a 和 b b b 形成的整数。更正式地说, c o n c a t ( a , b ) \mathrm{concat}(a, b) concat ( a , b ) 定义如下。
令 A A A 和 B B B 分别为通过十进制写入 a a a 和 b b b 形成的字符串。令 C C C 为按顺序连接 A A A 和 B B B 形成的字符串。将 C C C 解释为十进制整数的值为 c o n c a t ( a , b ) \mathrm{concat}(a, b) concat ( a , b ) 。
例如,如果 a = 123 a = 123 a = 123 和 b = 45 b = 45 b = 45 ,则为 c o n c a t ( a , b ) = 12345 \mathrm{concat}(a, b) = 12345 concat ( a , b ) = 12345 。
您将获得正整数 N N N 和 M M M 。
求不大于 N N N 的正整数对 ( x , y ) (x, y) ( x , y ) 的模 998244353 998244353 998244353 的数量,使得 c o n c a t ( x , y ) ≡ x + y ( m o d M ) \mathrm{concat}(x, y) \equiv x + y \pmod{M} concat ( x , y ) ≡ x + y ( mod M ) 。
您获得了 T T T 个测试用例;逐一解决。
思路:
数论题,推导过程如图:
L ( x ) L(x) L ( x ) 表示x的长度,如L ( 325799 ) = 6 L(325799)=6 L ( 325799 ) = 6 。
在本题中,由于数据很大,需要用到unsigned long long ,并且N M G \frac{N}{\frac{M}{G}} G M N 不能写成N × G M \frac{N\times G}{M} M N × G ,否则数据会溢出
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int _10[20 ];int L (int x) { int res=0 ; while (x){x/=10 ;res++;} return res; } void sol () { int ans=0 ; int N,M; cin>>N>>M; for (int i=1 ;i<=L (N);i++) { int G=__gcd(M,_10[i]-1 ); int sumx=N/(M/G); int sumy=9 *_10[i-1 ]; if (i==L (N))sumy=N-_10[i-1 ]+1 ; ans=(ans+sumx%mod*(sumy%mod)%mod)%mod; } cout<<ans<<'\n' ; } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int T=1 ; _10[0 ]=1 ; for (int i=1 ;i<=19 ;i++)_10[i]=_10[i-1 ]*10 ; cin>>T; while (T--)sol (); }
F
问题陈述
有一棵树,其顶点为 N N N 。顶点编号为 1 , 2 , … , N 1, 2, \ldots, N 1 , 2 , … , N ,第 i i i 条边连接顶点 U i U_i U i 和 V i V_i V i 。
最初,所有顶点都涂成黑色。
按顺序处理以下形式的 Q Q Q 查询,并找到每个查询的答案。
给出一个整数 x x x ( 1 ≤ x ≤ N ) (1 \leq x \leq N) ( 1 ≤ x ≤ N ) 。如果顶点 x x x 为白色,则将其重新绘制为黑色;如果顶点 x x x 为黑色,则将其重新绘制为白色。然后,找到两个黑色顶点之间的最大距离。这里,树上两个顶点之间的距离是它们之间的简单路径中的边数。
在给定的输入中,按顺序处理查询时始终至少有两个黑色顶点。
分析:
我们需要求树的直径,首先就需要求树上两点距离 ,有公式:D i s t a n c e ( u , v ) = D e e p ( u ) + D e e p ( v ) − 2 × D e e p ( L C A ( u , v ) ) Distance(u,v)=Deep(u)+Deep(v)-2\times Deep(LCA(u,v)) D i s t an ce ( u , v ) = D ee p ( u ) + D ee p ( v ) − 2 × D ee p ( L C A ( u , v ))
而L C A LCA L C A 则用倍增法O ( log N ) O(\log N) O ( log N ) 、树链剖分O ( log N ) O(\log N) O ( log N ) 乃至DFS序+ST表O ( 1 ) O(1) O ( 1 ) 均可。(倍增法是最简单的)
随后我们考虑求直径并且支持动态修改,这里使用线段树 来维护。
思路:
线段树节点信息 :
对于表示[ l , r ] [l,r] [ l , r ] 区间的节点,我们记录u , v ∈ [ l , r ] u,v\in [l,r] u , v ∈ [ l , r ] ,且d i s t ( u , v ) = max l ≤ i , j ≤ r d i s t ( i , j ) dist(u,v)=\max\limits_{l\le i,j\le r}dist(i,j) d i s t ( u , v ) = l ≤ i , j ≤ r max d i s t ( i , j )
那么树的直径就是线段树根节点对应的d i s t dist d i s t 。
m e r g e merge m er g e 函数 :
设线段树中,x x x 节点的左孩子是l c lc l c ,右孩子是r c rc r c 。
左孩子l c lc l c 对应的u = a u=a u = a ,v = b v=b v = b ,右孩子r c rc r c 对应的u = c u=c u = c ,v = d v=d v = d ,则合并区间后的最大距离为( a , b , c , d ) (a,b,c,d) ( a , b , c , d ) 四点之间的某两点的距离。
u p d a t e update u p d a t e 函数 :
修改p p p 位置的颜色后:
如果该位置变白,则该点不能被计入直径计算,我们将该节点的d i s t dist d i s t 改为− ∞ -\infty − ∞ ,同时u u u 和v v v 改成0。
如果该位置变黑,我们则恢复该点状态,将节点数据( p , p , 0 ) (p,p,0) ( p , p , 0 ) 。
随后向上更新到线段树根为止。
复杂度O ( ( Q + N ) log N ) O((Q+N)\log N) O (( Q + N ) log N )
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 int dist (int x,int y) ;struct Node { int x,y,d; Node (int x=0 ,int y=0 ):x (x),y (y),d (dist (x,y)){} bool operator <(const Node& t)const { return d<t.d; } }; vector<int >dep,col; vector<vector<int > >tree,f; vector<Node>segtre; void dfs (int x,int fa) { dep[x]=dep[fa]+1 ; f[x][0 ]=fa; for (int i=1 ;i<=20 ;i++) { f[x][i]=f[f[x][i-1 ]][i-1 ]; } for (auto c:tree[x]) { if (c==fa)continue ; dfs (c,x); } } int lca (int x,int y) { if (dep[x]!=dep[y]) { if (dep[x]<dep[y])swap (x,y); int tem=dep[x]-dep[y],i=0 ; while (tem) { if (tem&1 )x=f[x][i]; tem>>=1 ; i++; } } if (x==y)return x; for (int i=20 ;i>=0 ;i--) { if (f[x][i]==f[y][i])continue ; x=f[x][i]; y=f[y][i]; } return f[x][0 ]; } int dist (int x,int y) { if (x==0 ||y==0 )return -1 ; if (x==y)return 0 ; return dep[x]+dep[y]-2 *dep[lca (x,y)]; } Node merge (int x,int y) { Node res=max (segtre[x],segtre[y]); res=max (res,Node (segtre[x].x,segtre[y].x)); res=max (res,Node (segtre[x].x,segtre[y].y)); res=max (res,Node (segtre[x].y,segtre[y].x)); res=max (res,Node (segtre[x].y,segtre[y].y)); return res; } void build (int x,int l,int r) { if (l==r) { segtre[x]=Node (l,l); return ; } int mid=(l+r)>>1 ; build (x<<1 ,l,mid); build (x<<1 |1 ,mid+1 ,r); segtre[x]=merge (x<<1 ,x<<1 |1 ); } void update (int x,int l,int r,int pos) { if (l==r) { col[l]^=1 ; if (col[l])segtre[x]=Node (l,l); else segtre[x]=Node (0 ,0 ); return ; } int mid=(l+r)>>1 ; if (pos>=mid+1 )update (x<<1 |1 ,mid+1 ,r,pos); else update (x<<1 ,l,mid,pos); segtre[x]=merge (x<<1 ,x<<1 |1 ); } void sol () { int N; cin>>N; dep.assign (N+1 ,0 ); col.assign (N+1 ,1 ); tree.assign (N+1 ,vector <int >(0 )); f.assign (N+1 ,vector <int >(22 )); segtre.assign (N<<2 ,Node ()); for (int i=1 ;i<=N-1 ;i++) { int u,v; cin>>u>>v; tree[u].push_back (v); tree[v].push_back (u); } dfs (1 ,0 ); build (1 ,1 ,N); int Q; cin>>Q; while (Q--) { int x; cin>>x; update (1 ,1 ,N,x); cout<<segtre[1 ].d<<'\n' ; } }