【题解】 AtCoder Beginner Contest 319

发布时间 2023-09-10 07:06:16作者: linyihdfj

没有写 F,不确定我的做法对不对。
评价:什么牛逼场次,代码大赛是嘛,从 A 开始就感觉到不对了,而且题面写的真答辩。

A.Legendary Players

题目分析:

直接按题目模拟即可。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	string s;
	cin>>s;
	if(s == "tourist")	printf("3858\n");
	if(s == "ksun48")	printf("3679\n");
	if(s == "Benq")	printf("3658");
	if(s == "Um_nik")	printf("3648");
	if(s == "apiad")	printf("3638");
	if(s == "Stonefeang")	printf("3630");
	if(s == "ecnerwala")	printf("3613");
	if(s == "mnbvmar")	printf("3555");
	if(s == "newbiedmy")	printf("3516");
	if(s == "semiexp")	printf("3481");
	return 0;
}

B.Measure

题目分析:

直接枚举 \(i,j\) 然后判断一下就好.

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;scanf("%d",&n);
	for(int i=0; i<=n; i++){
		char s = '-';
		for(int j=1; j<=9; j++){
			if(n % j != 0)	continue;
			if(i % (n/j)== 0){
				s = '1' + j - 1;
				break;
			}
		}
		cout<<s;
	}
	return 0;
}

C.False Hope

题目分析:

一个比较好写的写法就是:直接枚举看到的顺序。
然后枚举 \(i < j < k\),若 \(p_i,p_j,p_k\) 这三个位于同一条线然后就判断一下是不是值满足条件即可。
对于判断同一条线,行和列是简单的,同一条对角线就是 \(x+y\)\(x-y\) 为定值。

代码:

点击查看代码
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
PII pos[100];
int a[100],p[100],tot,ans;
bool vis[100];
void chk(){
	++tot;
	for(int i=1; i<=9; i++){
		for(int j=i+1; j<=9; j++){
			for(int k=j+1; k<=9; k++){
				if(a[p[i]] == a[p[j]] && a[p[j]] != a[p[k]]){
					PII b[4] = {pos[p[i]],pos[p[j]],pos[p[k]]};
					bool flag = false;
					if(b[0].first + b[0].second == b[1].first + b[1].second && b[0].first + b[0].second == b[2].first + b[2].second)	flag = true;
					if(b[0].first - b[0].second == b[1].first - b[1].second && b[0].first - b[0].second == b[2].first - b[2].second)	flag = true;
					if(b[0].first == b[1].first && b[0].first == b[2].first)	flag = true;
					if(b[0].second == b[1].second && b[0].second == b[2].second)	flag = true;
					if(!flag)	continue;
					ans += 1;
					return;
				}
			}
		}
	}
}
void dfs(int now){
	if(now == 10){
		chk();
		return;
	}
	for(int i=1; i<=9; i++){
		if(vis[i])	continue;
		p[now] = i; vis[i] = true;
		dfs(now+1);
		vis[i] = false;
	}
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	for(int i=1; i<=3; i++){
		for(int j=1; j<=3; j++){
			scanf("%d",&a[(i-1)*3 + j]);
			pos[(i-1)*3 + j] = {i,j};
		}
	}
	dfs(1);
	double tmp = tot;
	tmp = 1.0 * ans / tmp;
	printf("%.9f",1 - tmp); 
	return 0;
}

D.Minimum Width

题目分析:

所占的行数显然随着宽度的增加而单调不增,所以可以直接二分宽度。
判断占了多少行就是根据题目进行模拟。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int n,m,l[N];
int get(int len){
	int now = len;
	int cnt = 1;
	for(int i=1; i<=n; i++){
		if(l[i] > len)	return m + 1;
		if(l[i] > now)	now = len,++cnt;
		if(l[i] <= now)	now = now - l[i] - 1;
	}
	return cnt;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1; i<=n; i++)	scanf("%lld",&l[i]);
	int L = 1,R = 1e15,ans = 0;
	while(L <= R){
		int mid = (L + R) >> 1;
		if(get(mid) <= m){
			ans = mid;
			R = mid - 1;
		}
		else	L = mid + 1;
	}
	printf("%lld\n",ans);
	return 0;
}

