CF1561D1 Up the Strip (simplified version)

题意:
给你一个长度为n的数组,对于数组的每个位置x,你可以执行两种操作:①.从1~x-1(包括1和x-1)中选一个数字y从x转移到y。②.从2 ~ x(包括2和x)选一个数字y从x转移到x/y(向下取整)这个位置。要求从位置n到达位置1的方案数有多少种。这里特别需要注意方案数的算法:假设当前在4号位置,通过操作①直接选择1号位置是一种方案,通过操作②选则4/4的位置进行转移也算一种方案,这两种情况算两种方案。
tle思路:
用dp的思想,首先设dp[n]=1,然后从n~1进行循环,设当前位置为i,首先把能通过操作①能到达的所有位置都加上dp[i],也就是dp[1 ~ i]+=dp[i].然后把能通过操作②到达的点都加上dp[i],也就是dp[i/(2 ~ i-1)]+=dp[i].复杂度为O(N^2),会超时。
代码示例:

#include<bits/stdc++.h>
using namespace std;
 
int dp[2*100005];
int main()
{
	int n,mod;
	cin>>n>>mod;
	dp[n]=1;
	for(int i=n;i>0;i--)
	{
		for(int j=1;j<i;j++)//i节点通过操作1能到达的节点j
		dp[j]+=dp[i],dp[j]%=mod;
		for(int j=2;j<=i;j++)//i节点通过操作2能到达的节点i/j(向下取整)
		{
			dp[i/j]+=dp[i];
			dp[i/j]%=mod;
		}
	}
	cout<<dp[1]<<endl;	
return 0;
}

ac思路:
需要将上面的两种操作进行优化,设当前位置为i,操作1:对于从i位置能通过操作①到达的位置为1~i-1这些节点的,也就是dp[1]+=dp[i],dp[2]+=dp[i]…dp[i-1]+=dp[i],对于下一次循环就是dp[1]+=dp[i-1],dp[2]+=dp[i-1],…,dp[i-2]+=dp[i-1],这里是不是能发现一个规律,对于任意一个位置x,他能从(x+1 ~n)这些地方通过操作1到达,也就是dp[x]+=dp[x+1]+dp[x+2]+…+dp[n],因为我们是从后往前遍历,那么那么我们只需要用一个sum记录当前i位置后面dp的和是多少,加到当前的dp[i]上就好了,也就是dp[i]+=sum,这样对于每个位置i就不需要通过O(n)来更新他能到达的位置了。操作2:操作22 ~ i(包括2i)选一个数字ji转移到i/j(向下取整)这个位置。上面tle的方法是从2 ~ i遍历j,然后让dp[i/j]+=dp[i],因为i/j这个数字可能重复出现多次(比如10/4=210/5=2),也就是对同一个dp[i/j]的值操作了多次,要尽量减少这样的重复操作。优化方法:因为通过操作②从位置i到位置y的方法数为i/y-i/(y+1),可以直接求得方法数。既然可以直接知道方法数,就可以直接针对每个dp[y]+=(i/y-i/(y+1))*dp[i]即可。i/j肯定有1 ~sqrt(i)这些数字,而sqrt(i) ~ i不一定能通过i/j获得,所以对于j从2 ~ sqrt(i)我们用遍历j2~ sqrt(i),**dp[i/j]+=dp[i]**获得,对与后面的i/j为1 ~ sqrt(i)的我们使用y从1~sqrt(n) dp[y]+=[i/y-i/(y+1)]× dp [i]获得。这里需要注意如果i/sqrt(i)==sqrt(i),会让dp[sqrt(i)]多加了一次dp[i],这种情况下要提前对dp[sqrt(i)] - =dp[i].

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[2*100005];
int main() {
	ll n,mod;
	cin>>n>>mod;
	dp[n]=1;
	ll sum=0;
	for(int i=n; i>=1; i--) {
		dp[i]+=sum;//位置(i+1~n)有sum种方法通过操作1到达当前位置i
		sum+=dp[i];//更新后的sum为 位置(i~n)有sum种方法通过操作1到i-1
		sum%=mod;
		dp[i]%=mod;
		ll m=(ll)sqrt(i);
		if(i>=2) {//i==1的时候操作2无法操作
			if(i/m==m)//dp[sqrt(i)]会多加一次,所以提前剪掉
				dp[m]-=dp[i];
			for(int j=1; j<=m; j++) {
				if(j!=1)//j从2~sqrt(i)每个dp[i/j]+=dp[i]
				dp[i/j]+=dp[i];
				dp[i/j]%=mod;
				int l=i/j;
				int r=i/(j+1);
				if(l>r&&i/j!=1)
					dp[j]+=(l-r)*dp[i];//y 从1~sqrt(i) 每个dp[y]+=[i/y-i/(y+1)]*dp[i]
					dp[j]%=mod;
			}
		}

	}
	cout<<dp[1]<<endl;
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值