选择问题,经典DP
目录
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间方案数。
可以看到他的思路和我在大方向上一致,那他是怎么优化的?
- 化时间为空间:
- 因数(约数)预处理
- 不用递归用数组。
- 容斥原理直接跳过被使用过的因数。
由题意可知: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;
}