E.Bus Stops

题目分析:

一个想法就是建分层图,对于每一个点拆成模 \(p_i\)\(0,1,2,p_i-1\)\(p_i\) 个点,但是不同车站之间无法连边。
所以考虑将每一个点拆成模 \(lcm(p_1,p_2,p_3,\cdots)\) 下的几个点,这样不同车站之间的边就很好连了。
但是其实建分层图是个很傻的行为,因为车站之间只能通过公交车相互到达,所以可以直接枚举 \(lcm(p_1,p_2,p_3,\cdots)\) 个起始时间,然后贪心地得到答案即可。
这里的 \(lcm\) 经过计算不会超过 \(840\)

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5+5;
int n,f[850],p[N],t[N];
int solve(int now){
	int ans = 0;
	for(int i=1; i<n; i++){
		int tmp = p[i] - now % p[i];
		if(tmp == p[i])	tmp = 0;
		now = (now + tmp + t[i])%840;
		ans = ans + tmp + t[i];
	}
	return ans;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int x,y;scanf("%lld%lld%lld",&n,&x,&y);
	for(int i=1; i<n; i++)	scanf("%lld%lld",&p[i],&t[i]);
	for(int i=0; i<=839; i++)	f[i] = solve(i);
	int q;scanf("%lld",&q);
	while(q--){
		int k;scanf("%lld",&k);
		printf("%lld\n",f[(k+x)%840] + x + y + k);
	}
	return 0;
}

G.Counting Shortest Paths

题目分析:

注意到我们只需要在所有的边都被删完了,再进行询问。(我一开始以为每一次删边都需要询问,然后就被卡卡卡)
所以可以考虑直接从小到大枚举最短路长度,也就是从 \(1\) 开始一步步扩展直到扩展到 \(n\)
所以一个直观的想法就是一步步预处理出 \(f[i][j]\) 表示 \(1\)\(i\) 路径长度为 \(j\) 的方案数,\(O(n)\) 显然。
考虑优化,\(f[i][j]\) 的第二维显然只有当 \(j\)\(1\)\(i\) 的最短路时才有意义,所以可以将其它的所有忽略,总状态数变成了 \(O(n)\)
对于转移,因为是现在的点对其余所有的点产生贡献,所以可以直接一起转移,只有 \(O(m)\) 次转移才是需要特殊注意的,所以只需要管这 \(O(m)\) 次转移,转移的总复杂度为 \(O(m)\)
\(nm\) 同阶,则总时间复杂度为 \(O(n)\)
代码中为了好写,使用了 set 复杂度会变成 \(O(n \log n)\)

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
const int MOD = 998244353;
set<int> E[N],cur,lst,tmp;
int cnt[N],a[N];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,m;scanf("%lld%lld",&n,&m);
	for(int i=1; i<=m; i++){
		int u,v;scanf("%lld%lld",&u,&v);
		E[u].insert(v);
		E[v].insert(u);
	}
	cnt[n] = 1;
	cur.insert(n);
	for(int i=1; i<n; i++)	lst.insert(i);
	while(!cur.empty()){
//		for(auto now : cur)	printf("%lld ",now);
//		printf("\n"); 
		int res = 0;
		for(auto now : cur)	res = (res + cnt[now])%MOD;
		for(auto to : lst)	cnt[to] = (cnt[to] + res)%MOD;
		for(auto now : cur){
			for(auto to : E[now]){
				if(lst.find(to) != lst.end())  //避免奇奇怪怪的相互影响 
					cnt[to] = (cnt[to] - cnt[now] + MOD)%MOD;
			}
		}
		for(auto now : cur){
			int tot = 0;
			for(auto to : E[now])	a[++tot] = to;
			for(int i=1; i<=tot; i++){
				E[a[i]].erase(now);
				E[now].erase(a[i]);
			}
		}
		swap(tmp,lst);
		cur.clear(),lst.clear();
		for(auto i : tmp){
			if(cnt[i]){
				cur.insert(i);
				if(i == 1){
					printf("%lld\n",cnt[1]);
					return 0;
				}
			}
			else	lst.insert(i);
		}
		tmp.clear();
	}
	printf("-1\n");
	return 0;
}