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;
}