(纪中)5102. 连词成句【DP】

66 篇文章 1 订阅

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


解题思路

巨老说这题可以倒着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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值