ABC-Warp-(dp状态的选择+记忆化搜索)

E

题意:
就是在一个平面图上,刚开始在(0,0)点,然后给你6个数a,b,c,d,e,f。每次可以走到(x+a,y+b),(x+c,y+d),(x+e,y+f)。然后给你m个障碍物的下标。现在问你走n次,一共有多少不同的路径。

思考:

  1. 看到n的范围不大,就感觉可以直接dp呀,想到这种二维下标类型的我就想记忆化搜索,因为对于二维下表的点用记忆化更好做。就写了个记忆化,定义dp[x][y][sum]为走到(x,y)点还有sum步可以走的方案数,但是测试一下发现TLE了。然后我就有点不解,按常理说是可以的。然后就怀疑自己时间复杂度算错了?
  2. 看了眼别人的发现,完全可以定义dp[i][j][k]为一共走了i步,走了i步第一种,走了j步第二种的方案数。这样一样可以把现在的点算出来,所以就不去维护点的值了。然后c的方案树就i-a-b就可以了。然后我感觉这和我的记忆化搜索复杂度一样的啊。
  3. 想了会发现,因为我记忆化搜索的时候是对点的下标来记忆的,由于点的下标太大我用了pair和map,由于一共有n3种状态,所以mp和pair用的比较多就超时了。所以我就根据上面的做法,想到完全可以定义dp[i][j][k]为走了i次第一种,j次第二种,k次第三种,这样就直接跑dfs就行了。
  4. 主要还是只要枚举用了几次谁就可以找到当前的下标来判断是否为障碍物的点,这样就可以dp去转移了。这种题和以前做的传递纸条什么的都差不多。

代码:

直接dp
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 998244353,inf = 1e18;
const int N = 2e5+10,M = 310;

int T,n,m,k;
int va[N];
int dp[M][M][M];

vector<PII > v;
map<PII,int > vis;

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=3;i++)
	{
		int a,b;
		cin>>a>>b;
		v.pb({a,b});
	}
	while(m--)
	{
		int a,b;
		cin>>a>>b;
		vis[{a,b}] = 1;
	}
	dp[0][0][0] = 1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			for(int k=0;k+j<=i;k++)
			{
				int x = j*v[0].fi+k*v[1].fi+(i-j-k)*v[2].fi;
				int y = j*v[0].se+k*v[1].se+(i-j-k)*v[2].se;
				for(int cs=0;cs<3;cs++)
				{
					int xx = x+v[cs].fi,yy = y+v[cs].se;
					if(!vis.count({xx,yy}))
					dp[i+1][j+(cs==0)][k+(cs==1)] = (dp[i+1][j+(cs==0)][k+(cs==1)]+dp[i][j][k])%mod;
				}
			}
		}
	}
	int ans = 0;
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j+i<=n;j++)
		ans = (ans+dp[n][i][j])%mod;
	}
	ans = (ans%mod+mod)%mod;
	cout<<ans;
	return 0;
}
记忆化搜索:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 998244353,inf = 1e18;
const int N = 2e5+10,M = 310;

int T,n,m,k;
PII va[N];
int dp[M][M][M];

map<PII,int> vis;

int dfs(int a,int b,int c)
{
	if(a+b+c==n) return 1;
	if(dp[a][b][c]!=-1) return dp[a][b][c];
	int sum = 0;
	for(int i=1;i<=3;i++)
	{
		int x = a*va[1].fi+b*va[2].fi+c*va[3].fi+va[i].fi;
		int y = a*va[1].se+b*va[2].se+c*va[3].se+va[i].se;
		if(!vis.count({x,y})) sum = (sum+dfs(a+(i==1),b+(i==2),c+(i==3)))%mod;
	}
	dp[a][b][c] = sum;
	return sum;
}

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=3;i++) cin>>va[i].fi>>va[i].se;
	while(m--)
	{
		int x,y;
		cin>>x>>y;
		vis[{x,y}] = 1;
	}
	mem(dp,-1);
	cout<<dfs(0,0,0);
	return 0;
}

总结:
多多思考,多换种状态,当想的差不多的时候,如果出问题就试着去转化转化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值