zoukankan      html  css  js  c++  java
  • 【搜索剪枝】【dp】POJ1934

    题面

    给你两个由'a'到'z'组成的字符串,长度均不超过80,按字典序输出它们的所有的最长公共子序列的方案,保证方案数不超过1000。

    链接:http://poj.org/problem?id=1934

    思路

    开始看到这道题时,一想不就是一道暴力吗,用(f_i,_j)按字典序保存A串的前i个字符与B串的前j个字符所组成的所有的最长公共子序列,普通转移时归并维护字符串的顺序,顺带去重,复杂度为O((80^3)*1000)。不过好像是常数太大?所以会超时_

    当你觉得陷入死胡同时,一定不要走到黑。设(S_i,_j)为A串的前i个字符与B串的前j个字符所组成一个公共子序列,对于一个(S_i,_j),我们考虑它可能转移到的状态,即下一个字母为'a'到'z'。如果要转移到的下一个字母为k,可能有很多个符合条件的点可以转移,那选哪一个呢?假设(nextA_i,_k) 表示离A序列的第i号位最近的且比i大的k字符的位置,(nextB_j,_k)类似,如果下一个字母是k,那么一定是转移到 (S_{{nextA_i,_k},{nextB_i,k}}),因为对于转移到的两个的i与j而言,转移到一对l,r(l>=i,r>=j)一定不会更优,可以用贪心的决策包容性来证明。所以我们预处理(nextA_i,_k)(nextB_j,_k)。再用dfs(i,j,len,s)表示当前搜到了(S_i,_j),长度为len的,串为s的一个序列,通过循环'a'-'z'继续搜索,当一个次dfs时26个字母都无法搜下去,那么就更新答案。最后的答案不用排序,不用判重。

    但复杂度还是太高,所以我们剪枝。考虑对于最终的一种最优方案,他一定是从子状态的一种最优方案转移来的。所以我们先用dp计算(S_i,_j)的最长长度,如果对于dfs(i,j,len,s),len小于这个长度时,那么他肯定不能转移到最优答案,所以return。我们巧妙的避开了dp的缺点(不好保存具体方案),而利用了优点(很好求出最优长度)。

    代码

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int N=1100;
    string ans[N];
    int dp[90][90],fa[90][30],fb[90][30],alph[30],maxl=0,cnt;
    char a[90],b[90];
    string s;
    void load(char a[],int fa[][30],int n)
    {
    	memset(alph,0,sizeof(alph));
    	for(int i=n;i>=1;i--)
    	{
    		int k=a[i]-'a'+1;
    		if(alph[k])
    			for(int j=i;j<alph[k];j++)
    				fa[j][k]=alph[k];
    		alph[k]=i;
    	}
    	for(int i=1;i<=26;i++)
    		if(alph[i])
    			for(int j=0;j<alph[i];j++)
    				fa[j][i]=alph[i];
    }
    void updata(int len)
    {
    	if(len<maxl) return;
    	if(len>maxl) cnt=0,maxl=len;
    	ans[++cnt]=s;
    }
    void dfs(int l,int r,int len)
    {
    	int ll,rr,k=0;
    	for(int i=1;i<=26;i++)
    	{
    		ll=fa[l][i],rr=fb[r][i];
    		if(!ll||!rr||dp[ll][rr]>len+1) continue;
    		k=1;
    		s.insert(s.end(),1,'a'+i-1);
    		dfs(ll,rr,len+1);
    		s.erase(s.end()-1);
    	}
    	if(!k) updata(len);
    }
    int main()
    {
    	scanf("%s%s",a+1,b+1);
    	int n=strlen(a+1),m=strlen(b+1);
    	load(a,fa,n);load(b,fb,m);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    		{
    			if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
    			dp[i][j]=max(dp[i][j],max(dp[i-1][j],dp[i][j-1]));
    		}
    	dfs(0,0,0);
    	for(int i=1;i<=cnt;i++)
    		cout<<ans[i]<<endl;
    	return 0;
    }
    		
    
  • 相关阅读:
    八月二十九学习报告
    文本操作
    EL表达式
    注解开发
    逆向
    内置对象和方法
    每日日报2020.11.10 1905
    每日日报2020.11.12 1905
    每日日报2020.11.17 1905
    每日日报2020.11.20 1905
  • 原文地址:https://www.cnblogs.com/flashlizard/p/10993595.html
Copyright © 2011-2022 走看看