csp 202104-4 校门外的树 c++ AC题解

选择问题,经典DP

目录

O.题目

题解一 30分dp超时程序

题解二 优化时间复杂度 CPP11 AC

ac代码 


O.题目

着实是有点长3414. 校门外的树 - AcWing题库

题解一 30分dp超时程序

不会优化复杂度好菜qwq

简单说一下核心思路:用l,r记录左右路障位置的下标。flag[]用于记录路障,plant是一段区间的方案数(不算上子区间),主要是求此区间长度的因数,并排除有路障的方案。count函数是一个dp函数,已知r-1个区间的最多解,如果再加一个区间,则总体需加上这个循环:

for(ll i=l+1;i<r;i++)
{
    sum+=count(l,i)*plant(i,r);
}

最后加上[l,r]区间的plant方案数。

可以看到最后我们的程序时间复杂度爆炸!那如何来优化呢?

#include<bits/stdc++.h> 
#define ll long long
#define MAXN 100010
using namespace std;
ll n;
ll barrier[MAXN];
bool flag[MAXN];
ll plant(ll l,ll r)
{
	ll num=0;
	ll left=barrier[l];
	ll right=barrier[r];
	ll cnt=right-left;
	for(ll i=1;i<=(cnt/2);i++)
	{
		if(!(cnt%i))
		{
			ll j;
			for(j=left+i;j<right;j+=i)
			{
				if(flag[j])break;
			}
			if(j==right)num++;
		}
	}
	return num;
}
ll count(ll l,ll r)
{
	ll sum=0;
	if(l+1!=r)
	{
		for(ll i=l+1;i<r;i++)
		{
			sum+=count(l,i)*plant(i,r);
		}
	}
	sum+=plant(l,r);
	return sum;
}
 
int main()
{
	cin>>n;
	for(ll i=0;i<n;i++)
	{
		cin>>barrier[i];
		flag[barrier[i]]=true;
	}
	cout<<count(0,n-1)%1000000007;
}

题解二 优化时间复杂度 CPP11 AC

还得是y总:AcWing 3414. 校门外的树(CCF-CSP认证辅导课) - AcWing

下面写一下y总的思路:

首先:可以看到测试规模

 需要我们把时间复杂度控制在n^2.

由于1k以内的数约数个数最多为128,因此我们可以将时间复杂度控制在128*n^2,只需要提前预处理一下,算出1k以内数的所有因数就行。

预处理:

//首先预处理一下,找出所有的因子 
void pre_handle()
{
	for(int i=1;i<N;i++)
	{
		for(int j=2*i;j<N;j+=i)
		{
			obj[j].push_back(i);
		}
	}
}

那我们如何去计算呢?

首先进行一个分析:

  • 状态表示:f[i]
    • 集合:a0-ai的所有选项集合
    • 属性:数量
  • 状态计算:f[i]+=f[j]*cnt; f[j]是a0-aj的所有选项集合,cnt指aj-ai间方案数。

  可以看到他的思路和我在大方向上一致,那他是怎么优化的?

  1. 化时间为空间:
    1. 因数(约数)预处理
    2. 不用递归用数组。
  2. 容斥原理直接跳过被使用过的因数。

         

         由题意可知:aj在[j,i]区间的等差数点中,而不可能在[j',i]区间的等差数点中,因为路障不能种树。也就是说i-j不能作为[j',i]区间的公差。因此,设一个flag数组记录约数是否使用过,每次算完j,只需要把相应约数的flag设为1就行,算j'时跳过该因数。

核心代码:

	f[1]=1;//初始化 
	for(int i = 2 ; i <= n ; i ++ )
	{
        //每次i增加都要初始化flag,
		memset(flag , 0 ,sizeof(flag));
		for( int j = i - 1 ; j >= 1 ;j-- )
		{
            //遍历j,计算区间[j,i]之间的方案数
			int d = a[i] - a[j];
			cnt=0;
			for(int k:obj[d])
			{
                //将未使用的因数标为1,cnt增加
				if(!flag[k])
				{
					cnt++;
					flag[k] = 1 ;
				}
			}
            //最后不要忘了d不能作为其他区间的公差
			flag[d]=1;
            //f[i]+=f[j]*cnt
			f[i]=(f[i]+f[j]%mod*cnt%mod)%mod;
		}
	}

ac代码 

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N =1e5+10,mod=1e9+7,M=1010;
vector <int> obj[N];
bool flag[N];
int n,a[M],f[M],cnt;
//首先预处理一下,找出所有的因子 
void pre_handle()
{
	for(int i=1;i<N;i++)
	{
		for(int j=2*i;j<N;j+=i)
		{
			obj[j].push_back(i);
		}
	}
}
signed main()
{
	pre_handle();
	cin >> n;
	for(int i = 1 ; i <= n ; i ++ )
	{
		cin >> a[i];
	}
	f[1]=1; 
	for(int i = 2 ; i <= n ; i ++ )
	{
		memset(flag , 0 ,sizeof(flag));
		for( int j = i - 1 ; j >= 1 ;j-- )
		{
			int d = a[i] - a[j];
			cnt=0;
			for(int k:obj[d])
			{
				if(!flag[k])
				{
					cnt++;
					flag[k] = 1 ;
				}
			}
			flag[d]=1;
			f[i]=(f[i]+f[j]%mod*cnt%mod)%mod;
		}
	}
	cout<<f[n];
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值