线性DP及优化

画中漂流

蒻蒟的version 1:
```plaintext
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int D=3000,T=3000,M=1500,MOD=1e9+7;
LL d,t,m,ans;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>d>>t>>m;
	vector<vector<vector<LL> > > dp(t+5,vector<vector<LL> > (d+5,vector<LL> (m+5,0)));
	dp[t][d][m]=1;
	for(int i=t-1;i>=0;i--)
	{//每秒 
		for(int j=0;j<=d+m;j++)
		{//距下游距离 
			for(int k=0;k<=m;k++)
			{//剩余体力
				if(j==0||j==1)
				{
					dp[i][j][k]=(dp[i][j][k]+dp[i+1][j+1][k])%MOD;
					continue;
				}
				dp[i][j][k]=(dp[i][j][k]+dp[i+1][j-1][k+1]+dp[i+1][j+1][k])%MOD;
			}
		}
	}
	for(int j=0;j<=d+m;j++)
	{
		ans=(ans+dp[0][j][0])%MOD;
	}
	cout<<ans%MOD;
	return 0;
}

结果是一片 RE + MLE

发现是 距离 j 开少了 ,有可能船划倒 d + m 处

修改后
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int D=3000,T=3000,M=1500,MOD=1e9+7;
LL d,t,m,ans;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>d>>t>>m;
	vector<vector<vector<LL> > > dp(t+5,vector<vector<LL> > (d+m+5,vector<LL> (m+5,0)));
	dp[t][d][m]=1;
	for(int i=t-1;i>=0;i--)
	{//每秒 
		for(int j=0;j<=d+m;j++)
		{//距下游距离 
			for(int k=0;k<=m;k++)
			{//剩余体力
				if(j==0||j==1)
				{
					dp[i][j][k]=(dp[i][j][k]+dp[i+1][j+1][k])%MOD;
					continue;
				}
				dp[i][j][k]=(dp[i][j][k]+dp[i+1][j-1][k+1]+dp[i+1][j+1][k])%MOD;
			}
		}
	}
	for(int j=0;j<=d+m;j++)
	{
		ans=(ans+dp[0][j][0])%MOD;
	}
	cout<<ans%MOD;
	return 0;
}

结果还是一片 MLE

存储的太多了

发现每次都是从上一秒继承的 ,可以像背包DP优化那样开两行的第一维

但是不能像背包那样完全变成一行来继承 , 因为 j 同时依赖于 j - 1 和 j + 1 不管从前往后还是从后往前都会导致信息统计混乱

#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int D=3000,T=3000,M=1500,MOD=1e9+7;
LL d,t,m,ans;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>d>>t>>m;
	vector<vector<LL> > prev(d+m+5, vector<LL>(m+5, 0));
	vector<vector<LL> > curr(d+m+5, vector<LL>(m+5, 0));
	prev[d][m] = 1;
	
	for(int i=t-1;i>=0;i--)
	{
		for(int j=0;j<=d+m;j++)
		{
			for(int k=0;k<=m;k++)
			{
				if(j==0||j==1)
				{
					curr[j][k] = prev[j+1][k];
					continue;
				}
				curr[j][k] = (prev[j-1][k+1] + prev[j+1][k]) % MOD;
			}
		}
		swap(prev, curr);//swap小妙用,直接置换两个向量,一直把pre当成当前最优,cur计算下一个当前最优
	}
	
	for(int j=0;j<=d+m;j++)
	{
		ans = (ans + prev[j][0]) % MOD;
	}
	cout<<ans%MOD;
	return 0;
}

MLE 没有了 ,但都变成 TLE 了

三维还是太慢了

得优化成二维

但是怎么去掉呢 ?

t是枚举每秒的,d是枚举距离的,k是枚举消耗的体力的

哪个能消掉?

答案求的是最后体力为零且没进峡谷的的状态

而我们求答案有奖所有的 d 累加起来

说明 d 可以进行压缩,因为我们展开来求最后也要将它求和

而且答案涉及的控制的变量里没有他

那怎么优化?

因为 消耗体力 就前进, 不消耗就后退 ,所以 d 其实可以由 t 和 m 唯一确定

DP要做的是记忆化搜索所有变量的可能性,递推得得到答案

而 d 这个变量可以省去 ,因为可以被 其他变量 唯一确定

我们要做的就是确定当前的 d 不为 0

#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int D=3000,T=3000,M=1500,MOD=1e9+7;
LL d,t,m,ans,dp[3010][3010],len;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>d>>t>>m;
	dp[0][m]=1;
	for(int i=1;i<=t;i++)
	{
		for(int j=0;j<=m;j++)
		{
			len=d+(m-j)-(i-(m-j));
			if(len>0)
			{
				dp[i][j]=(dp[i-1][j]+dp[i-1][j+1])%MOD;
			}
		}
	}
	
	cout<<dp[t][0]%MOD;
	return 0;
}



这就是5p

做的时候想的是n^3找出所有组合再算所有是m的倍数的组合的概率,但是显然会超时。

为m的倍数,即mod m 等于0
所以只要考虑和 mod m 下的意义,
dp 每个数下 所有可能的 和 的概率

#include <bits/stdc++.h>
typedef long long int LL;
using namespace std;
const int N=1e5+10,M=1010,MOD=998244853;
LL n,m,p[N],a[N],dp[N][M];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>p[i];

	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			LL temp = (j - a[i] % m + m) % m;
			dp[i][j] = (dp[i-1][j] * (1 - p[i] + MOD) % MOD + p[i] * dp[i-1][temp] % MOD) % MOD;
		}
	}
	cout<<dp[n][0];
	return 0;
}

但这样会MLE

256MB 1e8就超了,8k8k刚好不溢出
512MB 1e5
1e5 的int刚好不超 300300300 也刚好

做蓝桥时可不会知道交上去怎么样子
最好还是优化一下

#include <bits/stdc++.h>
typedef long long int LL;
using namespace std;
const int N=1e5+10,M=1010,MOD=998244853;
LL n,m,p[N],a[N],pre[M],cur[M];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>p[i];

	pre[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			LL temp = (j - a[i] % m + m) % m;
			cur[j] = (pre[j] * (1 - p[i] + MOD) % MOD + p[i] * pre[temp] % MOD) % MOD;
		}
		swap(pre,cur);
	}
	cout<<pre[0];
	return 0;
}



刷油漆

设 a [ i ] 为走满前 2×n 个格子的方案数

从一个顶点出发走完整片格子:
只有三种走法
1.走完同列另一个后走下一列,a[ i ]=a[ i - 1 ] × 2 ;
2.走完下一列一个格子后,回来,再走下一列另一个格子,a[ i ]=a[ i - 2 ] × 2;
3.不断往前走到头,再回来,回来时路径唯一,因为每次都是没走满的,新开一个 b[ i ],记录这个状态
b[ i ]=b[ i - 1 ]×2;

4 × a[n]就是以顶点为开始的方案数,当然还有中间开始的

中间开始的就是以 i 为起点 ,
1.i 往左/往右 进行上方第3种方式回到初始位置的另一行
2.i 往右/往左 进行上方整个方式,即以一个顶点开始刷满剩下的格子

所以再 ×4 累加上去

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1010,MOD=1e9+7;
LL n,a[N],b[N],ans;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	if(n==1)
	{
		cout<<2;
		return 0;
	}
	a[1]=1,a[2]=6;
	b[1]=1,b[2]=2;
	for(int i=3;i<=n;i++)
	{
		b[i]=b[i-1]*2%MOD;
		a[i]=(b[i]+2*a[i-1]+4*a[i-2])%MOD;
	}
	ans+=4*a[n];
	for(int i=2;i<n;i++)
	{
		ans=(ans+4*(a[i-1]*b[n-i+1]+a[n-i]*b[i]))%MOD;
	}
	cout<<ans%MOD;
	return 0;
}



子2023

2023由2、20、202、2023组成
遍历
每次根据当前数字增加对应的可能数量

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,m,ans,dp[N];
string s;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	s="";
	for(int i=1;i<=2023;i++)
	{
		s+=to_string(i);
	}
	for(int i=0;i<s.size();i++)
	{
        //0代表2,1代表20,2代表202,3代表2023
		if(s[i]=='2') dp[0]++,dp[2]+=dp[1];
		if(s[i]=='0') dp[1]+=dp[0];
		if(s[i]=='3') dp[3]+=dp[2];
	}
	cout<<dp[3];
	return 0;
}
posted @ 2025-03-26 20:58  石磨豆浆  阅读(41)  评论(0)    收藏  举报