洛谷4933:大师【动态规划,本菜写这道题的心路历程】

我突然发现如果一味地写对的方案,那么对其他人学习这道题其实没用多大的帮助,其更重要的是心路写题人的心路历程,只有体会了这个历程才能有所提高,那么我就来分享一下我的心路历程吧;

我先哪到这个题【因为是从动态规划的题单里找到的,所以思路就直接是动态规划了】,我想到两个维度的dp,因为答案状态我们可以划分为:前N个电塔中,分别由1~n个电塔构成的等差数列,然后往下想状态转移方程,我先找了一个比较好想到的划分集合的方式【本菜这里将每一个状态,即dp[i][j]抽象为一个集合,即前i个电塔拿出j个电塔构成等差数列的方案数】,我们以选不选最后一个电塔为集合划分的依据,然后我开始想状态的划分:

将f[i][j]分为不选最后一个电塔和选最后一个电塔:

(1)如果不选最后一个电塔,那么其方案数等价于f[i-1][j]即由前i-1个电塔构成的方案;

(2)如果选第i个电塔的话...以下是本菜的心理活动

【那么我可以确认最后一个电塔的高度,但公差是多少??没有描述公差的维度,那么我们是不可能知道公差的信息的,因为公差这个信息已经被抹杀了,斯~,不行这样分维度不行,那么居然用到的公差,那么我们给公差开一个维度吧,嘶~有点像一道题,就那啥来着???哦对,求最长递增子序列问题!那么看看能不能借鉴这个问题的分维方案吧....哦哦,设f[i][j]是以第i坐电塔为结尾,且公差为j的方案数?那么怎么状态转移呢??好弄!找到第k【k<i,k>=1】于第i个电塔之间的差d,然后看以第k个电塔为结尾且公差为d的方案数为多少,然后我们直接加起来就行了~,🆗问题解决了】

当然,说得轻巧,其实想了好久,而且这样分状态还有一个天大的坑!

#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int N=20010,M=1010;

ll f[M][2*N];//以i为结尾,且公差为j的方案数
int n;
int h[M];
ll ans;
int mx;
//公差转换 如果两个数的公差为z,那么对应的j为z+N

int main(){
    
    cin>>n;
    
    for(int i=1;i<=n;i++) cin>>h[i],mx=max(mx,h[i]);
    
    for(int i=1;i<=n;i++) f[i][N]=1,ans++;
    
    for(int i=1;i<=n;i++){
        
        for(int j=1;j<i;j++){
            
            int d=h[i]-h[j];

            if(f[j][d+N]) {//看有没有方案,如果有的话就加起来
                f[i][d+N]+=f[j][d+N]+(d!=0);//为什么加个d!=0,因为加的是等差数列开头不是第j个电塔的等差数列,所以我们还得加上一个由第j和第i电塔组成的等差数列的方案数
                f[i][d+N]%=998244353;
                ans+=f[j][d+N]+(d!=0);
                ans%=998244353;
            }
            else {//如果没有的话,那么j、i两个电塔构成一个方案
                f[i][d+N]++;
                f[i][d+N]%=998244353;
                ans++; ans%=998244353;
            }
        }
        
    }
    
    cout<<ans;
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值