10-30 NOIP模拟赛

10-30 NOIP模拟赛

今天分数还看的过去,只是第二题没有正解,第三题没有35我表示很伤心。必须继续努力,保持内心纯净,心无杂念,知行合一,摒除恶念。

100 + 80 + 5 = 185 芜湖!

T1 新的阶乘(factorial)

题目描述

我们定义 \(f(x)=x^1×(x−1)^2×(x−2)^3…2^{x−1}×1^x\),请求出 \(f(n)\) 的质因子分解形式。

输入数据 1

5

输出数据 1

f(5)=2^8*3^3*5

首先观察数据范围 \(2≤n≤10^7\),首先就想到 \(O(n)\) 的做法,这道题唯一需要我们做的就是把合数分解成其的质因数,我们考虑遇到一个合数怎么办,如果把它分解成质因数肯定是不怎么优的,实际上这么做也确实只有30分,只能过 \(10^5\)

其实接下来就很轻松想到,其实只要把每个数分解成两个数相乘即可,这样从大枚举到小,就能把所有的数全部都给分解掉。怎么把每个数拆成两个数呢,试除肯定是不现实的,质数需要枚举的 \(\sqrt n\),就用线性筛即可,不是质数的数,开一个数组记录其最小质因子。

代码也很好懂的啦!

#include<bits/stdc++.h>
using namespace std;
#define N 12000010
int prime[N],cnt=0;
int n,sp[N];
long long m[N];
bool isp[N];

void primes(int x){
	memset(isp,1,sizeof(isp));
	for(int i=2;i<=x;i++){
		if(isp[i]) prime[++cnt]=i;
		for(int j=1;j<=cnt;j++){
			if(i*prime[j]>x) break;
			isp[i*prime[j]]=0;sp[i*prime[j]]=prime[j];
			if(i%prime[j]==0) break;
		}
	}
}

int main(){
	freopen("factorial.in","r",stdin);
	freopen("factorial.out","w",stdout); 
	scanf("%d",&n);
	primes(n);
	for(int i=n;i>=2;i--){
		long long num=n-i+1;
		if(isp[i]) m[i]+=num;
		else{
			m[sp[i]]+=num+m[i];
			m[i/sp[i]]+=num+m[i];
			m[i]=0;
		}
	}
	printf("f(%d)=",n);
	int s=2;
	while(!m[s]) s++;
	printf("%d",s);
	if(m[s]>1) printf("^%lld",m[s]);
	for(int i=s+1;i<=n;i++){
		if(m[i]){
			printf("*%d",i);
			if(m[i]>1) printf("^%lld",m[i]);
		}
	}
	return 0;
}

T2 博弈树(tree)

题目描述

Alice 和 Bob 又开始玩游戏了, 他们已经把石子堆得比山还高了, 现在他们要玩一种更新奇的游戏,这种游戏的规则如下:

  • 给定一颗 \(n\) 个节点的树,Alice 和 Bob 随机选择一个节点作为起点放上棋子,由 Alice 先手。
  • 轮到一方后可以将这颗棋子移动到树上任意一点,每次一方移动的距离必须比对方上一次移动的距离还要大,开始时默认为 0 。
  • 当一方不能再次移动之后判负。

现在 Alice 和 Bob 已经找到了一棵节点编号为 \(1∼n\) 的树准备开始游戏,作为博亦高手,Alice 和 Bob 均会做出最优的选择,选择一个节点后,他们知道游戏必然有一种必胜策略,现在他们想知道游戏的胜负,他们会询问你 \(q\) 次,每次他们会选择一个节点询问,你只需要回答在最优策略下以这个为节点为起点的胜者是谁即可。

输入数据 1

6 3
1 2
2 3
2 4
1 5
4 6
2
3
1

输出数据 1

Bob
Alice
Alice

这是很少遇到的比较有意思的 T2,只不过呢,就是正解有点太简单了。

