解题思路
巨老说这题可以倒着DP:
设 f [ i ] [ j ] f[i][j] f[i][j]表示 a a a串 i n i~n i n都匹配完成, b b b串用到了 j n j~n j n(有些可能不动,有些可能抽了出来)
我们考虑怎么转移:
-
f [ j ] [ k ] = f [ j + 1 ] [ k + 1 ] f[j][k]=f[j+1][k+1] f[j][k]=f[j+1][k+1],那么就是说当前的这个位置 a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j],可以不用动
上面的是不用抽出来的情况(不用贡献 + 1 +1 +1),所以要先更新状态,否则会有问题 -
f [ i ] [ j ] = f [ i + 1 ] [ j ] f[i][j]=f[i+1][j] f[i][j]=f[i+1][j]
那么就是说抽出来的可以把i填满,那么判断一下现在 b b b串 ( j + 1 n ) (j+1~n) (j+1 n)这个字符的数量是否比 a 串 ( i + 1 n ) a串(i+1~n) a串(i+1 n)串这个字符的数量大
以上两个是只更新状态,不用抽出来的情况(不用代价 + 1 +1 +1)
-
f
[
i
]
[
j
]
=
f
[
i
]
[
j
+
1
]
+
1
f[i][j]=f[i][j+1]+1
f[i][j]=f[i][j+1]+1 (只把j抽出来,代价
+
1
+1
+1,不匹配)
预处理: f [ n + 1 ] [ n + 1 ] = 0 , f [ n + 1 ] [ n ] = 1 , … … , f [ n + 1 ] [ 1 ] = n f[n+1][n+1]=0,f[n+1][n]=1,……,f[n+1][1]=n f[n+1][n+1]=0,f[n+1][n]=1,……,f[n+1][1]=n,就是一个个抽出来不匹配。
然后就是方案数要怎么求。我们用一个 g [ i ] [ j ] g[i][j] g[i][j]在更新 f [ i ] [ j ] f[i][j] f[i][j]的时候存储它的上一个状态的 i , j i,j i,j
我们知道上一个状态是 ( i + 1 , j ) (i+1,j) (i+1,j)的点,就是需要被后面抽出的数填上的,标记这些要被填的点。填数的时候模拟一遍,先把要抽出来的点放进一个桶里面,遇到要被填的点就从桶里拿出点填上。
最后得到了一个答案数组,要考虑之前操作会让一些点后移,所以要把这些点的坐标加1.
代码
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<cmath>
using namespace std;
int cnt,n,x,y,xx,yy,l,f[2100][2100],v[2100],q[30],sum[30];
char a[2100],b[2100],c[2100][30],d[2100][30];
struct c{
int x,y;
}g[2100][2100],ans[2100];
int main(){
freopen("chinese.in","r",stdin);
freopen("chinese.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
memset(d,0,sizeof(d));memset(c,0,sizeof(c));
memset(f,0x7f,sizeof(f));memset(q,0,sizeof(q));memset(sum,0,sizeof(sum));
scanf("%s",a+1);
scanf("%s",b+1);
l=strlen(a+1);
for(int k=l;k>0;k--)
{
for(int j=0;j<26;j++)
d[k][j]=d[k+1][j],c[k][j]=c[k+1][j];
d[k][a[k]-97]++,c[k][b[k]-97]++;//记录k~n中每个字符各有几个
}
for(int j=1;j<=l+1;j++)f[l+1][j]=l+1-j,g[l+1][j].x=l+1,g[l+1][j].y=j+1;
for(int j=l;j>0;j--)
{
for(int k=j;k>0;k--)
{
f[j][k]=f[j][k+1]+1,g[j][k].x=j,g[j][k].y=k+1;
if(d[j][a[j]-97]<=c[k][a[j]-97]&&f[j+1][k]<f[j][k])f[j][k]=f[j+1][k],g[j][k].x=j+1,g[j][k].y=k;
if(a[j]==b[k]&&f[j+1][k+1]<f[j][k])f[j][k]=f[j+1][k+1],g[j][k].x=j+1,g[j][k].y=k+1;
}
}
printf("%d\n",f[1][1]);
x=1,y=1;memset(v,0,sizeof(v));
while(f[x][y])
{
xx=g[x][y].x,yy=g[x][y].y;
if(xx==x+1 && yy==y)
v[x]=1;//标记哪里需要被后面抽出的数填上
x=xx,y=yy;
}
int j=l,k=l,cnt=0;
while(k)
{
if(a[k]==b[j])//把一样的字母跳过,不用处理
{
j--,k--;
continue;
}
if(!v[k])
{
f[b[j]-97][++sum[b[j]-97]]=j;//f被重新定义,f[i][j]表示第j个字符i的位置在哪,sum[i]记录字符i能被用的个数
j--;
}
else {//如果当前位置需要被填,就记下从哪里抽出来的即填入的位置,q[i]记录用了几个i字符
ans[++cnt].x=f[a[k]-97][++q[a[k]-97]];
ans[cnt].y=j+1;
k--;
}
}
for(int j=1;j<=cnt;j++)
for(int k=j+1;k<=cnt;k++)
if(ans[j].x>ans[k].x&&ans[j].y<ans[k].x)
ans[k].x++;//从ans[j].x抽出的字符放在ans[j].y前,则ans[j].x~ans[j].y间所有的字符都要往后移一位
for(int j=1;j<=cnt;j++)
printf("%d %d\n",ans[j].x,ans[j].y);
}
}