CSP 202104-4 DP

校门外的树

题意

给定 n 个障碍物,在 [ a 1 , a n ] [a_1,a_n] [a1,an] 之间种树,树应该是 等间隔 的,要求在每个子区间(树和端点间)构成一个 等差数列 (每个子区间至少种一棵树),不同子区间公差可以不同。

划分子区间:
在这里插入图片描述
在全区间:
在这里插入图片描述
求出所有美观种树的方案数,结果对 1 0 9 + 7 10^9+7 109+7 取模。
在这里插入图片描述

样例说明

Input
3
0 2 6
Output
3

Input
11
0 10 20 30 40 50 60 70 80 90 100
Output
256507

在这里插入图片描述

Round 1

这题看着就是个dp,哈哈哈哈,但我暂时不想想优化,万物先暴力,我先暴力一手玩会儿吧,好久没写dp了呀~(纯粹是不想写那啥另外一场第三题的大模拟哈哈哈哈哈,我选择先跑路写这个…)

你看看上面的数据量,暴力dp能整个60分呢~

果然最近BUG改多了想来写写算法题找找快乐 呜呜呜~

思路

因为要构成等差数列,最大的间隔 dmax=(a[j]-a[i])/2
看看上面那个0,2,6的样例:
[0,2] 间 只能 d=1
[2,6] 间 d=1,d=2   dmax: (6-2)/2=2
[0,6] 间 d=3   dmax: (6-0)/2=3
总方案数:1*2 子区间 +1 全区间 =3
在这里插入图片描述
c n t [ 1 ] = 1 cnt[1] = 1 cnt[1]=1
c n t [ 2 ] = c n t [ 1 ] ∗ d p [ 1 ] [ 2 ] cnt[2] = cnt[1] * dp[1][2] cnt[2]=cnt[1]dp[1][2]
c n t [ 3 ] = c n t [ 1 ] ∗ d p [ 1 ] [ 3 ] + c n t [ 2 ] ∗ d p [ 2 ] [ 3 ] cnt[3] = cnt[1] * dp[1][3] + cnt[2] * dp[2][3] cnt[3]=cnt[1]dp[1][3]+cnt[2]dp[2][3]
c n t [ 4 ] = c n t [ 1 ] ∗ d p [ 1 ] [ 4 ] + c n t [ 2 ] ∗ d p [ 2 ] [ 4 ] + c n t [ 3 ] ∗ d p [ 3 ] [ 4 ] cnt[4] = cnt[1] * dp[1][4] + cnt[2] * dp[2][4] + cnt[3] * dp[3][4] cnt[4]=cnt[1]dp[1][4]+cnt[2]dp[2][4]+cnt[3]dp[3][4]
c n t [ 5 ] = c n t [ 1 ] ∗ d p [ 1 ] [ 5 ] + c n t [ 2 ] ∗ d p [ 2 ] [ 5 ] + c n t [ 3 ] ∗ d p [ 3 ] [ 5 ] + c n t [ 4 ] ∗ d p [ 4 ] [ 5 ] cnt[5] = cnt[1] * dp[1][5] + cnt[2] * dp[2][5] + cnt[3] * dp[3][5] + cnt[4] * dp[4][5] cnt[5]=cnt[1]dp[1][5]+cnt[2]dp[2][5]+cnt[3]dp[3][5]+cnt[4]dp[4][5]

=>

c n t [ j ] = ∑ i = 1 j c n t [ i ] ∗ d p [ i ] [ j ] ,   c n t [ 1 ] = 1 cnt[j]=\begin{matrix} \sum_{i=1}^{j} cnt[i]*dp[i][j] ,\ cnt[1]=1 \end{matrix} cnt[j]=i=1jcnt[i]dp[i][j], cnt[1]=1


好,接下来我们要想一想 dp怎么求了,暴力法就是像上面的手推一样,去遍历区间里的每个公差d计算咯。

//#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
using namespace std;
const int mod=1e9+7;
const int maxn=1e3+5;//对了 如果选择暴力就不要试图开1e5了炸内存..会编译错误的
int a[maxn];
long long int cnt[maxn];//注意这里要开long long
long long int dp[maxn][maxn];//不然溢出...会只有30分
int vis[maxn];//标记障碍物
int n;
int find_dp(int l,int r)
{
    int dmax=(a[r]-a[l])/2;
    int cnt=0;
    for(int i=1;i<=dmax;i++)//遍历公差咯
    {
        int k;
        for(k=a[l]+i;k<=a[r];k+=i)//去看会不会碰到障碍物咯
        {
            if(vis[k])
                break;
        }
        if(k==a[r])
            cnt++;
    }
    return cnt;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        vis[a[i]]=1;
    }    
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dp[i][j]=find_dp(i,j);

    cnt[1]=1;
    for(int j=2;j<=n;j++)//套上面推出来的递推式子咯
        for(int i=1;i<j;i++)
            cnt[j]=(cnt[j]%mod+cnt[i]*dp[i][j]%mod)%mod;
    
    cout<<cnt[n]<<endl;
    return 0;
}

Round 2

暴力版的耗时就在于dp[i][j]的计算,所以要提速主要就在于如何 优化dp[i][j]的计算

你看啊,这不就是要遍历的是 a[j]-a[i] 的约数嘛,看哪些会撞上障碍物,哪些不会~(上面的暴力不就是直接一个一个加公差看会不会碰到咯) 所以,做个 约数优化 咯~

首先你可以预处理出 maxn=1e5+5 范围内的所有数的约数,一个区间内的方案数必然会 <= 约数个数。然后我们要想怎么样能不碰到障碍物,我们可以划分子区间,从 j-1 开始 从后往前 枚举左端点 i,示例流程如下:
在这里插入图片描述
在这里插入图片描述

//#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int mod=1e9+7;
const int maxn=1e5+5;
int a[maxn];
long long int cnt[maxn];//注意这里要开long long
//long long int dp[maxn][maxn];//1e5会炸内存了宝...这里不能再开这个数组了
int vis[maxn];//标记障碍物
int n;
vector<int> v[maxn],vv[maxn];
void get_divisors(int n)
{
	for(int i=1;i<=n/i;i++)
	{
		if(n%i==0)
		{
			v[n].push_back(i);
			if(i!=n/i)//把更大的那个约数加进来
			v[n].push_back(n/i);
		}
	}
    
	//sort(v[n].begin(),v[n].end()); //这里我们并不需要排序,我们只是要用到所有的因子
	/*
    for(int i=0;i<v[n].size();i++)
	    cout<<v[n][i]<<" ";
	cout<<endl;
    */
}
int main()
{
    //预处理出1e5+5范围内所有数的约数
    for(int i=1;i<=maxn;i++)
        get_divisors(i);
       
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];  
    
    cnt[1]=1;
    for(int j=2;j<=n;j++)
    {
        memset(vis,0,sizeof(vis));
        for(int i=j-1;i>=1;i--)
        {
            int d=a[j]-a[i];
            int num=0;
            for(int k=0;k<v[d].size();k++)
            {
                int x=v[d][k];
                if(vis[x])
                    continue;
                //cout<<x<<endl;
                num++;
                vis[x]=1;
            }
            num=num-1;//注意,上面没法排除等于自身长度d的约数的情况,所以要-1
            //cout<<d<<" "<<num<<endl;
            vis[d]=1;
            cnt[j]=(cnt[j]%mod+cnt[i]*num%mod)%mod;
        }
    }
    cout<<cnt[n]<<endl;
    return 0;
}

注意:自身长度d的约数的情况,num最后要-1
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值