你告诉我直接输出 Alice 有70分?????(好歹来个捆绑啊

先讲暴力 80 分做法:
受到昨天坐的 NOIP2021 T2 的暴力启发,我打了一个记忆化爆搜,即 \(f_{i,j}\) 表示当前在编号为 \(i\) 的节点上,上一步走的距离为 \(j\),然后每次询问暴力枚举当前点能走到的所有点,如果有一个点必输,那么当前点必赢,如果全部都必赢,那么当前点必败。

80 分代码:

#include<bits/stdc++.h>
using namespace std;
#define N 300005
int fir[N],nex[N],to[N],tot=0;
int n,q;
int f[5010][5010];
int dep[5010][5010];

void add(int x,int y){
	nex[++tot]=fir[x];
	fir[x]=tot;
	to[tot]=y;
}

void find(int x,int u,int fa){
	dep[x][u]=dep[x][fa]+1;
	for(int e=fir[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa) continue;
		find(x,v,u);
	}
}

int dfs(int u,int x){
	if(f[u][x]) return f[u][x];
	if(dep[u][u]!=-520){
		dep[u][u]=-520;
		dep[u][0]=-1;
		find(u,u,0);
	}
	for(int i=1;i<=n;i++){
		if(i==u) continue;
		if(dep[u][i]>x){
			if(dfs(i,dep[u][i])==2)
				return f[u][x]=1; 
		}
	}
	return f[u][x]=2;
}

int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	for(int i=1;i<=q;i++){
		int u;
		scanf("%d",&u);
		if(!f[u][0]) dfs(u,0);
		if(f[u][0]==2){
			printf("Bob\n");
		}
		else{
			printf("Alice\n");
		}
	}
	return 0;
}

然后来思考正解:

我们考虑先手的位置如果在直径端点的话一定是先手必胜的,否则先手一定不能将点移动到直径端点,于是我们考虑删除了原树所有直径端点的树,如果初始点在这棵树上为直径端点那么也一定是先手必胜的,因为先手可以将点移动到另一个直径端点,这样后手就一定会将点移动到原树的直径端点上,并且移动的长度一定小于原树直径,这样先手就可以将点移动到原树的另一个直径端点取得胜利。

所以这样我们可以将树的叶子一层一层的删下去,如果最后删剩下一个点那么在这个点是后手必胜的(因为先手无论如何都会将这个点移动到某一层的直径端点),否则根据我们之前的证明先手一定可以通过不断将点移动到下一层的直径端点取得胜利。

直接实现是 \(O(n^2)\) 的,不过我们注意到原树的直径中点一定是删一层后树的直径中点,于是直接判断原树直径长度即可。

AC 代码:

#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n,q;
vector<int>e[N];
int fa[N];
int p=1,dep[N],st,ed;
int bs;
void dfs(int u, int f){
	dep[u]=dep[f]+1;
	fa[u]=f;
	if (dep[u]>dep[p]) p=u;
	for (auto v:e[u]) {
		if (v==f) continue;
		dfs(v,u);
	}
}
int main() {
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	scanf("%d%d",&n,&q);
	for (int i=1,u,v;i<n;++i) {
		scanf("%d%d",&u,&v);
		e[u].emplace_back(v);
		e[v].emplace_back(u);
	}
	dfs(1, 0);st=p;
	dfs(p, 0);ed=p;
	if(dep[ed] & 1){
		bs=ed;
		for (int i=0;i<dep[ed]/2;++i) bs=fa[bs];
	}
	for(int i=1,u;i<=q;++i) {
		scanf("%d",&u);
		puts(u == bs ? "Bob" : "Alice");
	}
	return 0;
}

T3 划分(divide)

题目描述

对于一个长度为 \(n\) 的 01 字符串 \(S\),请你求出将其分为至少 \(k\) 段,将每一段看为二进制数求和后的最大值以及取到这个最大值的划分方案的数量。

输入格式

输入的第一行包含两个正整数 \(n, k\),保证 \(n \leq 2 \times 10^{6}\)\(1 \leq k \leq n\)

输入的第二行包含一个长度为 \(n\) 的字符串 \(S\),保证 \(S\) 只包含字符 \(0,1\)

输出格式

输出一行两个整数,分别表示最大的和 \(\bmod \ 998244353\) 的值以及此时划分的方案数 \(\bmod\ 998244353\) 的值。

posted @ 2023-11-02 15:39  alloverzyt  阅读(5)  评论(0编辑  收藏  举报