AtCoder Regular Contest 116(C~E)

AtCoder Regular Contest 116(C~E)

C - Multiple Sequences

题意:给一个n,m要求构造一个长为n的数组,使得每个后一位是前一位的倍数,并且所有值小于等于m,求方案数。

题解:

如何不重不漏的计算所有方案,我考虑枚举最后一位的值是多少,并求和。

那么问题就变成的已知最后一位,求有多少种方案使数组的前一位是后一位的倍数。

我们设dp[i] [j]表示长度为i时,第i位为j时的方案数。

易知dp[i] [j]可以由所有dp[i-1] [x] (x为j的因子)转移,那么时间复杂度就为n*m。

直接超时了。

考虑如何优化,很明显一个数被质因数分解后最多的大概在20个因子左右,那么一个数组要求每一位是前一位的倍数,其实最多就增长了20多次。

想当于,把因子视为板插入大小为n的数组里,每个板之间数都是一样大的,在

板交界处翻倍,经典隔板法求方案。

所以我们把dp[i] [j]表示长度为i,第i位j时的方案数(与上不同的是,这里前一位必须比j小,那么第一维就只要开20了)。

#include<iostream>
using namespace std;
#define ll long long
const ll mod=998244353;
const ll N=2e5+7;
ll n,m;
ll f[27][N];
ll fac[N];
ll qpow(ll x,ll n) {
	ll res=1;
	for (;n;n>>=1,x=x*x%mod){
		if (n&1)res=res*x%mod;
	}
	return res;
}
ll inv(ll a){
	return qpow(a,mod-2)%mod;
}
void solve(){
	fac[0]=1;
	for(int i=1;i<N;i++) {
		fac[i]=(fac[i-1]*i)%mod;
	}
}
ll comb(ll n,ll k){
	if(k>n)return 0;
	if(k==1)return n;
	return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
int main(){
	solve();
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++){
		f[1][i]=1;
	}
	for(int i=1;i<=20;i++){
		for(int j=1;j<=m;j++){
			for(int k=2;k*j<=m;k++){
				f[i+1][k*j]+=f[i][j];
				f[i+1][k*j]%=mod;
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=20;i++){
		for(int j=1;j<=m;j++){
			ans=(ans+f[i][j]*comb(n-1,i-1)%mod)%mod;
		}
	}
	cout<<ans<<"\n";
}

D - I Wanna Win The Game

题意:给一个n,m要求构造一个长为n的数组,使得所有数之和为m,且所有数的异或和为0求方案数。

题解:

首先思考怎么时异或和为0,显然将数组中所有数按二进制拆开,如果所有二进制数的个数之和为偶数的话,那么数组的异或和就为0。

所以我们其实是要将m个小球放入n个盒子里,同时放的时候要按二进制放并且只能放偶数个(就先放偶数个1,再放偶数个2,再放偶数个4)有点类似于硬币问题求方案数同时在加个组合数学。

#include<iostream>
using namespace std;
#define ll long long
const ll N=5007;
const ll mod=998244353;
ll fac[N];
ll pow(ll x,ll n,ll mod){
    ll res=1;
	while(n>0){
	   if(n%2==1){
	   	 res=res*x%mod;
	   }
	   x=x*x%mod;
	   n>>=1;
	}
	return res;
}
ll inv(ll a){
	return pow(a,mod-2,mod);
}
void solve(){
	fac[0]=1;
	for(int i=1;i<N;i++) {
		fac[i]=(fac[i-1]*i)%mod;
	}
}
ll comb(ll n,ll k){
	if(k>n)return 0;
	if(k==1)return n;
	return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
ll er[20],c[5007][5007];
ll n,m;
ll f[5007][5007];
void init(){
	er[0]=1;
	for(int i=1;i<=20;i++){
		er[i]=er[i-1]*2;
	}
	for(int i=0;i<=n;i++){
		c[n][i]=comb(n,i);
	}
}

int main(){
	solve();
	scanf("%lld%lld",&n,&m);
	init();
	f[0][0]=1;
	for(int i=1;i<=20;i++){
		if(er[i-1]>m){
			printf("%lld\n",f[i-1][m]);
			break;
		}
		for(int j=0;j<=n;j+=2){
			ll lin=er[i-1]*j;
			if(lin>m)break;
			for(int k=lin;k<=m;k++){
				f[i][k]+=f[i-1][k-lin]*c[n][j]%mod;
				f[i][k]%=mod;
			}
		}
	}
	return 0;
}

E - Spread of Information

题意:给一个树,要你最多放k个点,以这k个点作为源点,bfs能到的最远距离最短。

题解:

我们直接二分答案,那么题目就转变成了:

1、最远距离为x,使用k个点是否够用。

我们可以在进行转换,若我们能求出:

2、使最远距离为x,最少可使用y个点。

通过比较y和x就可以判断x是否合法。

那么对于问题2,这是一个很经典的树形dp题,与 https://www.luogu.com.cn/problem/P2016 类似。只是上面题是距离为1的边,此题是距离为x的点。

但可以启发我们做此题,具体操作就是用两个数组,一个表示其子树最远为被感染的点的距离,一个表示子树中最近的被覆盖点的距离,两值相加小于x时,说明当前点也被感染。当最远未被感染的点距离为x时,说明此时必须覆盖此点已便感染儿子节点。

#include<iostream>
#include<vector>
using namespace std;
#define ll long long
const ll N=2e5+7;
const ll inf=1e9;
ll n,m;
vector<ll>ho[N];
ll f[N],d[N],X,cnt;
void dfs(ll p,ll fa){
	f[p]=inf;
	d[p]=0;
	for(int i=0;i<ho[p].size();i++){
		ll to=ho[p][i];
		if(to==fa)continue;
		dfs(to,p);
		f[p]=min(f[p],f[to]+1);
		d[p]=max(d[p],d[to]+1);
	}
	if(f[p]+d[p]<=X){
		d[p]=-inf;
	}
	else if(d[p]==X){
		cnt++;
		f[p]=0;
		d[p]=-inf;
	}
}
bool check(ll x){
	X=x;
	cnt=0;
	dfs(1,0);
	if(d[1]>=0){
		cnt++;
	}
	return cnt<=m;
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<n;i++){
		ll u,v;
		scanf("%lld%lld",&u,&v);
		ho[u].push_back(v);
		ho[v].push_back(u);
	}
	ll l=0,r=n,ans=n;
	while(l<=r){
		ll mid=(l+r)/2;
		if(check(mid)){
			ans=mid;
			r=mid-1;
		}
		else{
			l=mid+1;
		}
	}
	cout<<ans<<"\n";
}
posted @ 2021-04-15 11:18  ccsu_madoka  阅读(130)  评论(0编辑  收藏  举报