牛客多校-Link with Level Edito I-(线性dp)

本文探讨了一个图论和动态规划相结合的算法问题,涉及连续世界中的有向边和点转移。题目要求从1号点通过最少的世界选择走到m号点。初始错误理解为非连续选择,后修正为必须连续选择。解决方案包括滚动数组优化的动态规划,状态定义为到达某个点的最大1的下标,并更新过程中找到最短路径。最终得出到达m号点的最小世界数,若无法到达则输出-1。
摘要由CSDN通过智能技术生成

L

题意:
就是给你n个世界,每一个世界都有m个点,然后每个世界有一些有向边。当你在这个世界的时候,你要么停在这个点,要么走到可以走到的点。然后下一秒你会被传送到下一个世界,你所在点的ID不会变。现在让你选择一段连续的世界,然后可以从1号点走到m号点。问你最少选几个世界。如果没有答案输出-1。

思考:
1.刚开始我以为题意是,按顺序选择一些世界就行,不用连续。如果是这样的话,可以定义dp[i][j]为,用到了第i个世界,到j点的最小花费。这里要理清的是:不是在第i个世界中,到j点。而是在这前i个世界中到j点的最少选择几个世界。所以更新的时候,直接先从上一层更新最小值。然后再更新到达这个第i个世界右端点的最小花费。注意点在代码中注释了。
2.正确题意是连续的,那么就不能这样取最小花费了。如果在每个i点进行二分这样复杂度n×n×log(n)。那么就想dp。可以定义dp[i]为,所有能从1走到i中的下标最大的1的下标。然后更新的时候先更新maxn数组,看看这个世界的路径中可以到达哪些点。然后再更新一下dp,如果此时m点可达了,那么花费就是i-dp[m]+1。就是用到第i个世界的时候才可达,那么就看看最近的可以走过来的1是哪里。

代码:

可以不连续的代码:
#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 = 1e9+7,inf = 1e18;
const int N = 1e4+10,M = 2e3+10;

int T,n,m,k;
int va[N];
int dp[N][M]; //到第i个世界,到j点的最小花费
int minn[N];

vector<PII > v[N];

int solve()
{
	mem(dp,0x3f);
	for(int i=1;i<=n;i++)
	{
		dp[i][1] = min(dp[i][1],1ll); //到1的花费就是1
		for(int j=1;j<=m;j++) dp[i][j] = min(dp[i][j],dp[i-1][j]); //到达j点的最小花费(不一定就是在这个世界中到达)
		for(auto t:v[i])
		{
			if(t.fi==1) dp[i][t.fi] = dp[i][t.se] = 1; //特别注意的地方
			else dp[i][t.se] = min(dp[i][t.se],dp[i-1][t.fi]+1); //如果在第i个世界到达t.se点,那么最小花费是多少。
		}
	}
	if(dp[n][m]>1e9) return -1;
	else return dp[n][m];
}

/*
由于每次只用上一层的,可以用滚动数组优化空间。
int solve()
{
	mem(dp,0x3f);
	for(int i=1;i<=n;i++)
	{
		dp[i&1][1] = min(dp[i&1][1],1ll);
		for(int j=1;j<=m;j++) dp[i&1][j] = min(dp[i&1][j],dp[(i-1)&1][j]);
		for(auto t:v[i])
		{
			if(t.fi==1) dp[i&1][t.fi] = dp[i&1][t.se] = 1;
			else dp[i&1][t.se] = min(dp[i&1][t.se],dp[(i-1)&1][t.fi]+1);
		}
	}
	if(dp[n][m]>1e9) return -1;
	else return dp[n][m];
}
*/

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) dp[i][j] = inf;
	for(int i=0;i<=m;i++) minn[i] = inf;
	for(int i=1;i<=n;i++)
	{
		cin>>k;
		while(k--)
		{
			int a,b;
			cin>>a>>b;
			v[i].pb({a,b});
		}
	}
	cout<<solve()<<"\n";
	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 = 1e9+7,inf = 1e18;
const int N = 1e4+10,M = 2e3+10;

int T,n,m,k;
int va[N];
int dp[M]; //能走到i的最大的1的下标。
int maxn[M];

vector<PII > v[N];

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>k;
		while(k--)
		{
			int a,b;
			cin>>a>>b;
			v[i].pb({a,b});
		}
	}
	int minn = inf;
	for(int i=1;i<=n;i++)
	{
		dp[1] = i; //每个世界都可以走到1
		for(auto t:v[i]) maxn[t.se] = max(maxn[t.se],dp[t.fi]); //用上一层的dp更新maxn
		for(int j=1;j<=m;j++) dp[j] = max(dp[j],maxn[j]); //再更新dp
		if(dp[m]) minn = min(minn,i-dp[m]+1); //如果到第i个世界,可以走到m了,答案就是最近的1走到i的花费
	}
	if(minn==inf) cout<<-1;
	else cout<<minn;
	return 0;
}

总结:
多多思考,理清dp的状态定义。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值