CSP202104-4 校门外的树

原题点我

DP好题

打眼一看,感觉是个dp,而我的dp很菜(其实不管什么都很菜),看到区间就想到区间dp,导致有一点思路,但是无法实现。实际上我们不关心中间的某一段的种树情况,只需要知道从第一个障碍物开始到最后一个障碍物结束的种树方案,因此只需要一维数组。
\(f_i\)表示从第一个障碍物到第\(i\)个障碍物的总方案数,那么很容易写出\(f_i = \sum_{j = 1}^{i - 1}{f_j * calc(i, j)}\),其中\(calc(i, j)\)表示第\(i\)个障碍物到第\(j\)个障碍物之间的方案数。假设\(f_1\)\(f_{i - 1}\)都已经求出来,那么求\(f_i\)就只需要求出\(calc(i, j)\)
现在我们设法写出\(calc\)函数。显然,对于中间没有障碍物的两个障碍物来说,他们之间的方案数就是两个障碍物之间距离的约数(不包括自己)的个数。举例: 设两个障碍物的坐标分别是\(a_i\)\(a_{i + 1}\),那么\(calc(i + 1, i) = sizeof(X)\),其中\(X\)表示\(a_{i + 1} - a_i\)的约数(不包括自己)所组成的集合。那么对于中间有障碍物的两个障碍物来说呢?因为在dp的过程中,在枚举左端点\(j\)时,右端点\(i\)是不动的,所以我们就以右端点为基准。显然,在集合X中的数都不能再作为现在的备选答案了,因为若选择这些数作为答案,则从右端点开始计算,必将在之前的左端点处种下一棵树,但实际上这是非法的操作。同样地,之前的两个障碍物之间的距离也不能作为备选答案。
具体来说,建立一个初始为空的集合\(Y\),从\(j = i - 1\)开始枚举倒序枚举\(j\),每次都找出\(a_i - a_j\)的约数所组成的集合\(Z_j\),对于\(Z_j\)中的每一个元素,检查它是否在集合\(Y\)中,若在集合\(Y\)中,则表明以这个元素作为间隔去种树会在某一个障碍物处种数,所以不能计入答案,若不在集合\(Y\)中,则可以计入答案,并将其插入集合\(Y\)中,处理完后,还要把\(a_i - a_j\)也加入集合中。

初始状态: \(f_1 = 1\) 最终状态: \(f_n = ?\)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#define ll long long
using namespace std;
const int maxn = 1e4 + 10;
const int maxm = 1e5 + 10;
const int m = 1e9 + 7;
int a[maxn];
vector<int> pr[maxm];
int n;
bool vis[maxm];

ll f[maxn];
ll calc(int p, int q);

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    
    for(int i = 1; i <= a[n]; i++)
        for(int j = 2 * i; j <= a[n]; j += i)
            pr[j].push_back(i);
    
    f[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        memset(vis, 0, sizeof(vis));
        for(int j = i - 1; j >= 1; j--)
            f[i] = f[j] % m * calc(i, j) % m + f[i] % m;
        
    }
    printf("%lld\n", f[n] % m);
    
}

ll calc(int p, int q)
{
    ll ret = 0;
    for(int t : pr[a[p] - a[q]])
        if(!vis[t])
        {
            ret = ret + 1 % m;
            vis[t] = true;
        }
    vis[a[p] - a[q]] = true;
    return ret % m;
}

总结

倒序枚举\(j\)是比较关键的操作,想到这一步的原因是两个相邻的障碍物之间的方案数比较容易计算,而之后扩展到不相邻的障碍物时,非法集合中的元素只增多不减少,比较容易维护。

posted @ 2021-10-06 17:10  Franky0705  阅读(101)  评论(0编辑  收藏  举报