比赛传送门
A. Average distance
题意:有一棵 n n n 节点的带边权树,求不同点对的平均距离。
平均距离即为总距离除以数量,所以考虑总距离。显然可以树形 DP,让点对的贡献在 LCA 处统计。维护 f [ u ] f[u] f [ u ] 表示以 u u u 为根的子树内所有节点到 u u u 的距离之和,s z [ u ] sz[u] s z [ u ] 表示子树大小,转移显然。统计答案时,对于每个儿子 u → v ( w ) u\to v(w) u → v ( w ) ,考虑从 v v v 内的每个节点往其他子树的贡献都需要先到 u u u ,而往其他子树贡献的次数为 s z [ u ] − s z [ v ] sz[u]-sz[v] s z [ u ] − s z [ v ] ,所以每个子树 v v v 造成 ( f [ v ] + w ) ∗ ( s z [ u ] − s z [ v ] ) (f[v]+w)*(sz[u]-sz[v]) ( f [ v ] + w ) ∗ ( s z [ u ] − s z [ v ] ) 的贡献。
By cxm1024
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;#define int long long vector<pair<int ,int > > a[10010 ]; int f[10010 ],sz[10010 ];int dfs (int now,int fa) { vector<pair<int ,int > > to; sz[now]=1 ; for (auto [v,w]:a[now]) { if (v==fa) continue ; int tmp=dfs (v,now); to.push_back ({v,tmp+w*sz[v]}); sz[now]+=sz[v]; } int res=0 ; for (auto [v,w]:to) { res+=w; f[now]+=w*(sz[now]-sz[v]); } return res; } signed main () { int t; scanf ("%lld" ,&t); while (t--) { int n,ans=0 ; scanf ("%lld" ,&n); for (int i=0 ;i<n;i++) a[i].clear (); memset (f,0 ,sizeof (f)); memset (sz,0 ,sizeof (sz)); for (int i=1 ;i<n;i++) { int x,y,z; scanf ("%lld%lld%lld" ,&x,&y,&z); a[x].push_back ({y,z}); a[y].push_back ({x,z}); } dfs (0 ,0 ); for (int i=0 ;i<n;i++) ans+=f[i]; printf ("%.7Lf\n" ,(long double )ans/(n*(n-1 )/2 )); } return 0 ; }
B. Bus Pass
给你一个 n n n 个点 m m m 条边的无向连通图,没有边权。选中了 k k k 个特殊点,你需要确定一个中心点,使其到特殊点的距离最大值最小。距离为最短路长度。答案相同输出编号较小的中心点。T ≤ 100 , n ≤ 1 0 4 , m ≤ 5 × 1 0 4 , k ≤ 200 T\le 100,n\le 10^4,m\le 5\times 10^4,k\le 200 T ≤ 1 0 0 , n ≤ 1 0 4 , m ≤ 5 × 1 0 4 , k ≤ 2 0 0 。
显然可以对于每个特殊点跑一遍 BFS,更新每个节点到最远的特殊点的距离,最后统计最远距离最小的点作为答案即可。复杂度 O ( T k m ) O(Tkm) O ( T k m ) 。
By cxm1024
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 #include <bits/stdc++.h> using namespace std;vector<int > a[10010 ]; int dis[10010 ];bool vis[10010 ];signed main () { int t; cin>>t; while (t--) { for (int i=1 ;i<=10000 ;i++) a[i].clear (); int n,m; cin>>n>>m; set<int > ver; for (int i=1 ;i<=n;i++) { int x,y,cnt; cin>>x>>cnt; ver.insert (x); while (cnt--) { cin>>y; a[x].push_back (y); } } set<int > st; for (int i=1 ;i<=m;i++) { int cnt,x; cin>>cnt; while (cnt--) { cin>>x; st.insert (x); } } memset (dis,0 ,sizeof (dis)); for (int s:st) { memset (vis,0 ,sizeof (vis)); queue<pair<int ,int > > q; q.push ({s,1 }); while (!q.empty ()) { auto [u,d]=q.front ();q.pop (); if (vis[u]) continue ; vis[u]=1 ,dis[u]=max (dis[u],d); for (int v:a[u]) if (!vis[v]) q.push ({v,d+1 }); } } int ans=0 ,mind=1e9 ; for (int i:ver) if (dis[i]<mind) ans=i,mind=dis[i]; cout<<mind<<" " <<ans<<endl; } return 0 ; }
C. Cutting Banknotes
题意:有 n n n 种不同的钞票 a 1 , a 2 ⋯ a n a_1,a_2\cdots a_n a 1 , a 2 ⋯ a n ,每种任意张,可以将钞票砍成面值为原来 1 / 2 1/2 1 / 2 的两半,可以有找零,问有限次操作是否能购买价值为 x x x 的物品。x x x 小数点后最多 2 2 2 位。
转化一下,即为 k 1 a 1 + k 2 a 2 + ⋯ + k n a n = 2 p x k_1a_1+k_2a_2+\cdots+k_na_n=2^px k 1 a 1 + k 2 a 2 + ⋯ + k n a n = 2 p x 。为了避免两位小数的影响,同时乘 100 100 1 0 0 ,得 25 ( k 1 a 1 + k 2 a 2 + ⋯ k n a n ) = 2 p ⋅ 100 x 25(k_1a_1+k_2a_2+\cdots k_na_n)=2^p\cdot 100x 2 5 ( k 1 a 1 + k 2 a 2 + ⋯ k n a n ) = 2 p ⋅ 1 0 0 x (左边因数 4 4 4 可以归到右边 2 p 2^p 2 p 内),其中 100 x 100x 1 0 0 x 为整数。
首先判断右边 100 x 100x 1 0 0 x 是否为 25 25 2 5 的倍数。如果是则可以除到右边,转化为 k 1 a 1 + k 2 a 2 + ⋯ + k n a n = 2 p ⋅ 4 x k_1a_1+k_2a_2+\cdots+k_na_n=2^p\cdot 4x k 1 a 1 + k 2 a 2 + ⋯ + k n a n = 2 p ⋅ 4 x ,其中 4 x 4x 4 x 为整数。这个式子显然为裴蜀定理的形式,有解的充要条件为 2 p ⋅ 4 x 2^p\cdot 4x 2 p ⋅ 4 x 是 gcd ( a 1 , a 2 , ⋯ , a n ) \gcd(a_1,a_2,\cdots,a_n) g cd( a 1 , a 2 , ⋯ , a n ) 的倍数。由于有一个 2 p 2^p 2 p ,所以可以将 gcd \gcd g cd 中所有 2 2 2 的因数全部消掉,判断剩下的 4 x 4x 4 x 是否为删掉 2 2 2 的因数的 gcd \gcd g cd 的倍数即可。
By cxm1024
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 #include <bits/stdc++.h> using namespace std;int gcd (int a,int b) { if (b==0 ) return a; return gcd (b,a%b); } signed main () { int t; cin>>t; while (t--) { double x; int n,m,d=0 ; cin>>x>>n; m=int (x*100 +0.5 ); for (int i=1 ;i<=n;i++) { int k; cin>>k; d=gcd (d,k); } while (d%2 ==0 ) d/=2 ; if (m%25 !=0 ) cout<<"no" <<endl; else if (m/25 %d==0 ) cout<<"yes" <<endl; else cout<<"no" <<endl; } return 0 ; }
D. Dice Password Security
题意:给你一个 n n n 个单词的集合,每个单词可以使用任意多次,只能使用恰好 m m m 个单词。每次询问组成长度为 k k k 的字符串的方案数。
每个单词只有长度信息有用,所以存数组 a [ i ] a[i] a [ i ] 表示第 i i i 个单词的长度。考虑 DP:设 f [ i ] [ j ] [ k ] f[i][j][k] f [ i ] [ j ] [ k ] 表示考虑前 i i i 个单词,已经用了 j j j 个,长度为 k k k 的方案数。则转移枚举这一个单词使用几次,用插板法组合数求这些单词放置的位置即可。
By cxm1024
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 #include <bits/stdc++.h> using namespace std;#define int long long const int MAXV=5 ;int jc[MAXV+5 ];int ksm (int a,int b,int res=1 ) { for (;b;a=a*a,b>>=1 ) if (b&1 ) res=res*a; return res; } void init () { jc[0 ]=1 ; for (int i=1 ;i<=MAXV;i++) jc[i]=jc[i-1 ]*i; } int C (int x,int y) { return jc[x]/jc[y]/jc[x-y]; } int a[10010 ],f[2 ][6 ][52 ];signed main () { init (); int t; cin>>t; while (t--) { memset (f,0 ,sizeof (f)); int n,m,q; cin>>n>>m>>q; for (int i=1 ;i<=n;i++) { string s; cin>>s; a[i]=s.size (); } f[0 ][0 ][0 ]=1 ; for (int i=1 ;i<=n;i++) { int x=i%2 ,y=1 -x; for (int j=0 ;j<=m;j++) for (int k=0 ;k<=50 ;k++) { f[x][j][k]=0 ; for (int cnt=0 ;cnt<=j&&cnt*a[i]<=k;cnt++) f[x][j][k]+=f[y][j-cnt][k-cnt*a[i]]*C (j,cnt); } } while (q--) { int l; cin>>l; cout<<f[n%2 ][m][l]<<endl; } } return 0 ; }
G. Pachinko
题意:有一个矩阵,有些格子有障碍,有些格子有洞,每个洞有权值。你可以将一个小球放在任意一列的顶端,经过洞会掉进洞里,得分为该洞的权值,碰到障碍会随机向左或向右。问最大期望得分。
显然可以 DP。f [ i ] [ j ] f[i][j] f [ i ] [ j ] 表示如果当前走到 ( i , j ) (i,j) ( i , j ) 的期望得分,则如果有洞直接得到答案;如果没有障碍,f [ i ] [ j ] = f [ i + 1 ] [ j ] f[i][j]=f[i+1][j] f [ i ] [ j ] = f [ i + 1 ] [ j ] ;如果有障碍,f [ i ] [ j ] = 1 2 f [ i + 1 ] [ j − 1 ] + 1 2 f [ i + 1 ] [ j + 1 ] f[i][j]=\frac{1}{2}f[i+1][j-1]+\frac{1}{2}f[i+1][j+1] f [ i ] [ j ] = 2 1 f [ i + 1 ] [ j − 1 ] + 2 1 f [ i + 1 ] [ j + 1 ] 。
By cxm1024
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;string s[110 ]; double f[110 ][110 ];signed main () { int t; cin>>t; while (t--) { int n,m; cin>>n>>m; for (int i=1 ;i<=n;i++) { cin>>s[i]; s[i]=" " +s[i]; for (int j=1 ;j<=m;j++) f[i][j]=0 ; } for (int j=1 ;j<=m;j++) if (s[n][j]!='*' &&s[n][j]!='.' ) f[n][j]=s[n][j]-'0' ; for (int i=n-1 ;i>0 ;i--) for (int j=1 ;j<=m;j++) { if (s[i][j]=='*' ) continue ; if (s[i][j]!='.' ) f[i][j]=s[i][j]-'0' ; else if (s[i+1 ][j]=='*' ) f[i][j]=f[i+1 ][j-1 ]*0.5 +f[i+1 ][j+1 ]*0.5 ; else f[i][j]=f[i+1 ][j]; } double ans=0 ; for (int j=1 ;j<=m;j++) ans=max (ans,f[1 ][j]); printf ("%.7lf\n" ,ans); } return 0 ; }
I. Ranking
题意:给定 n n n 个人在比赛中的提交记录,输出排名信息。排名规则如下:
做题数越多越好。
做题相同,罚时越低越好。每道题罚时为,这道题的第一次 AC 时间,加上在第一次 AC 之前的错误提交次数*20min。总罚时为每道 AC 的题目的罚时之和。
罚时相同,考虑两队最晚的“得分/罚时不同”的时刻,此时得分越高越好,得分相同罚时越低越好。
得分罚时的时间完全相同,则被认为平局。输出时排名要相同,但要先输出名字字典序较小的。
按照题意模拟即可。需要充分利用 STL 中的容器。
By cxm1024
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 #include <bits/stdc++.h> using namespace std;struct node { int pen; string st; vector<pair<int ,int > > sub; } a[55 ]; signed main () { int t; cin>>t; while (t--) { int n,m; cin>>n>>m; map<string,pair<int ,vector<pair<int ,int > > > > mp; map<pair<string,char >,pair<bool ,int > > done; map<string,int > ind; for (int i=1 ;i<=n;i++) { string s; cin>>s; mp[s]={0 ,vector<pair<int ,int > >()}; ind[s]=i,a[i].st=s; } for (int i=1 ;i<=m;i++) { int t; string tm,op; char pr; cin>>t>>tm>>pr>>op; if (op=="rejected" ) { if (!done[{tm,pr}].first) done[{tm,pr}].second+=20 ; } else { if (!done[{tm,pr}].first) { done[{tm,pr}].first=1 ; mp[tm].first+=t+done[{tm,pr}].second; mp[tm].second.push_back ({t,-done[{tm,pr}].second}); } } } for (auto tmp:mp) { int x=ind[tmp.first]; a[x].pen=tmp.second.first; a[x].sub=tmp.second.second; reverse (a[x].sub.begin (),a[x].sub.end ()); } sort (a+1 ,a+n+1 ,[](node p,node q) { if (p.sub.size ()!=q.sub.size ()) return p.sub.size ()>q.sub.size (); if (p.pen!=q.pen) return p.pen<q.pen; if (p.sub!=q.sub) return p.sub<q.sub; return p.st<q.st; }); for (int i=1 ,now=0 ;i<=n;i++) { if (i==1 ||a[i].sub!=a[i-1 ].sub) now=i; cout<<now<<" " <<a[i].st<<" " <<a[i].sub.size ()<<" " <<a[i].pen<<endl; } } return 0 ; }
J. Stock
题意:有一个股票,第 i i i 天你会获得 x i x_i x i 股(可以保存到后面),当天卖价为 p i p_i p i ,当天最多能卖 m i m_i m i 股。问最大赚钱量。
考虑最后一天获得的股票必须最后一天卖,剩下卖不掉的只能舍弃。倒数第二天可以选择当天卖还是到最后一天卖(如果有剩余),显然应该贪心地选最贵的那一天卖,用完剩余量了再选次贵的那一天卖,以此类推。
这个做法的本质在于股票之间没有区别,所以可以贪心选最贵的。如果这个不卖给最贵的而将前面几天的卖给最贵的,没有任何区别。
By cxm1024
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;#define int long long int x[100010 ],p[100010 ],m[100010 ];signed main () { int t; cin>>t; while (t--) { int n,ans=0 ; cin>>n; for (int i=1 ;i<=n;i++) cin>>x[i]>>p[i]>>m[i]; map<int ,int > mp; for (int i=n;i>0 ;i--) { mp[p[i]]+=m[i]; auto tmp=--mp.end (); while (x[i]>0 &&!mp.empty ()) { if (x[i]<(*tmp).second) (*tmp).second-=x[i],ans+=(*tmp).first*x[i],x[i]=0 ; else x[i]-=(*tmp).second,ans+=(*tmp).first*(*tmp).second,mp.erase (tmp); if (!mp.empty ()) tmp=--mp.end (); } } cout<<ans<<endl; } return 0 ; }