【题解】AtCoder Regular Contest 162

A.Ekiden Race

题目描述:

n 个人参加了往返赛跑,每个人有一个编号 1n。已知以下信息:

  • 如果按照往路的成绩排序,那么任何两个人的成绩都不相同。同时第 i 个人在往路中排名第 i
  • 如果按照往返的成绩排序,那么任何两个人的成绩都不相同。同时第 i 个人在往返中排名第 pi
  • 如果復路的成绩最快,那么获得復路奖的人是復路成绩最快的人。

请计算有多少人可能得到了復路奖项。

T 个测试用例,请对于每个测试用例输出答案。

(翻译者注:往路指来回的第一段路程,復路指来回的第二段路程。)

2n103

题目分析:

如果 a 在第一段路快于 b,而经过了第二段路之后 a 慢于 b,则必然有 a 第二段路慢于 b
否则无法确定 a,b 的第二段路的快慢,也就是可以直接认为 a,b 都有可能。
直接这样判就可以了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
		int ans = n+1;
		int tot = 0;
		for(int i=n; i>=1; i--){
			if(a[i] < ans)	++tot;
			ans = min(ans,a[i]);
		}
		printf("%d\n",tot);
	}
	return 0;
}

B.Insertion Sort 2

题目描述:

给定 1,2,...,N 的排列 P=(P1,P2,...,PN)

最多进行 2×103 次操作,每次操作满足 1iN1,0jN2,选取整数 i,j,从 P 中取出 (Pi,Pi+1) 得到序列 Q=(Q1,Q2,...,QN2),则将 P(Pi,Pi+1) 替换成序列 Qjj+1 位置上的数,得到新的排列 P

判断是否能够通过这样的操作使 P 变成升序排列,如果可以,给出操作步骤。
2  N  103

题目分析:

一个显然的想法就是一个个数地确定,也就是先把 1 通过操作放到第一个,然后 2 放到第二个然后依次类推。
但是现在的有的一个问题就是如果我们现在要放 x,但是 x 是序列中的最后一个数就做不了这个操作了,可是我们可以做类似如下的操作:(n2,n1,n) (n1,n,n2) 这样 Pn 就不在最后了,我们就可以操作它了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3+5;
vector<pair<int,int> > v;
int n,a[N],b[N];
void solve(int pos1,int pos2){
	if(!(1 <= pos1 && pos1 <= n-1))	return;
	if(!(0 <= pos2 && pos2 <= n-2))	return;
	v.push_back({pos1,pos2});
	int tot = 0;
	if(tot == pos2)	b[++tot] = a[pos1],b[++tot] = a[pos1+1];
	for(int i=1; i<=n; i++){
		if(i == pos1 || i == pos1 + 1)	continue;
		b[++tot] = a[i];
		if(tot == pos2)	b[++tot] = a[pos1],b[++tot] = a[pos1+1];
	}
	for(int i=1; i<=n; i++)	a[i] = b[i];
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
	for(int i=1; i<=n; i++){
		if(a[i] == i)	continue;
		if(a[n] == i)	solve(n-1,n-3);
		int pos = i;
		for(int j=i+1; j<=n; j++){
			if(a[j] == i)	pos = j;
		}
		if(pos > i)	solve(pos,i-1);
	}
	if(a[n] != n)	printf("No\n");
	else{
		printf("Yes\n");
		printf("%d\n",(int)v.size());
		for(auto i : v)	printf("%d %d\n",i.first,i.second);
	}
	return 0;
}

C.Mex Game on Tree

题目描述:

有一棵 n 个节点且以 1 为根的数。

每个节点上有一个数表示颜色。

  • 如果为 1,表示没有填颜色。
  • 否则,表示填的颜色。

AliceBob 轮流对没填颜色的节点填上 0nAlice 先手)。

填完后,如果某个点和它的子树颜色的 mexkAlice 胜,否则为 Bob

2  N  103

题目分析:

