UVA1424 Salesmen 题解

这道题在蓝书上的习题上。

这道题明显是一道 dp 题。

翻译

给定一个长度为 \(n_1\) ( \(n_1 \leq 100\) ) 的点的无向连通图和一个长度为 \(n\) 的序列 \(A\) (\(n \leq 200\) ) ,你的任务是修改尽量少的数,使得序列的任意相邻两个数在图上是是相邻节点,或者相同。

思路

首先我们定义状态 \(dp(i,j)\) 表示从第 1 到第 i 个位置 ,数字 j 为结尾的最小修改次数。

对于状态 \(dp(i,j)\) 来说,那么它只可以从 \(dp(i-1,k)\) 来,那么转移的条件是什么呢?我们可以从题干中了解到 如果 \(j\)\(k\) 连通,那么就可以转移。

当然不要忘了,如果 \(j\) 是原本序列里的数,那么就直接转移,不用加 1。

那么,综上所述,状态转移方程为:

\(dp(i,j) = \min(dp(i,j),dp(i-1,k)+1) (j,k)\)

\(dp(i,j) = \min(dp(i,j),dp(i-1,k)) (j=A(i))\)

注 : \((j,k)\) 表示 \(j\)\(k\) 连通。

代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

const int N = 110;
int f[N][N];
int a[220];
int t;
int dp[N*2][N*2];

void init(){
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<N;i++)
		for(int j=1;j<N;j++)
			f[i][j] = 0;
	for(int i=0;i<N;i++)
		f[i][i] = 1;
}

int main()
{
	cin>>t;
	while(t--){
		init();
		int n,n2,m;
		scanf("%d %d",&n,&n2);
		for(int i=1;i<=n2;i++){
			int x,y;
			scanf("%d %d",&x,&y);
			f[x][y] = f[y][x] = 1;
		}
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
			scanf("%d",&a[i]);
			
		for(int i=1;i<=m;i++)
			dp[1][i] = 1;
		dp[1][a[1]] = 0;
		
		for(int i=2;i<=m;i++){
			for(int j=1;j<=n;j++){
				dp[i][j] = 0x3f3f3f3f;
				 for(int k=1;k<=n;k++){
					if(f[j][k] != 0){		// 转移
						if(j == a[i])
							dp[i][j] = min(dp[i][j],dp[i-1][k]);
						else
							dp[i][j] = min(dp[i][j],dp[i-1][k]+1);
					}
				 }
			}
		}
		
		int ans = 0x3f3f3f;
		for(int i=1;i<=n;i++)
			ans = min(ans,dp[m][i]);
		printf("%d\n",ans);
	}
	return 0;
}

posted @ 2021-08-22 12:27  在那遥远的悠穹下  阅读(54)  评论(0编辑  收藏  举报