题目
给一个打字机,有三种操作:
很容易想到DP,在观察一下这个状态是 n 2 n^2 n2的:
f [ i ] [ j ] : 表 示 已 经 输 入 了 i , 当 前 剪 贴 板 是 当 前 串 长 度 为 j 的 后 缀 , j = 0 表 示 剪 贴 板 为 空 f[i][j] : 表示已经输入了i,当前剪贴板是当前串长度为j的后缀,j=0表示剪贴板为空 f[i][j]:表示已经输入了i,当前剪贴板是当前串长度为j的后缀,j=0表示剪贴板为空。通过转移来说明为什么只需要记录剪贴板为后缀的情况:
然后是求: p r e [ i ] [ j ] : 表 示 在 i 结 尾 长 度 为 j 的 串 的 上 一 个 出 现 结 尾 位 置 pre[i][j]: 表示在i结尾长度为j的串的上一个出现结尾位置 pre[i][j]:表示在i结尾长度为j的串的上一个出现结尾位置。 这个用lcs来求最方便(不用hash,因为是对所有串进行处理)。利用更长串的信息,直接倒着扫一遍即可求出,详见代码。
#include
using namespace std;
#define PB push_back
#define lowbit(x) (x&(-x))
#define MP make_pair
#define fi first
#define se second
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
#define rep(i,l,r) for (int i = l ; i <= r ; i++)
#define down(i,r,l) for (int i = r ; i >= l ; i--)
#define fore(i,x) for (int i = head[x] ; i ; i = e[i].next)
#define SZ(v) (int)v.size()
typedef long long ll;
typedef pair <int,int> pr;
const int maxn = 1e6 + 10;
const ll inf = 1e18;
void up (ll &x,ll y){
if ( x > y ) x = y; }
ll solve(){
int n; ll X,Y,Z;
cin>>n>>X>>Y>>Z;
vector <vector <int>> lcs(n,vector <int> (n)) , pre(n,vector <int> (n,-1));
vector <int> s(n);
rep(i,0,n - 1) cin>>s[i];
rep(i,0,n - 1) rep(j,0,n - 1){
if ( s[i] == s[j] ) lcs[i][j] = (i > 0 && j > 0) ? (lcs[i - 1][j - 1] + 1) : 1;
else lcs[i][j] = 0;
}
rep(i,0,n - 1) rep(j,0,i - 1) pre[i][min(i - j,lcs[i][j])] = j;
rep(i,0,n - 1) down(j,i - 1,0) pre[i][j] = max(pre[i][j],pre[i][j + 1]);
vector <vector <ll>> f(n,vector <ll> (n,inf));
f[0][0] = X;
rep(i,1,n - 1){
f[i][0] = f[i - 1][0] + X;
rep(j,1,i){
if ( pre[i][j] == -1 ) continue;
up(f[i][j],f[pre[i][j]][j] + (i - pre[i][j] - j) * X + Z);
up(f[i][j],f[i - j][0] + Y + Z);
up(f[i][0],f[i][j]);
}
}
return f[n - 1][0];
}
int main(){
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int cases;
cin>>cases;
rep(t,1,cases){
cout<<"Case #"<<t<<": "<<solve()<<"\n";
}
}
也是复习,第二次做这个题目。还是觉得比较经典。这次还是以为要用hash来求pre,忘记了最简单的方法!