Gym-100506解题报告

比赛传送门

A. Average distance

题意:有一棵 nn 节点的带边权树,求不同点对的平均距离。

平均距离即为总距离除以数量,所以考虑总距离。显然可以树形 DP,让点对的贡献在 LCA 处统计。维护 f[u]f[u] 表示以 uu 为根的子树内所有节点到 uu 的距离之和,sz[u]sz[u] 表示子树大小,转移显然。统计答案时,对于每个儿子 uv(w)u\to v(w),考虑从 vv 内的每个节点往其他子树的贡献都需要先到 uu,而往其他子树贡献的次数为 sz[u]sz[v]sz[u]-sz[v],所以每个子树 vv 造成 (f[v]+w)(sz[u]sz[v])(f[v]+w)*(sz[u]-sz[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

给你一个 nn 个点 mm 条边的无向连通图,没有边权。选中了 kk 个特殊点,你需要确定一个中心点,使其到特殊点的距离最大值最小。距离为最短路长度。答案相同输出编号较小的中心点。T100,n104,m5×104,k200T\le 100,n\le 10^4,m\le 5\times 10^4,k\le 200

显然可以对于每个特殊点跑一遍 BFS,更新每个节点到最远的特殊点的距离,最后统计最远距离最小的点作为答案即可。复杂度 O(Tkm)O(Tkm)

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

题意:有 nn 种不同的钞票 a1,a2ana_1,a_2\cdots a_n,每种任意张,可以将钞票砍成面值为原来 1/21/2 的两半,可以有找零,问有限次操作是否能购买价值为 xx 的物品。xx 小数点后最多 22 位。

转化一下,即为 k1a1+k2a2++knan=2pxk_1a_1+k_2a_2+\cdots+k_na_n=2^px。为了避免两位小数的影响,同时乘 100100,得 25(k1a1+k2a2+knan)=2p100x25(k_1a_1+k_2a_2+\cdots k_na_n)=2^p\cdot 100x(左边因数 44 可以归到右边 2p2^p 内),其中 100x100x 为整数。

首先判断右边 100x100x 是否为 2525 的倍数。如果是则可以除到右边,转化为 k1a1+k2a2++knan=2p4xk_1a_1+k_2a_2+\cdots+k_na_n=2^p\cdot 4x,其中 4x4x 为整数。这个式子显然为裴蜀定理的形式,有解的充要条件为 2p4x2^p\cdot 4xgcd(a1,a2,,an)\gcd(a_1,a_2,\cdots,a_n) 的倍数。由于有一个 2p2^p,所以可以将 gcd\gcd 中所有 22 的因数全部消掉,判断剩下的 4x4x 是否为删掉 22 的因数的 gcd\gcd 的倍数即可。

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

题意:给你一个 nn 个单词的集合,每个单词可以使用任意多次,只能使用恰好 mm 个单词。每次询问组成长度为 kk 的字符串的方案数。

每个单词只有长度信息有用,所以存数组 a[i]a[i] 表示第 ii 个单词的长度。考虑 DP:设 f[i][j][k]f[i][j][k] 表示考虑前 ii 个单词,已经用了 jj 个,长度为 kk 的方案数。则转移枚举这一个单词使用几次,用插板法组合数求这些单词放置的位置即可。

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;//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] 表示如果当前走到 (i,j)(i,j) 的期望得分,则如果有洞直接得到答案;如果没有障碍,f[i][j]=f[i+1][j]f[i][j]=f[i+1][j];如果有障碍,f[i][j]=12f[i+1][j1]+12f[i+1][j+1]f[i][j]=\frac{1}{2}f[i+1][j-1]+\frac{1}{2}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

题意:给定 nn 个人在比赛中的提交记录,输出排名信息。排名规则如下:

  1. 做题数越多越好。
  2. 做题相同,罚时越低越好。每道题罚时为,这道题的第一次 AC 时间,加上在第一次 AC 之前的错误提交次数*20min。总罚时为每道 AC 的题目的罚时之和。
  3. 罚时相同,考虑两队最晚的“得分/罚时不同”的时刻,此时得分越高越好,得分相同罚时越低越好。
  4. 得分罚时的时间完全相同,则被认为平局。输出时排名要相同,但要先输出名字字典序较小的。

按照题意模拟即可。需要充分利用 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

题意:有一个股票,第 ii 天你会获得 xix_i 股(可以保存到后面),当天卖价为 pip_i,当天最多能卖 mim_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;
}