Codeforces Round #563 (Div. 2)-E. Ehab and the Expected GCD Problem

53 篇文章 0 订阅
4 篇文章 0 订阅

地址:https://codeforces.com/contest/1174/problem/E

思路:dp  官方题解https://codeforces.com/blog/entry/67388

我们称一个好的置换中的第一个元素为s。那么,s必须有最大可能的质因数个数。此外,每次在移动前缀时更改gcd时,必须只从其中删除一个素数除数。

这样,可以保证我们有尽可能多的不同的gcds。现在,关于s有两个重要的意见:

#1:s=2^x*3^y,换句话说,s只有2和3两个质因子,那是因为如果s有某个素因子 (5,7,11,...),你至少可以把它换成2^2。这样,就会有更多质因数。

# 2:y <=1。这是因为如果s=2^x*3^y,且y≥2,那么就可以用2^(x+3)*3^(y - 2)来代替它(除以9,再乘以8),这样就会得到更多的素数因子。

创建dp[i][x][y],也就是从列表下标1->i时的良好排列的方法的数量,它的gcd就是2^x*3^y。

让f (x, y) = n/(2^x∗3^y)。它表示n中 2^x*3^y的倍数个数。有3种转变方式


1.添加2^x*3^y的倍数。这样,gcd就不会改变。可以添加f(x,y)个数,但是已经添加了其中的i,所以:

dp[i+1][x][y]=dp[i+1][x][y]+dp[i][x][y]*(f(x,y) - i)

2.把x减1。为此,可以添加一个2^(x - 1)*3^y的倍数,而2^x*3^y是其的倍数,因此:

dp[i+1][x - 1][y]=dp[i+1][x - 1][y]+dp[i][x][y]*(f(x−1,y)−f(x,y)).

3.把y减1。为此,可以添加一个2^x*3^(y-1)的倍数,而2^x*3^y是其的倍数,因此:

dp[i+1][x][y−1]=dp[i+1][x][y−1]+dp[i][x][y]*(f(x,y−1)−f(x,y)).

令p=log2(n)。可以从2^p开始,所以dp[1][p][0]=1。另外,如果2^(x-1)*3≤n,可以从2^(x-1)*3开始,所以dp[1][p-1][1]=1。

因为到末尾是gcd肯定为1,所以答案是dp[n][0][0]。

 

Code :

#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;

const int MAX_N=1e6+5;
const LL MOD=1e9+7;
int n;
int dp[MAX_N][21][2];

int f(int x,int y){
	int tmp=(1<<x);
	if(y)	tmp*=3;
	return n/tmp;
}
int main()
{
	ios::sync_with_stdio(false); 
	cin>>n;
	int p=0;
	while ((1<<p)<=n)
		++p;
	--p;
	dp[1][p][0]=1;
	if((1<<(p-1))*3<=n)	dp[1][p-1][1]=1;
	for(int i=1;i<n;++i)
		for(int x=0;x<=p;++x)
			for(int y=0;y<=1;++y)
			{
				dp[i+1][x][y]=(dp[i+1][x][y]+(LL)dp[i][x][y]*(f(x,y)-i))%MOD;
				if(x)	dp[i+1][x-1][y]=(dp[i+1][x-1][y]+(LL)dp[i][x][y]*(f(x-1,y)-f(x,y)))%MOD;
				if(y)	dp[i+1][x][y-1]=(dp[i+1][x][y-1]+(LL)dp[i][x][y]*(f(x,y-1)-f(x,y)))%MOD;
			}
	cout<<dp[n][0][0]<<endl;
	
	return 0;
}

 

Code 优化空间:

#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;

const int MAX_N=1e6+5;
const LL MOD=1e9+7;
int n;
LL dp[2][21][2];

int f(int x,int y){
	int tmp=(1<<x);
	if(y)	tmp*=3;
	return n/tmp;
}
int main()
{
	ios::sync_with_stdio(false); 
	cin>>n;
	int p=0;
	while ((1<<p)<=n)
		++p;
	--p;
	dp[1][p][0]=1;
	if((1<<(p-1))*3<=n)	dp[1][p-1][1]=1;
	int u,v;
	for(int i=1;i<n;++i)
		for(int x=0;x<=p;++x)
		{
			u=i%2;	v=(i+1)%2;
			dp[v][x][0]=dp[v][x][1]=0;
			for(int y=0;y<=1;++y)
			{
				dp[v][x][y]=(dp[v][x][y]+(LL)dp[u][x][y]*(f(x,y)-i))%MOD;
				if(x)	dp[v][x-1][y]=(dp[v][x-1][y]+(LL)dp[u][x][y]*(f(x-1,y)-f(x,y)))%MOD;
				if(y)	dp[v][x][y-1]=(dp[v][x][y-1]+(LL)dp[u][x][y]*(f(x,y-1)-f(x,y)))%MOD;
			}
		}
	cout<<dp[n%2][0][0]<<endl;
	
	return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值