231030校内赛

T1 新的阶乘

有三种做法,第一种也就是我写的这种容易被评测机波动坑,复杂度玄学

考虑处理出每个数的质因数,然后就暴力除每个数的质因数的种类次

非常的简单,也容易被卡

第二种是与第一种差距不大,就是在线性筛中处理出质因数之后,再对每个数除以线筛中处理的质因数,将它的答案加到除数和商的答案里,质数本身不用处理

最后遍历每一个质数输出答案就行了

第三种是推式子,就是有点难想,我就不详说了

#include<bits/stdc++.h>
#define N 10000010
#define ll long long
using namespace std;
int n,p[N],is[N],cnt;
ll ans[N];
inline void pre(){
	is[0] = is[1] = 1;
	for(int i = 2;i<=n;++i){
		if(!is[i]) p[++cnt] = i,is[i] = i;
		for(int j = 1;j<=cnt&&i*p[j]<=n;++j){
			is[i*p[j]] = max(is[i*p[j]],p[j]);
			if(!(i%p[j])) break;
		}
	}
}
int main(){
	freopen("factorial.in","r",stdin);
	freopen("factorial.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	pre();
	for(int i = 2;i<=n;++i){
		int x = i;
		while(x>1){
			ans[is[x]]+=n-i+1;
			x/=is[x];
		}
	}
	cout<<"f("<<n<<")=";
	for(int i = 1;i<=cnt;++i){
		if(i!=1)cout<<"*";
		cout<<p[i];
		if(ans[p[i]]!=1) cout<<"^"<<ans[p[i]];
	}
	return 0;
}

T2 博弈树

一道需要推性质的题,你全输出 \(Alice\) 都有 \(70\)

我们考虑先手的位置如果在直径端点的话一定是先手必胜的,否则先手一定不能将点移动到直径端点

于是我们考虑删除了原树所有直径端点的树,如果初始点在这棵树上为直径端点那么也一定是先手必胜的

因为先手可以将点移动到另一个直径端点,这样后手就一定会将点移动到原树的直径端点上,并且移动的长度一定小于原树直径,这样先手就可以将点移动到原树的另一个直径端点取得胜利

所以这样我们可以将树的叶子一层一层的删下去,如果最后删剩下一个点那么在这个点是后手必胜的 ,因为先手无论如何都会将这个点移动到某一层的直径端点

否则根据我们之前的证明先手一定可以通过不断将点移动到下一层的直径端点取得胜利

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

如果不明白上述过程的话,其实可以自己求一下 \(SG\) 函数打个表就很容易发现规律了

#include<bits/stdc++.h>
#define N 100010
using namespace std;
struct edge{
	int v,ne;
}e[N<<1];
int cnt,h[N],dep[N],rt,lf,n,q,f[N];
void add(int u,int v){
	e[++cnt].v = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
int dfs(int x,int fa){
	f[x] = x;dep[x] = dep[fa]+1;
	for(int i = h[x];i;i = e[i].ne){
		int v = e[i].v;
		if(v!=fa){
			int tmp = dfs(v,x);
			if(dep[tmp]>dep[f[x]]) f[x] = tmp;
		}
	}
	return f[x];
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	for(int i = 1;i<n;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);add(v,u);
	}
	rt = dfs(1,0);
	lf = dfs(rt,0);
	for(int i = 1;i<=q;i++){
		int x;cin>>x;
		if((dep[lf]&1)&&f[x]==lf&&dep[x]==(dep[lf]+1)/2) cout<<"Bob\n";
		else cout<<"Alice\n";
	}
	return 0;
}

T3 划分

对于部分分的前 \(k\) 位没有 \(1\) 那么最优解一定是在第一个 \(1\) 之前分段,使用组合数计算

否则最优解一定会使自身尽量长,那么就是每回选出 \(n-k+1\) 的长度的一段,剩下的都是单独一段

考虑每个数的位权,每个方案对应的 \(n-k+1\) 长度的那一段的前 \(n-k\) 位一定相同

所以可以二分找到最大划分点,判断有多少划分点的前 \(n-k\) 位相同即可

需特判 \(n==k\)

#include<bits/stdc++.h>
#define mod 998244353
#define N 2000010
using namespace std;
int n,k,fac[N],inv[N],a[N],h[N],pw[N];
char s[N];
int ksm(int x,int y){
	int res = 1;
	while(y){
		if(y&1) res = 1ll*res*x%mod;
		x = 1ll*x*x%mod;
		y>>=1;
	}
	return res;
}
int C(int x,int y){
	if(x<y) return 0;
	return 1ll*fac[x]*inv[y]%mod*1ll*inv[x-y]%mod;
}
int hsh(int l,int r){
	return (h[r]-1ll*h[l-1]*pw[r-l+1]%mod+mod)%mod;
}
int main(){
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>k>>s+1;
	fac[0] = pw[0] = 1;
	for(int i = 1;i<=n;i++){
		fac[i] = 1ll*fac[i-1]*i%mod;
		pw[i] = 2*pw[i-1]%mod;
		h[i] = (h[i-1]*2+s[i]-'0')%mod;
	}
	inv[n] = ksm(fac[n],mod-2);
	for(int i = n-1;i>=0;i--)
		inv[i] = 1ll*inv[i+1]*(i+1)%mod;
	int p = n;
	for(int i = 1;i<=n;i++)
		if(s[i]=='1'){
			p = i-1;
			break;
		}
	if(p>=k-1){
		int val = 0,ans = 0;
		for(int i = p;i<=n;i++)
			val = (val*2+(s[i]-'0'))%mod;
		if(p==n) p--;
		for(int i = k-1;i<=p;i++)
			ans = (ans+C(p,i))%mod;
		cout<<val<<" "<<ans;
		return 0;
	}
	int mx = 1;
	for(int i = 2;i<=k;i++){
		int l = 1,r = n-k+1,ans = -1;
		while(l<=r){
			int mid = (l+r)>>1;
			if(hsh(i,i+mid-1)!=hsh(mx,mx+mid-1)){
				r = mid-1;ans = mid;
			}else l = mid+1;
		}
		if(ans!=-1&&s[i+ans-1]>s[mx+ans-1]) mx = i;
	}
	int ans = 0;
	for(int i = 1;i<mx;i++)
		ans+=(s[i]=='1');
	for(int i = mx+(n-k+1);i<=n;i++)
		ans+=(s[i]=='1');
	ans = (ans+hsh(mx,mx+n-k))%mod;
	int cnt = 0;
	for(int i = 1;i<=k;i++)
		if(hsh(i,i+n-k-1)==hsh(mx,mx+n-k-1))
			cnt++;
	cout<<ans<<" "<<(n==k?1:cnt);
	return 0;
}
posted @ 2023-10-30 19:35  cztq  阅读(5)  评论(0编辑  收藏  举报