Gym-100506解题报告

比赛传送门

A. Average distance

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

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

By cxm1024

#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\) 个点 \(m\) 条边的无向连通图,没有边权。选中了 \(k\) 个特殊点,你需要确定一个中心点,使其到特殊点的距离最大值最小。距离为最短路长度。答案相同输出编号较小的中心点。\(T\le 100,n\le 10^4,m\le 5\times 10^4,k\le 200\)

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

By cxm1024

#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\) 种不同的钞票 \(a_1,a_2\cdots a_n\),每种任意张,可以将钞票砍成面值为原来 \(1/2\) 的两半,可以有找零,问有限次操作是否能购买价值为 \(x\) 的物品。\(x\) 小数点后最多 \(2\) 位。

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

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

By cxm1024

#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\) 个单词的集合,每个单词可以使用任意多次,只能使用恰好 \(m\) 个单词。每次询问组成长度为 \(k\) 的字符串的方案数。

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

By cxm1024

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

By cxm1024

#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\) 个人在比赛中的提交记录,输出排名信息。排名规则如下:

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

按照题意模拟即可。需要充分利用 STL 中的容器。

By cxm1024

#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\) 天你会获得 \(x_i\) 股(可以保存到后面),当天卖价为 \(p_i\),当天最多能卖 \(m_i\) 股。问最大赚钱量。

考虑最后一天获得的股票必须最后一天卖,剩下卖不掉的只能舍弃。倒数第二天可以选择当天卖还是到最后一天卖(如果有剩余),显然应该贪心地选最贵的那一天卖,用完剩余量了再选次贵的那一天卖,以此类推。

这个做法的本质在于股票之间没有区别,所以可以贪心选最贵的。如果这个不卖给最贵的而将前面几天的卖给最贵的,没有任何区别。

By cxm1024

#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;
}
posted @ 2023-03-01 15:40  曹轩鸣  阅读(30)  评论(0编辑  收藏  举报