牛牛的回文串(预处理 + 区间DP)

题目链接

题意:

牛牛喜欢回文串,牛妹给了牛牛一个字符串S,牛牛想把S变成回文串
牛牛可以做如下三种操作
1:在任意位置增加一个字符
2:删除一个字符
3:改变一个字符

每种操作都有限定的字符,比如,只能删除’a’,增加’b’,把’c’变成’d’等等
每种操作都有相应的代价
用M条语句来描述能进行的操作
add c x 表示增加c字符需要x的代价
erase c x表示删除c字符需要x的代价
change c1 c2 x表示将c1 改成c2需要x的代价
求牛牛想要得到回文串需要的最少代价
如果不行输出-1
串的长度<=50

题解:

dp(i,j)表示把[i, j]这一区间变成回文串的最小代价,那么dp(i, j)可以由dp(i+1, j), dp(i, j-1), dp(i+1, j-1)这三种状态转移而来。
但是因为它给出的操作可以出现很多变换,直接进行它所给的操作有可能不会得到最优解:比如change(a,c) = 10,add( a ) = 1,add( c ) = 100,那么直接add( c )的代价要比先add( a )再change(a,c)要大,而change之间也类似。所以我们用类似最短路的算法预处理出所有类型操作的最小费用(代价为inf = 不能进行该操作)。
预处理完之后,对于区间(i, j),它可能的转移:

  1. 擦除其中一个端点的代价 + 剩余部分为回文的代价
  2. 擦除两个端点的代价 + 剩余部分为回文的代价
  3. 在其中一边添加字符v,并让另一边的端点变为字符v 的代价 + 去除两端点剩余部分为回文的代价(枚举字符v)
  4. 把两个端点都变成字符v的代价 + 剩余部分为回文的代价。

AC代码:

#include
#define ll long long
using namespace std;
const int maxn = 55;
const ll inf = 0x3f3f3f3f;
ll dp[maxn][maxn];
char s[maxn];
int a[maxn];
ll add[maxn],era[maxn],to[maxn][maxn];
void up_add(){//spfa思想更新add操作 
	int inq[30];
	queue q;
	for(int i = 0; i < 26; ++i){
		q.push(i);inq[i] = 1;
	}
	while(q.size()){
		int u = q.front();q.pop();inq[u] = 0;
		for(int i = 0;i < 26;++i){
			if(add[i] > add[u] + to[u][i]){
				add[i] = add[u] + to[u][i];
				if(!inq[i]) q.push(i), inq[i] = 1;
			}
		}
	}
}
void up_era(){//spfa思想更新era操作 
	int inq[30];
	queue q;
	for(int i = 0; i < 26; ++i){
		q.push(i);inq[i] = 1;
	}
	while(q.size()){
		int u = q.front();q.pop();inq[u] = 0;
		for(int i = 0;i < 26;++i){
			if(era[i] > to[i][u] + era[u]){
				era[i] = to[i][u] + era[u];
				if(!inq[i]) q.push(i), inq[i] = 1;
			}
		}
	}
}
ll cul(int l,int r){
	if(l > r) return 0;
	return dp[l][r];
} 
int main(){
//	memset(dp,0x3f,sizeof dp);
//	memset(add,0x3f,sizeof add);
//	memset(era,0x3f,sizeof era);
//	memset(to,0x3f,sizeof to);
	for(int i = 0; i < maxn; ++i){//因为后面可能会出现3个inf相加,
									//所以inf定小一点防止爆ll 
		add[i] = era[i] = inf;
		for(int j = 0; j < maxn; ++j){
			dp[i][j] = to[i][j] = inf;
		}
	}
	scanf("%s",s+1);
	int len = strlen(s+1);
	for(int i = 1; i <= len; ++i) a[i] = s[i] - 'a';
	for(int i = 1; i <= len; ++i) dp[i][i] = 0;
	int n; scanf("%d",&n);
	while(n--){
		char op[10],c[2];
		int x;
		scanf("%s",op);
		if(op[0] == 'a'){
			scanf("%s%d",c,&x);
			add[c[0]-'a'] = x;
		}
		else if(op[0] == 'e'){
			scanf("%s%d",c,&x);
			era[c[0]-'a'] = x;
		}
		else{
			char c2[2];
			scanf("%s%s%d",c,c2,&x);
			to[c[0]-'a'][c2[0]-'a'] = x;
		}
	}
	for(int k = 0; k < 26; ++k) {//Floyd思想更新to操作 
		for(int i = 0; i < 26; ++i){
			for(int j = 0; j < 26; ++j){
				if(i == j) to[i][j] = 0;
				to[i][j] = min(to[i][j],to[i][k] + to[k][j]);
			}
		}
	}
	up_add(); up_era();
	for(int d = 1; d < len; ++d){
		for(int r = d+1; r <= len; ++r){
			int l = r - d;
			//erase
			dp[l][r] = min(cul(l+1, r) + era[a[l]], dp[l][r]);
			dp[l][r] = min(cul(l, r-1) + era[a[r]], dp[l][r]);
			dp[l][r] = min(cul(l+1, r-1) + era[a[l]] + era[a[r]], dp[l][r]);
			//add && change 
			for(int v = 0; v < 26; ++v){
				dp[l][r] = min(dp[l][r], cul(l, r-1) + add[v] + to[a[r]][v]);
				dp[l][r] = min(dp[l][r], cul(l+1, r) + add[v] + to[a[l]][v]);
				dp[l][r] = min(dp[l][r], cul(l+1, r-1) + to[a[r]][v] + to[a[l]][v]);
			}
			//cout<<"l:"<

你可能感兴趣的:(dp)