BoB 的策略十分显然,就是填 k,所以只要让 Bob 走一步,那么 Alice 之前的所有布局就全没用了。
因为是考虑某一个子树,所以不妨对每一棵子树都求一下是不是可以使得它的 mexk
因为我们对每一棵子树都考虑了,所以若 Alice 走两步可以让某个子树变成合法,要么就是那棵子树走一步就可以合法,要么就是受到前一步的影响,而第二种情况显然 Bob 可以直接堵掉 Alice 的第二步,就结束了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int n,k,cnt,head[N],a[N];
bool vis[N],ans;
vector<int> v;
void add_edge(int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
void get_val(int now,int fath){
	v.push_back(a[now]);
	for(int i=head[now]; i; i=e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		get_val(to,now);
	}
}
void dfs(int now,int fath){
	for(int i=head[now]; i; i=e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);
	}
	
	get_val(now,fath);
	int flag = 0;
	for(auto i : v){
		if(i == -1){
			flag++;
			continue;
		}
		vis[i] = true;
	}
	int tmp = 0;
	while(vis[tmp])	++tmp;
	if(flag && tmp < k){
		++tmp;
		while(vis[tmp])	++tmp;
	}
	if(tmp == k && flag <= 1)	ans = true;
	for(auto i : v){
		if(i != -1)	vis[i] = false; 
	}
	v.clear();
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&k);
		for(int i=2; i<=n; i++){
			int fa;scanf("%d",&fa);
			add_edge(fa,i);add_edge(i,fa);
		}
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
		dfs(1,0);
		if(ans)	printf("Alice\n");
		else	printf("Bob\n");
		cnt = 0;
		for(int i=1; i<=n; i++)	head[i] = 0;
		ans = false;
	}
	return 0;
}

D.Smallest Vertices

题目描述:

在本问题中,当我们提到有根有向树时,我们指的是所有边都指向从根到叶子的有根树。

给定一个使得其总和为 N1 的非负整数序列 d=(d1,d2,,dN)

对于带编号从 1N 的顶点,假设 1 是根,我们将其点度数定义为 di

我们称满足以下条件的根付有向树为好树

  • i 的出度是 di

此外,对于好树的顶点 v,定义 f(v) 为“包含顶点 v 的子树中的顶点(包括 v)的顶点编号的最小值”。我们将满足 f(v)=v 的顶点称为好顶点

求好树中所有好顶点的总数,将其对 998244353 取模后的余数。

  • 2  N  500
  • 0  di  N1
  • d1  1
  • i=1N di = N1

题目分析:

显然可以考虑对于每一个点作为好节点的方案数求和就是答案。

v=1dv=0,则任意一个好树都是合法的,这个时候根据 prufer 序列,显然方案数就是:

(n2)!u(d1,u1)!

其中 di,j 表示以 i 为根的时候 j 的度数。
否则的话,考虑以 v 为根的子树只能包含 [v,n] 这些点,但是好像除了这个就发现不了什么式子了,就不大能做。
所以不妨设 Sv 子树内的点的点集,看看这个时候的方案数是什么:

(|S|2)!uS(dv,u1)!×(n|S|+12)!uS(d1,u1)!=(|S|2)!(n|S|1)!d1dvu(du!)

第一个式子就是考虑 v 的子树的方案数,乘以其余点构成一棵树的方案数,其中在其余点构成的树中 v 所在子树充当一个叶子。
第二个式子就是考虑如下的事实:

