String Distance( 比较难的dp )
题意:给两个字符串A和B( 只由小写字母组成 ),A的长度最大1e5,B的长度最大20 。q次询问(最多1e5次),每次询问给一个区间 [ l , r ] 1<=l,r<=lenA 问A[ l,l+1...r ] 和 B整个字符串 进行最少几次操作可以变成相同的,操作1:选择任意一个字符串在任意一个位置增加一个字符;操作2:选择任意一个字符串删除任意一个字符。
思路:显然操作1增加字符是没有用的,因为我增加字符就是为了让增加的字符和另外的一个字符相同,那我直接删掉另外的字符就可以了。这就转变成了求字符AB的最大公共子序列,把不是最大公共子序列的字符删掉就是答案,ans=lenA+lenB-2*LCS。
到这基本就能想到用dp来做了,dp的一个特点就是都是从小状态往大状态迁移,B字符串比较小,可能是dp的一个维度,考虑B字符串只有前1个,前两个,前三个,考虑有什么联系。我们是要求LCS的,想一下是把LCS放到左边维度里还是放到右边答案里,容易想到开个二维dp[ i ][ j ] 表示 B取前 i 个字符时和A的LCS是 j 时,需要的A的范围。
再考虑考虑A的边界条件,准确的表达,dp[ i ][ j ] 表示 B的前 i 字符,和A的lcs长度是 j ,在[l 条件下 的 r]
这里涉及到在[ l,左边界的条件下,还考虑到第一维度是B的长度,状态更新时涉及到单个字符。所以我们再引入g[ i ][ j ]表示 A[i..n] 里字符 j 最早出现的下标。
这样我们得到状态迁移方程是:dp[ i ][ j ] = min dp[ i ][ j ], dp[ i-1 ][ j ] , g[ dp[i-1][j-1 ][ B[ i ] ]
解释是:dp[ i-1 ][ j ] 是前i-1个字符lcs也是 j 那么加上当前的i 也还是满足lcs是 j
g[ dp[i-1][j-1 ][ B[ i ] ] 是前i-1个字符lcs是j-1 那么我当前的B[ i ] 一定要加入到lcs里才可以,就需要找在前 j-1个字符匹配成功的后面再匹配B[ i ] .
初始条件是:dp[i][0] = l-1.
写代码过程中一定要注意在状态迁移时有没有越界情况,我就因为g数组卡的边界,导致dp迁移时g比边界大了一些,wa了半天。
上面只是思路,写代码过程中还有好多细节需要注意。
看了题解,还是写了三个小时才A的我实在是太菜了.....
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[22][22]; /// B的前i字符,lcs长度是j ,在[l 条件下 的 r]
int g[200005][30];
string A,B;
int lenA,lenB;
void init()
{
for ( int i=0; i<=180002; i++ ) { // 在这全设置成最大值了,卡边界出错了
for ( int j=0; j<=28; j++ ) {
g[i][j] = lenA+1;
}
}
for ( int i=lenA-1; i>=0; i-- ) {
for ( int j='a'-'a'; j<='z'-'a'; j++ ) {
if ( A[i]-'a'==j ) g[i][j] = i;
else g[i][j]=g[i+1][j];
}
}
}
signed main()
{
// freopen("1012.in","r",stdin);
// freopen("1012.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T;cin>>T;
while ( T-- ) {
cin>>A>>B;
lenA = A.size();lenB = B.size();
init();
int m;cin>>m;
while ( m-- ) {
int l,r;cin>>l>>r;
for ( int i=0; i<=lenB; i++ ) {
for ( int j=0; j<=lenB; j++ ) {
dp[i][j] = r;
}
}
/
for ( int i=0; i<=lenB; i++ ) dp[i][0] = l-2;
for ( int i=1; i<=lenB; i++ ) {
for ( int j=1; j<=i; j++ ) {
dp[i][j] = min( dp[i][j],dp[i-1][j] );
dp[i][j] = min( dp[i][j],g[ dp[i-1][j-1]+1 ][ B[i-1]-'a' ] );
}
}
/
int isp = 0;
for ( int i=lenB; i>=0; i-- ) {
for ( int j=1; j<=lenB; j++ ) {
if ( dp[j][i]+1<=r ) {
isp = 1;
cout << r-l+1+lenB-2*i << endl;
break;
}
}
if ( isp==1 ) break;
}
}
}
return 0;
}
/*
1
rtedxofnuatdpogdzftepqhlwiavwisshpvlcwdkzlccofacdisafkpzmppgdwahposjlgqxqdupksokprgaymznbtyuenyikazrmjonfqawzcmuaqowrizdortxplnogmntadqqpwgolfcyqswtncqldgkrmgbovkyaxsuqwzftujheouhiynkjbzdnnhmreheoawkkljxmiwghewmqvjvmywukckjzwvfnjomthjkvijujdksawjmfrrgmhvpqazesdmeyjvougzmhlxrqmdyripgomcrawinxmfiyrxdhapolnjrveymwjfnhxrojwzbqyfijufsptrtoodwjdmticprauzscyorujojvwrrlpcvjwynvdcmdjwxruhpcandrefgtdvilsfhdnumdzqhcnzzqlevqhoeivtqvudorczqcvcvhlkfljcgi
bbbbabababbaabbababb
1
444 444
1
abcdwadefghiyuwehskwaspsduwxyz
wwac
5
1 6
2 9
7 12
4 19
12 23
6 8 10 16 10
*/
刚学了一晚上这个dp,今天打百度之星碰到了一道相似的dp,太爽了。
Problem Description
Alice 和 Bob 准备 solo 一场算法竞赛。
比赛一共有 n 个题,编号为 1,2....,n,对于第 i 道题,Alice 需要 a[i] 分钟写出一份正确的代码,Bob 需要 b[i] 分钟写出一份正确的代码。
比赛规则为
- 每道题第一个通过的人积 1 分,如果两人同时 AC 该题,只有 Alice 得分。
- 比赛时长为 10 ^ {18} 分钟。
Alice 和 Bob 的比赛策略都满足:决定要去做某道题后,会一直解决该题,直到自己或者对手 AC 此题,如果对手 AC 该题,则会立即放弃这题。
Bob 写完一份正确的代码后会立即提交,但 Alice 写完一份正确的代码,可以先暂时不交题,等之后再交(交题的时间忽略不计,任何时间都可以交题)。
另外 Alice 知道 Bob 是按 1,2,....,n 的顺序来依次做题,知道每道题自己需要的时间和 Bob 需要的时间(即 a 序列和 b 序列)。
输出 Alice 最优策略下最多得几分。
Alice 和 Bob 想题都不需要时间。
Input
第一行一个整数 t(1 <=t <= 10) 表示 t 组数据。
每组数据第一行一个整数 n (1 <= n <= 2000) 表示题数。
第二行 n 个整数,表示 a[1],a[2],...a[n](1 <= a[i] <= 1000000000) 。
第三行 n 个整数,表示 b[1],b[2],...b[n](1 <= b[i] <= 1000000000) 。
保证至多只有一组数据 n>100。
Output
对于每组数据,一行一个整数表示答案。
Sample Input
2 6 6 6 6 6 6 6 1 1 1 1 1 1 3 1 2 3 5 1 1
Sample Output
Copy
1 3 样例解释 Case 1 开场直接 rush 最后一题。 Case 2 [0,1) 写掉第一题,第 5 分钟交;[1,3) 写第二题第 6 分钟交,[3,6) 写第三题第 6 分钟交。
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
using namespace std;
int dp[2005][2005];
int a[2005],sumb[2005],n;
signed main()
{
// 前i个题中,得分为j,需要的时间
// dp[i][j] = dp[i-1][j]
// dp[i][j] = dp[i-1][j-1]+a[i] <= sumb[i]
int Ti;cin>>Ti;
for ( int ji=0; ji<Ti; ji++ ) {
cin>>n;
sumb[0] = 0;
for ( int i=1; i<=n; i++ ) scanf("%lld",&a[i]);
for ( int i=1; i<=n; i++ ) {
int x;scanf("%lld",&x);
sumb[i] = sumb[i-1]+x;
}
memset(dp,0x3f3f3f3f,sizeof(dp));
for ( int i=0; i<=n; i++ ) dp[i][0] = 0;
for ( int i=1; i<=n; i++ ) {
for ( int j=0; j<=n; j++ ) {
dp[i][j] = min( dp[i][j], dp[i-1][j] );
if ( dp[i-1][j-1]+a[i]<=sumb[i] ) {
dp[i][j] = min( dp[i][j], dp[i-1][j-1]+a[i] );
}
}
}
int isp = 0;
for ( int i=n; i>=0; i-- ) {
for ( int j=n; j>=1; j-- ) {
if ( dp[j][i]<=sumb[j] ) {
cout << i << endl;
isp = 1;
break;
}
}
if ( isp==1 ) break;
}
}
return 0;
}