P3847 [TJOI2007]调整队形 题解

博客园同步

原题链接

简要题意:

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

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

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

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

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

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

那么有:

\[ f_{i,j} = \begin {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 \not = 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\n",f[1][n]); //最终答案
	return 0;
}

posted @ 2020-03-31 20:00  bifanwen  阅读(104)  评论(0编辑  收藏  举报