Cheapest Palindrome
题目
可以进行加入一个字符或删除一个字符的操作,它们都有对应代价,求这个最小代价
思路
- 明确对任意字符的插入或删除是等价的(在该位置删除 <==> 在对称位置加入)因此每个字符的花费就是取两操作花费的min。
- 定义dp[i][j],表示[ i , j ] 上的子串是回文的可能集合,记录它的花费W
- 寻找状态转移,可分两类
1. 两端点字符相同,显然从 dp[i+1][j-1] 继承过来就行 , 增加花费为0。
2. 两端点字符不同,则可以操作左右任意一边,选择代价最小的操作
AC代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e3, mod = 1e9 + 7;
int n,m;
string s;
int v[30];
ll dp[N][N];
void solve()
{
cin>>n>>m>>s;
for(int i=0;i<n;i++)
{
int a,b;
char c;
cin>>c>>a>>b;
v[c-'a'] = min(a,b);
}
for(int j=1;j<m;j++)// 枚举右端点向右扩散
for(int i=j-1;i>=0;i--) // 枚举左端点向左扩散
if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1];
else dp[i][j] = min(dp[i+1][j] + v[s[i]-'a'],dp[i][j-1] + v[s[j]-'a']);
cout << dp[0][m-1] << endl;
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
solve();
return 0;
}
枚举区间更新状态转移的顺序和平常有一些不同,原因如下
对于正常的枚举:
for(int i=0;i<n-1;i++)
for(int j=i+1;j<n;j++)
printf("[%d,%d], ",i,j);
区间枚举顺序如下(取n = 10)
[0,1], [0,2], [0,3], [0,4], [0,5], [0,6], [0,7], [0,8], [0,9],
[1,2], [1,3], [1,4], [1,5], [1,6], [1,7], [1,8], [1,9],
[2,3], [2,4], [2,5], [2,6], [2,7], [2,8], [2,9],
[3,4], [3,5], [3,6], [3,7], [3,8], [3,9],
[4,5], [4,6], [4,7], [4,8], [4,9],
[5,6], [5,7], [5,8], [5,9],
[6,7], [6,8], [6,9],
[7,8], [7,9],
[8,9],
根据状态转移方程, 以区间[0,8]为例
它可能从 [1,7],[1,8],[0,7] 中转移过来 ,显然此时 [1,8] 还未更新 。
因此采用AC代码中的循环顺序。
区间枚举顺序如下(n=10)
[0,1],
[1,2], [0,2],
[2,3], [1,3], [0,3],
[3,4], [2,4], [1,4], [0,4],
[4,5], [3,5], [2,5], [1,5], [0,5],
[5,6], [4,6], [3,6], [2,6], [1,6], [0,6],
[6,7], [5,7], [4,7], [3,7], [2,7], [1,7], [0,7],
[7,8], [6,8], [5,8], [4,8], [3,8], [2,8], [1,8], [0,8],
[8,9], [7,9], [6,9], [5,9], [4,9], [3,9], [2,9], [1,9], [0,9],