csp 2021-04-4 校门外的树

此题极具学习价值,不学后悔系列😆
解答本题有两个关键点,动态规划约数优化加速
设障碍物编号为:a[0]~a[n-1],f[i]为到第i个障碍物的方案总数,则有状态转移方程为:
f[i]= ∑ j = 0 i − 1 f [ j ] \sum_{j=0}^{i-1} f[j] j=0i1f[j] * cnt(j,i) , f[0]=1
其中cnt(j, i)为第j个障碍物到第i个障碍物之间的方案数,注意这里是把a[j]和a[i]之间看作一个整体进行植树,不考虑分割情况,即在此区间里所有的树和a[j]、a[i]构成等差数列。这里我们可能一时间想不出来为什么状态转移方程会是这样,以及这样真的可以不重不漏地列举出所有的方案吗?
真的耶✌
嘿嘿,别捉急,我们看个栗子就明白了~

假设有三个障碍物:a0=0, a1=4, a2=10,很容易得知当前的方案数为2*3+1=7种。
其中2、3、1分别是a0和a1、a1和a2、a0和a2之间的方案数。
换一种计算方式:7=f[1]*cnt(1,2)+f[0]*cnt(0,2)
其中f[0]=1为初始设定,f[1]=2为a0和a1之间的方案数(取间隔为1、2),cnt(1,2)=3为a1和a2之间的方案数(取间隔为1、2、3),cnt(0,2)=1为a0和a2之间的方案数(取间隔为5)
到这里相信大家已经明白上述状态转移方程的逻辑了,关于更多障碍物的例子,大家不妨自己试试,使用上述动态规划方法能否不重不漏。

只要想到了动态规划,这道题基本上可以拿到60分了,满分解答此题的第二个关键在于怎么优化cnt(j,i)的计算。
我们很容易想到,第j个障碍物到第i个障碍物之间的 植树间隔 必须为a[i] - a[j]的 因子,方案数一定小于等于因子个数,因为这个间隔还不能撞上a[j]和a[i]之间的障碍物。我们倒着从i - 1开始枚举j,这样一开始i - 1和i之间是没有障碍物的,则a[i] - a[i - 1]的所有因子都满足条件;然后到了i - 2,同样先枚举a[i] - a[i - 2]的所有因子,这些因子中已经处于集合中的一定不可,等于a[i]-a[i-1]的也不可,因为按这样的间隔排列一定会有树遇上a[i - 1]这个障碍物;以此类推,倒着枚举到a[i] - a[k]因子的时候,如果已经在之前的枚举中使用过,则跳过,否则方案数就加一。我们使用一个状态数组st[M],保存某个因子是否已经使用过了,不要忘记外循环每一次都要先把状态数组清空,以及使用筛法预处理出1e5范围内每个数的因子。

#include <stdio.h>
#include <cstring>
#include <vector>

using namespace std;
typedef long long LL;
const int N = 1010, M=100010, mod=1e9+7;

int n, cnt=0;
int f[N], a[N];
vector<int> q[M];	//保存可能用到的所有距离的因子
bool st[M];	//状态数组
int main()
{
    for(int i=1; i<M; i++){	//使用倍数进行预处理,巧妙计算出每个数的因子
        for(int j=2*i; j<M; j+=i){
            q[j].push_back(i);
        }
    }
    f[0]=1;	//初始设定
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",a+i);
    }
    for (int i = 1; i < n; i ++ ){	//每轮添加一个障碍物,计算添加后的方案总数
        memset(st, 0, sizeof(st));	//清空状态数组
        for(int j=i-1;j>=0;j--){
            int d = a[i]-a[j];cnt=0;
            for(int k : q[d]){	//枚举d的所有因子,其实就是所有可能的方案
                if(!st[k]){	//如果此因子之前没使用过,方案数加一,并标记当前因子已被用过
                    cnt++;
                    st[k]=true;
                }
            }
            st[d]=true;	//手动添加d本身,因为下一轮如果按照这个间隔植树就会撞上本轮添加的障碍物
            			//因为最后一个障碍物必选。。
            f[i]=(f[i]+(LL)f[j] * cnt)%mod;	//更新方案总数
        }
    }
    printf("%d\n",f[n-1]);
    return 0;
}

什么?这还不够?你还想知道公式怎么打出来?
拿来吧你~


呐呐呐,表情也给你😘


参考文献

  • 23
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值