di,j={dji=jdj+1ij

所以带入就得到了第二个式子。
注意为了可以组成一棵树,一个隐形的限制就是:uSdu=|S|1
所以此时 dp 状态就相当显然了,dp[i][j][k] 表示大于等于 i 的点,选择了 j 个点,du 之和为 k 的方案数。
这样就可以直接统计方案数,然后乘以上面的系数就可以得到答案。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505;
const int MOD = 998244353;
int f[N][N][N],d[N],fac[N],inv[N];
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}
void add(int &a,int b){
	a = (a + b) % MOD;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;scanf("%lld",&n);
	fac[0] = 1;
	for(int i=1; i<=n; i++)	fac[i] = fac[i-1] * i % MOD;
	inv[n] = power(fac[n],MOD-2);
	for(int i=n-1; i>=0; i--)	inv[i] = inv[i+1] * (i+1) % MOD;
	for(int i=1; i<=n; i++)	scanf("%lld",&d[i]);
	f[n+1][0][0] = 1;
	for(int i=n; i>=1; i--){
		for(int j=0; j<=n; j++){
			for(int k=0; k<=n; k++){
				add(f[i][j][k],f[i+1][j][k]);
				if(j-d[i]>=0&&k-1>=0)	add(f[i][j][k],f[i+1][j-d[i]][k-1]);
			}
		}
	}
	int tmp = fac[n-2];
	for(int i=1; i<=n; i++){
		if(i == 1)	tmp = tmp * inv[d[i]-1] % MOD;
		else tmp = tmp * inv[d[i]] % MOD;
	}
	int mul = 1,ans = 0;
	for(int i=1; i<=n; i++)	mul = mul * inv[d[i]] % MOD;
	for(int i=1; i<=n; i++){  //统计 i 为好节点的贡献 
		if(i == 1 || d[i] == 0){
			add(ans,tmp);
//			printf("%lld %lld\n",i,tmp);
			continue;
		}
		for(int j=1; j<=n; j++){  //枚举 [i,n] 选择了多少个节点 
			int sum_d = j - 1;
			if(sum_d-d[i]>=0 && j-1>=0 && j-2>=0 && n-j-1>=0)
				add(ans,f[i+1][sum_d-d[i]][j-1] * d[1] %MOD* d[i] %MOD* fac[j-2] %MOD* fac[n-j-1]%MOD * mul%MOD);
		}
//		printf("%lld %lld\n",i,ans);
//		ans = 0;
	}
	printf("%lld\n",ans);
	return 0;
}

E.Strange Constraints

题目描述:

给定长度为 n 的序列 A,求序列 B 的个数模 998244353,满足以下条件:

  • 值域 [1,n]
  • i 的个数不超过 Ai
  • Bi 的个数不超过 Ai

1n500

题目分析:

考虑设 di 表示数 i 出现的次数,则题目条件可以转化为:

  • diAi
  • i 可以放在位置 j,则必然满足 diAj

ci=j=1n[iAj],则 ci 就代表出现次数为 i 的数可选的种类数,以及出现次数为 i 的数在 B 中可以放的位置数。
所以这个东西直接 dp 去做就很简单了,也就是设 dp[i][j][k] 表示考虑了出现次数大于等于 i 的数,总共选择了 j 种数,放在 Bk 个位置上。
转移就是枚举出现次数等于 i 的数有 x 个:

dp[i+1][j][k]×(cijx)×(cik)!(i!)x(cikix)!dp[i][j+x][k+ix]

转移的两个系数:第一个就是选择哪些数,第二个就是这些数放在哪些位置。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505;
const int MOD = 998244353;
int f[N][N][N],fac[N],inv[N],a[N],c[N];
int binom(int n,int m){
	if(n < m || n < 0 || m < 0)	return 0;
	return fac[n] * inv[m] % MOD * inv[n-m]%MOD;	
}
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1)	res = res * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}
void add(int &a,int b){
	a = (a + b)%MOD;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;scanf("%lld",&n);
	fac[0] = 1;
	for(int i=1; i<=n; i++)	fac[i] = fac[i-1] * i % MOD;
	inv[n] = power(fac[n],MOD-2);
	for(int i=n-1; i>=0; i--)	inv[i] = inv[i+1] * (i+1) % MOD;
	for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
	for(int i=0; i<=n; i++){
		for(int j=1; j<=n; j++){
			if(a[j] >= i)	c[i]++;
		}
	}
	f[n+1][0][0] = 1;
	for(int i=n; i>=1; i--){
		for(int j=0; j*i<=n; j++){
			if(j > c[i])	break;
			for(int k=0; k<=n; k++){
				if(k > c[i])	break;
				for(int x=0; x*i<=n; x++){
					if(k + i * x > c[i])	break;
					if(j+x<=n && k+i*x<=n && c[i]-j>=0 && c[i]-k>=0 && c[i]-k-i*x>=0)
						add(f[i][j+x][k+i*x],f[i+1][j][k] * binom(c[i]-j,x) %MOD* fac[c[i]-k] %MOD* power(inv[i],x) %MOD* inv[c[i]-k-i*x]%MOD);
				}
			}
		}
	}
	int ans = 0;
	for(int i=1; i<=n; i++)	add(ans,f[1][i][n]);
	printf("%lld\n",ans);
	return 0;
}
posted @   linyihdfj  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示