P3847 [TJOI2007]调整队形 题解

博客园同步

原题链接

简要题意:

每次可以在数组中插入一个数(可以在两端)或删除一个数,改变一个数;求让数组成为回文数组的最小步数。

这题的蓝太假了,水 ( ext{dp}),想到算法就可以做出。

关键还是要想到用 动态规划 啊,要是想不到这题就做不出来了。

(考场上可以先用搜索,然后记忆化推导也行,但是这里就不推了)

显然考虑区间 ( ext{dp}).

(f_{i,j}) 表示将 (a_i)(a_j) 变成回文的最小步数。

那么有:

[ f_{i,j} = egin {cases} f_{i+1,j-1} , s_i = s_j \ min(f_{i+1,j} , f_{i,j-1} , f_{i+1,j-1}) , s_i ot = s_j \ end{cases} ]

下面一一解释。

如果 (s_i = s_j),则说明已经回文了一位,直接考虑砍掉它们的答案。

(因为回文串两边添上或去掉一个相同的字符,仍然具有回文性)

否则,(f_{i+1,j}) 是去掉第 (i) 位的方案,(f_{i,j-1}) 是去掉第 (j) 位的方案。

(f_{i+1,j-1}) 是改变 第 (i) 位或第 (j) 位使得 (s_i = s_j),然后砍掉它们的答案。

均需要一步,所以最后 (+1).

你会问:添加操作没有用?

你想,假设你在某个位置添加一个数,等价于在其对应的位置去掉一个数。

这仍然是因为上述的回文性质。在对应的位置删除或添加一个相同的数,不影响回文性质。

时间复杂度:(O(n^2)).

实际得分:(100pts).

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int N=3e3+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,a[N];
int f[N][N];

int main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int len=1;len<=n;len++) //区间 dp 的套路,为了满足无后效性,需要按照区间大小进行枚举,而不是按照左右端点
	for(int i=1;i<=n-len+1;i++) {
		int j=i+len-1;
	// 如果 len=1 则 f[i][j]=f[i+1][j-1]=0 不影响答案,不必特判
		if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
		else f[i][j]=min(f[i+1][j],min(f[i][j-1],f[i+1][j-1]))+1;
	} printf("%d
",f[1][n]); //最终答案
	return 0;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12607880.html