比赛链接:这里
OP
个人能力有限,有的题目实在知识点欠缺太多,这里是大佬的题解 。
这一场总的来说友好极了,就是自己的基础数据总算错,自然也就推了个寂寞。
A 回文括号序列计数
回文序列,阅读理解
思路
对于任意一个左半串,比如说"()()("
,其回文右半串为"()()("
,而不是")()()"
;
回文对称中是左右对调 ,而不是左右对称 。
所以只有空串是合法的回文括号序列。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <bits/stdc++.h> using namespace std;typedef long long ll; int main () { int n,t; scanf ("%lld" ,&t); while (t--) { scanf ("%d" ,&n); printf ("%d\n" ,(n==0 )?1 :0 ); } return 0 ; }
阅读理解
B 系数
lucas定理(可以参照这里 与这里 )
思路
( x 2 + x + 1 ) n % 3 = ( x 2 − 3 ∗ x + x + 1 ) n % 3 = ( x + 1 ) 2 n % 3 (x^2+x+1)^n\%3=(x^2-3*x+x+1)^n\%3=(x+1)^{2n}\%3 ( x 2 + x + 1 ) n % 3 = ( x 2 − 3 ∗ x + x + 1 ) n % 3 = ( x + 1 ) 2 n % 3
故答案即为( − 1 ) k C ( 2 n , k ) % 3 (-1)^kC(2n,k)\%3 ( − 1 ) k C ( 2 n , k ) % 3
由于 1e15 的数据量和逆元的互质要求,正向计算很困难,所以使用lucas定理进行计算。
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=3 ;ll qm (ll a,ll b) { ll ans=1 ; for (;b;b>>=1 ) { if (b&1 )ans=ans*a%M; a=a*a%M; } return ans; }ll C (ll a, ll b, ll mod) { if (a < b)return 0 ; ll up = 1 , down = 1 ; for (int i = a,j = 1 ;j <= b;i--,j++){ up = up*i%mod; down = down*j%mod; } return up*qm (down, mod - 2 )%mod; }ll lucas (ll a, ll b, ll mod) { if (a < mod && b < mod)return C (a, b, mod); else return C (a%mod,b%mod,mod)*lucas (a/mod,b/mod,mod)%mod; }int main () { int t; cin>>t; while (t--) { ll n,k; scanf ("%lld%lld" ,&n,&k); ll ans=1 ; if (k % 2 == 1 )ans = -1 ; ans = ans * lucas (2 *n,k,M); ans = (ans + 3 *M)%M; cout << ans << endl; } return 0 ; }
C 末三位
快速幂or找规律
思路
读到题第一反应就是快速幂板子啦~
事实上确实能过;
但是也可以直接找规律做:
对于n=0,1,2,3,4,5,6,7…
应输出 001,005,025,125,625,125,625…
所以就是从 n=3 开始两个一循环,特判 n=0,1,2 即可。
下面代码是快速幂的。
代码
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 include<bits/stdc++.h>using namespace std;typedef long long ll;const ll M=1000 ;ll qm (ll a,ll b) { ll ans=1 ; while (b) { if (b&1 )ans=ans*a%M; a=a*a%M; b>>=1 ; } return ans%M; }int main () { ll n,a; while (~scanf ("%lld" ,&n)) { a=qm (5 ,n); if (a<10 )printf ("00%lld\n" ,a); else if (a<100 )printf ("0%lld\n" ,a); else printf ("%lld\n" ,a); } return 0 ; }
D 划数
取模运算性质
思路
由于 cnt>=11 ,可知剩下的数一定是初始数;
既然需要求剩下的另一个数,根据取模运算性质,即是求其余所有初始数之和对11的模;
至于实现上,个人是 全部求和 取模 之后减去剩余数 处理后 再取模;
注意要特判 n==2 ,此时另一个初始数有可能大于等于11,不能按上面方法做,摘出来即可。
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; int main () { int n,cnt; while (~scanf ("%d" ,&n)) { if (n!=2 ){ ll md=0 ,g,i; scanf ("%d" ,&cnt); for (i=1 ;i<=n;i++) { scanf ("%lld" ,&g); md+=g; md%=11 ; } md-=cnt; while (md<0 )md+=11 ; md%=11 ; printf ("%lld\n" ,md);} else { int a,b; scanf ("%d" ,&cnt); scanf ("%d%d" ,&a,&b); if (a==cnt)printf ("%d\n" ,b); else printf ("%d\n" ,a); } } return 0 ; }
E 网格
dp
思路
水平方向与竖直方向对答案的贡献是独立的,下面用水平方向说明具体dp过程。
用 d p [ i ] [ d ] ( d ∈ { 0 , 1 } ) dp[i][d](d\in\{0,1\}) d p [ i ] [ d ] ( d ∈ { 0 , 1 } ) 表示该行遍历至第 i 个时,选择d方向(1为右,0为左)时,对答案的最大贡献度;
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] ) dp[i][1]=max(dp[i-1][1],dp[i-1][0]) d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] ) ,即第 i 位选择向右时,不会产生贡献增量,继承第 i - 1 位两个方向中最大值即可;
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + w ( a [ i ] [ j ] ^ a [ i − 1 ) ) dp[i][0]=max(dp[i-1][0],dp[i-1][1]+w(a[i][j]\hat{}a[i-1)) d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + w ( a [ i ] [ j ] ^ a [ i − 1 ) ) ,即第 i 位选择向左时,第 i - 1 位选择向右才能产生增量,选择存入 i - 1 位向右的最大值与增量之和 与 i - 1 位选择向左的最大值 的最大值即可;
分别dp水平和垂直方向,每行/列加入答案。
注意运算优先级!
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;int main () { int bit[1050 ],n,m,i,j,dp[1003 ][2 ]={0 }; static int a[1003 ][1003 ]; for (i=0 ;i<=1024 ;i++) { bit[i]=i; j=i; for (;j;j>>=1 ) if (j&1 )bit[i]++; } cin>>n>>m; for (i=1 ;i<=n;i++) for (j=1 ;j<=m;j++) scanf ("%d" ,&a[i][j]); ll ans=0 ; for (i=1 ;i<=n;i++) { for (j=2 ;j<=m;j++) { dp[j][1 ]=max (dp[j-1 ][1 ],dp[j-1 ][0 ]); dp[j][0 ]=max (dp[j-1 ][0 ],dp[j-1 ][1 ]+bit[a[i][j]^a[i][j-1 ]]); } ans+=max (dp[m][1 ],dp[m][0 ]); } for (j=1 ;j<=m;j++) { for (i=2 ;i<=n;i++) { dp[i][1 ]=max (dp[i-1 ][1 ],dp[i-1 ][0 ]); dp[i][0 ]=max (dp[i-1 ][0 ],dp[i-1 ][1 ]+bit[a[i][j]^a[i-1 ][j]]); } ans+=max (dp[n][1 ],dp[n][0 ]); } cout<<ans; return 0 ; }
F 组合数问题
数学
思路
手算 计算器可得
S u m ( 4 ) = 2 ; Sum(4)=2; S u m ( 4 ) = 2 ;
S u m ( 8 ) = 72 ; Sum(8)=72; S u m ( 8 ) = 7 2 ;
S u m ( 12 ) = 992 ; Sum(12)=992; S u m ( 1 2 ) = 9 9 2 ;
S u m ( 16 ) = 16512 ; Sum(16)=16512; S u m ( 1 6 ) = 1 6 5 1 2 ;
S u m ( 20 ) = 261632 ; Sum(20)=261632; S u m ( 2 0 ) = 2 6 1 6 3 2 ;
经过找规律,得出
S u m ( 4 ) = 2 2 − 2 1 ; Sum(4)=2^2-2^1; S u m ( 4 ) = 2 2 − 2 1 ;
S u m ( 8 ) = 2 6 + 2 3 ; Sum(8)=2^6+2^3; S u m ( 8 ) = 2 6 + 2 3 ;
S u m ( 12 ) = 2 10 − 2 5 ; Sum(12)=2^{10}-2^5; S u m ( 1 2 ) = 2 1 0 − 2 5 ;
S u m ( 16 ) = 2 14 + 2 7 ; Sum(16)=2^{14}+2^7; S u m ( 1 6 ) = 2 1 4 + 2 7 ;
S u m ( 20 ) = 2 18 − 2 9 ; Sum(20)=2^{18}-2^{9}; S u m ( 2 0 ) = 2 1 8 − 2 9 ;
规律已经找出来了,这个规律的代码可以过,接下来是得到的证明:
由于二项式定理 ,我们可以如下表示:
(截自文首链题解 )
由于( 1 + i ) 4 = ( 1 − i ) 4 = − 4 (1+i)^4=(1-i)^4=-4 ( 1 + i ) 4 = ( 1 − i ) 4 = − 4 ,所以原式= 2 n + 2 ( − 4 ) n 4 4 = 2 n − 2 + ( − 1 ) n 4 2 n − 2 2 =\frac {2^n+2(-4)^{\frac{n}{4}}} {4}=2^{n-2}+(-1)^{\frac {n} {4}}2^{\frac{n-2}{2}} = 4 2 n + 2 ( − 4 ) 4 n = 2 n − 2 + ( − 1 ) 4 n 2 2 n − 2
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=998244353 ;ll qm (ll a,ll b) { ll ans=1 ; while (b) { if (b&1 )ans=(ans%M)*(a%M)%M; a=a*a%M; b>>=1 ; } return ans%M; }int main () { ll n,ans; cin>>n; ans=qm (2 ,n-2 >>1 ); if ((n/4 )&1 )ans=M-ans; ans+=qm (2 ,n-2 ); ans%=M; printf ("%lld" ,ans); return 0 ; }
G 机器人
排序问题,高精度(或__int128)
思路
首先所有系数均大于0;
我们可以把机器人的排序问题转化成从初始状态的有限次相邻对换;
接下来我们要判断对换的条件:
假设现有两机器人 i , i + 1 ,传入数据为 X ;
则对换前的传出数据X o l d = a i a i + 1 X + a i + 1 b i + b i + 1 X_{old}=a_ia_{i+1}X+a_{i+1}b_i+b_{i+1} X o l d = a i a i + 1 X + a i + 1 b i + b i + 1 ;
对换后的传出数据X n e w = a i a i + 1 X + a i b i + 1 + b i X_{new}=a_ia_{i+1}X+a_{i}b_{i+1}+b_{i} X n e w = a i a i + 1 X + a i b i + 1 + b i ;
若使X n e w > X o l d X_{new}>X_{old} X n e w > X o l d ,则需满足a i − 1 b 1 > a i + 1 − 1 b i + 1 \frac{a_i-1}{b_1}>\frac{a_{i+1}-1}{b_{i+1}} b 1 a i − 1 > b i + 1 a i + 1 − 1 ;
即所有机器人应按照a i − 1 b 1 \frac{a_i-1}{b_1} b 1 a i − 1 升序排列。
由于答案上限在 2 1 20 ≈ 2.7 e 26 21^{20}≈2.7e26 2 1 2 0 ≈ 2 . 7 e 2 6 左右,所以要高精度处理。
类似于这道题 耍杂技的牛
代码
下面的是高精度的,上面的直接用__int128了,大整数真香
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=998244353 ;void print (__int128 x) { if (x < 0 ) { x = -x; putchar ('-' ); } if (x > 9 ) print (x/10 ); putchar (x%10 + '0' ); }struct r { int a,b; }rob[22 ];bool cmp (r a,r b) { return (a.a*1.0 -1 )/a.b<(b.a*1.0 -1 )/b.b; }int main () { int n,x,a,b,i; cin>>n>>x; __int128 ans=x; for (i=1 ;i<=n;i++)scanf ("%d%d" ,&rob[i].a,&rob[i].b); sort (rob+1 ,rob+n+1 ,cmp); for (i=1 ;i<=n;i++) ans=ans*rob[i].a+rob[i].b; print (ans); return 0 ; }
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=998244353 ; vector<int > ans;vector<int > mul (vector<int > &A, int b) { vector<int >mid; int t = 0 ; for (int i = 0 ; i < A.size () || t; i++) { if (i < A.size ()) t += A[i] * b; mid.push_back (t % 10 ); t = t / 10 ; } while (mid.size () > 1 && mid.back () == 0 )mid.pop_back (); return mid; }struct r { int a,b; }rob[22 ];bool cmp (r a,r b) { return (a.a*1.0 -1 )/a.b<(b.a*1.0 -1 )/b.b; }int main () { int n,x,a,b,i; cin>>n>>x; ans.push_back (x); for (i=1 ;i<=n;i++)scanf ("%d%d" ,&rob[i].a,&rob[i].b); sort (rob+1 ,rob+n+1 ,cmp); for (i=1 ;i<=n;i++) { ans=mul (ans,rob[i].a); ans[0 ]+=rob[i].b; } ans=mul (ans,1 ); reverse (ans.begin (), ans.end ()); for (vector<int >::iterator it=ans.begin ();it!=ans.end ();it++) printf ("%d" ,*it); return 0 ; }
H 动态最小生成树
贪心,并查集
思路
由于数据比较水,按天空之城的代码直接改就行,暴力过;
注意要保证初始路的顺序,摘取和排序可以用另一个组解决。
代码
天空之城的注释还没删/
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;int fa[2002 ],cnt[2002 ]; ll len[2002 ];struct road { int fr,to; ll lrd; } rod[30004 ],z[30004 ];bool cmp (road a,road b) { return a.lrd<b.lrd; }int find (int x) { if (fa[x]!=x)fa[x]=find (fa[x]); return fa[x]; }int main () { int n,m,q,i,g1,g2; ll glen; scanf ("%d%d%d" ,&n,&m,&q); { for (i=1 ; i<=m; i++) { scanf ("%d%d%lld" ,&g1,&g2,&glen); rod[i].fr=g1,rod[i].to=g2,rod[i].lrd=glen; } while (q--) { int o; scanf ("%d" ,&o); if (o==1 ) { scanf ("%d%d%d%lld" ,&i,&g1,&g2,&glen); rod[i].fr=g1,rod[i].to=g2,rod[i].lrd=glen; } else { int l,r; scanf ("%d%d" ,&l,&r); for (i=l;i<=r;i++)z[i-l].fr=rod[i].fr,z[i-l].to=rod[i].to,z[i-l].lrd=rod[i].lrd; sort (z,z+1 +r-l,cmp); for (i=1 ; i<=n; i++)fa[i]=i,cnt[i]=1 ,len[i]=0 ; for (i=0 ; i<=r-l; i++) { int frn=z[i].fr,ton=z[i].to; if (find (frn)!=find (ton)) { if (find (frn)>find (ton))swap (frn,ton); cnt[find (frn)]+=cnt[find (ton)]; len[find (frn)]+=len[find (ton)]+z[i].lrd; fa[find (ton)]=find (frn); } } if (cnt[1 ]==n)printf ("%lld\n" ,len[1 ]); else printf ("Impossible\n" ,cnt[1 ]); } } } return 0 ; }
I 贪吃蛇
bfs
思路
bfs板题,身体不会消失即代表不能回到重复坐标。
注意单位转换。
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; queue<pair<int ,int > > que;const ll M=1000 ;int a[102 ][102 ]={0 },n,m,fi,fj,ti,tj,l[102 ][102 ]={0 };void bfs () { int di[]={0 ,1 ,0 ,-1 },dj[]={1 ,0 ,-1 ,0 }; while (!que.empty ()) { pair<int ,int > now=que.front (); que.pop (); for (int i=0 ;i<4 ;i++) { int ni=now.first+di[i],nj=now.second+dj[i]; if (ni>=0 &&ni<=n&&nj>=0 &&nj<=m&&a[ni][nj]==1 &&!l[ni][nj]) { l[ni][nj]=l[now.first][now.second]+1 ; que.push ({ni,nj}); } } } }int main () { int i,j; string g; scanf ("%d%d" ,&n,&m); scanf ("%d%d" ,&fi,&fj); scanf ("%d%d" ,&ti,&tj); for (i=1 ;i<=n;i++) { cin>>g; for (j=1 ;j<=m;j++) { if (g[j-1 ]=='.' )a[i][j]=1 ; } } l[fi][fj]=1 ; que.push ({fi,fj}); bfs (); if (l[ti][tj]) printf ("%d00" ,l[ti][tj]-1 ); else printf ("-1" ); return 0 ; }
J 天空之城
并查集,贪心
思路
出于由于天空之城具有魔力,如果希达想再走一次自己之前走过的路,则她可以在这条路上不花费任何时间。
这一性质,我们可以将目前 可以被联通的城市块放入同一个并查集中,如果某条道路可以连接任意两个不同集合中的城市,那么我们就可以认为这两个集合连通,且连通的代价为该路的代价。
至于上一段的“目前 ”,出于贪心策略,我们应该优先检索代价更低的道路,如果它能连通两个不同集,新集的连通代价 即为两该路代价与两旧集的连通代价三者之和,新集的城市数即为两旧集城市数之和。
为方便结果判断,我们可以规定某集合的根为该集合内序号最小的点,如此,如果路网能抵达所有城市,该集合的根即为1,开始时将起始点标记成1号即可;
之后补充:我这不就是Kruskal求的最小生成树 嘛,流下了什么都没学过的泪水。
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;int fa[5003 ],cnt[5003 ]; ll len[5003 ];struct road { int fr,to; ll lrd; }rod[200005 ];bool cmp (road a,road b) { return a.lrd<b.lrd; } map<string,int > mp;int find (int x) { if (fa[x]!=x)fa[x]=find (fa[x]); return fa[x]; }int main () { int n,q,i; while (~scanf ("%d%d" ,&n,&q)) { mp.clear (); int ccty=0 ,glen; string g1,g2; cin>>g1; mp[g1]=(++ccty); for (i=1 ;i<=q;i++) { cin>>g1; cin>>g2; scanf ("%d" ,&glen); if (!mp[g1])mp[g1]=++ccty; if (!mp[g2])mp[g2]=++ccty; rod[i].fr=mp[g2],rod[i].to=mp[g1],rod[i].lrd=glen; } if (ccty!=n){printf ("No!\n" );continue ;} sort (rod+1 ,rod+1 +q,cmp); for (i=1 ;i<=n;i++)fa[i]=i,cnt[i]=1 ,len[i]=0 ; for (i=1 ;i<=q;i++) { int frn=rod[i].fr,ton=rod[i].to; if (find (frn)!=find (ton)) { if (find (frn)>find (ton))swap (frn,ton); cnt[find (frn)]+=cnt[find (ton)]; len[find (frn)]+=len[find (ton)]+rod[i].lrd; fa[find (ton)]=find (frn); } } if (cnt[1 ]==n)printf ("%lld\n" ,len[1 ]); else printf ("No!\n" ); } return 0 ; }
ED
终于不用担心掉分了/