String DP summary

Posted by Fredric on Fri, 04 Feb 2022 09:37:42 +0100


preface:
Most question types of string DP use the minimum or maximum number of operation steps to change the original string into a new string. Therefore, most of them use interval DP to solve, and some of them will be optimized by interval DP. Below are some common question types.

1, Palindrome string multi-dimensional deformation

1. Find the length of the longest palindrome subsequence in the string

This is an interval DP template question. The code is easy to understand. I won't repeat it here

1. Longest palindrome subsequence interval DP

Title Link

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n=s.size();
        s=" "+s;
        vector<vector<int>>f(n+1,vector<int>(n+1));
        for(int len=1;len<=n;len++)
            for(int i=1;i+len-1<=n;i++)
            {
                int j=i+len-1;
                if(i==j)f[i][i]=1;
                else 
                {
                    f[i][j]=max(f[i+1][j],f[i][j-1]);
                    if(s[i]==s[j])f[i][j]=max(f[i+1][j-1]+2,f[i][j]);
                }
            }
        return f[1][n];
    }
};

2. Find the length of the longest palindrome substring in the string

1. Longest palindrome substring interval DP

Title Link

The fallibility of this question is that it can't be as direct as most interval dp l e n = 1 len=1 When len=1 f [ i ] [ j ] = 1 f[i][j]=1 f[i][j]=1
Pay attention to special judgment l e n = 2 And s [ i ] = s [ j ] len=2 and s[i]=s[j] len=2 and s[i]=s[j]

class Solution {
public:
    int getLongestPalindrome(string s) {
        int n=s.size();
        s=" "+s;
        int res=0;
        vector<vector<int>>f(n+1,vector<int>(n+1));
        for(int len=1;len<=n;len++)
            for(int i=1;i+len-1<=n;i++)
            {
                int j=i+len-1;
                if(s[i]==s[j])
                {
                    if(len<=3)f[i][j]=true;
                    else f[i][j]=f[i+1][j-1];
                }
                else f[i][j]=false;
                if(f[i][j])res=max(res,j-i+1);
            }
        return res;
    }
};

2. Palindrome string of small A interval DP

Title Link

Upgraded version, adding a common circular processing

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=5005;
bool f[2*N][2*N];
int main()
{
    string s;
    cin>>s;
    int n=s.size();
    s+=s;
    s=" "+s;
    int res=0;
    for(int len=1;len<=n;len++)
    {
        for(int i=1;i+len-1<=2*n;i++)
        {
            int j=i+len-1;
            if(s[i]==s[j])
            {
                if(j-i<3)f[i][j]=true;
                else f[i][j]=f[i+1][j-1];
            }
            else f[i][j]=false;
            if(f[i][j])res=max(res,j-i+1);
        }
    }
    cout<<res<<endl;
}

3. The maximum value of palindrome string with no right value

1.Palindrome - LCS optimization

Title Link

MLE writing method: (interval DP)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5010;
int f[N][N];
int main()
{
    int n;
    string s;
    cin>>n;
    cin>>s;
    s=" "+s;
    for(int len=1;len<=n;len++)
    {
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1;
            if(len==1)f[i][j]=0;
            else if(s[i]==s[j])
            {
                if(len==2)f[i][j]=0;
                else f[i][j]=f[i+1][j-1];
            }
            else
            {
                f[i][j]=min(f[i+1][j],f[i][j-1])+1;
            }
        }
    }
    cout<<f[1][n]<<endl;
}

Rolling array optimization AC writing method (a mathematical formula needs to be used here)
r e s = n − L C S res=n-LCS res=n−LCS

Because the length LCS of the largest common subsequence of the string and its reverse string is equal to the corresponding LCS characters in the original string. The answer is to add the same character in the corresponding position for those with unequal correspondence.

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5010;
int f[2][N]={0};
int main()
{
	int n;
	cin>>n;
    string a,b;
    cin>>b;a=b;
    reverse(b.begin(),b.end());
    a=" "+a;
    b=" "+b;
    for(int i=1;i<=n;i++)
    {
    	for(int j=1;j<=n;j++)
    	{
    		f[i&1][j]=max(f[i-1&1][j],f[i&1][j-1]);
    		if(a[i]==b[j])f[i&1][j]=max(f[i&1][j],f[i-1&1][j-1]+1);
		}
	}
	cout<<n-f[n&1][n]<<endl;
}

4. Maximum value problem of weighted palindrome string

1.Cheapest Palindrome - interval dp

Title Link

d[i][j]: from I to j is the minimum cost of palindrome string
State transition equation:
When s [ i ] = s [ j ] s[i]=s[j] When s[i]=s[j]:

d [ i ] [ j ] = d [ i + 1 ] [ j − 1 ] d[i][j]=d[i+1][j-1] d[i][j]=d[i+1][j−1]

otherwise

d [ i ] [ j ] = m i n ( d [ i ] [ j − 1 ] + a d d [ s [ j ] ] , d [ i + 1 ] [ j ] + a d d [ s [ i ] ] , d e l [ s [ i ] ] + d [ i + 1 ] [ j ] , d e l [ s [ j ] ] + d [ i ] [ j − 1 ] ) d[i][j]=min(d[i][j-1]+add[s[j]],d[i+1][j]+add[s[i]],del[s[i]]+d[i+1][j],del[s[j]]+d[i][j-1]) d[i][j]=min(d[i][j−1]+add[s[j]],d[i+1][j]+add[s[i]],del[s[i]]+d[i+1][j],del[s[j]]+d[i][j−1])

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2010;
int f[N][N];
int n,m;
int add[N],del[N];
int main()
{
    cin>>n>>m;
    string s;
    cin>>s;s=" "+s;
    char op[2];
    int x,y;
    for(int i=0;i<n;i++)
    {
    	cin>>op;
    	cin>>x>>y;
    	add[op[0]-'a']=x;
    	del[op[0]-'a']=y;
	}
	memset(f,0x3f,sizeof f);
	f[0][0]=0;
	for(int len=1;len<=m;len++)
	{
		for(int i=1;i+len-1<=m;i++)
		{
			int j=i+len-1;
			if(s[i]==s[j])
			{
				if(len<=2)f[i][j]=0;
				else f[i][j]=f[i+1][j-1];
			}
			else
			{
				int x=min(f[i+1][j]+add[s[i]-'a'],f[i][j-1]+add[s[j]-'a']);
				int y=min(f[i+1][j]+del[s[i]-'a'],f[i][j-1]+del[s[j]-'a']);
				f[i][j]=min(x,y);
			}
		}
	}
	cout<<f[1][m]<<endl;
}

5. Merge palindrome substrings

1. Merge palindrome substring interval DP

Title Link

It is equivalent to expanding two-dimensional on the basis of the longest palindrome substring

Detailed problem solving entry: Portal

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=110;
void solve()
{
    bool f[N][N][N][N];
    int res=0;
    string a,b;
    cin>>a>>b;
    int n=a.size(),m=b.size();
    a=" "+a,b=" "+b;
    for(int len1=0;len1<=n;len1++)
    {
        for(int len2=0;len2<=m;len2++)
        {
            for(int i=1;i+len1-1<=n;i++)
            {
                for(int k=1;k+len2-1<=m;k++)
                {
                    int j=i+len1-1,l=k+len2-1;
                    if(len1+len2<=1)f[i][j][k][l]=1;
                    else
                    {
                        bool &v=f[i][j][k][l];
                        v=0;
                        if(len1>1)v|=(f[i+1][j-1][k][l]&&(a[i]==a[j]));
                        if(len1&&len2)v|=(f[i+1][j][k][l-1]&&(a[i]==b[l]));
                        if(len1&&len2)v|=(f[i][j-1][k+1][l]&&(a[j]==b[k]));
                        if(len2>1)v|=(f[i][j][k+1][l-1]&&(b[k]==b[l]));
                    }
                    if(f[i][j][k][l])res=max(len1+len2,res);
                }
            }
        }
    }
    cout<<res<<endl;
    
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        solve();
    }
}

2, Find subsequence from original string

1. Different subsequences - DP

Title Link

y general solution:

class Solution {
public:
    int numDistinct(string s, string t) {
        int n=s.size(),m=t.size();
        s=" "+s;
        t=" "+t;
        vector<vector<unsigned long long>>f(n+1,vector<unsigned long long>(m+1));
        for(int i=0;i<=n;i++)f[i][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                f[i][j]=f[i-1][j];
                if(s[i]==t[j])f[i][j]+=f[i-1][j-1];
            }
        return f[n][m];
    }
};

2. Nun heh heh aaaaaa - dynamic programming + fast power + suffix sum

Title Link

See the code comments for details

Regardless of a, first calculate the number of non-A aromatic strings, multiply the last number by 2 to the power of A-1, add and take the modulus.

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+5;
typedef long long LL;
int dp[10][N];
int a[N];//Number of suffixes for a
const int mod = 998244353;


//Number of a in preprocessing suffix
void init(string s)
{
    memset(a,0,sizeof a);
    for(int i=s.size();i>=1;i--)
    {
        a[i]=a[i+1]+(s[i]=='a');
    }
}
// Don't accidentally use turtle speed here. I just accidentally used turtle speed to ride TLE
//There are several a's in the back. Each a can be selected or not, so it is the power of a of 2, but the number of a can't be 0, so - 1
LL qmi(int b)
{
    LL res=1%mod;
    LL a=2;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;

    }
    return (res-1)%mod;
}
int main()
{
    std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
    {
        string s;
        cin>>s;
        //Start subscript from 1
        s=" "+s;
        init(s);
        
        //string ss=" nunhehheh";
        
        //dp process nunhehheh
        //dp[j][i] indicates the number of occurrences of ss[1~j] string in the first I characters of s
        LL res=0;
        for(int i=1;i<=s.size();i++)
        {
            dp[1][i]=(dp[1][i-1]+(s[i]=='n'))%mod;
            dp[2][i]=(dp[2][i-1]+dp[1][i-1]*(s[i]=='u'))%mod;
            dp[3][i]=(dp[3][i-1]+dp[2][i-1]*(s[i]=='n'))%mod;
            dp[4][i]=(dp[4][i-1]+dp[3][i-1]*(s[i]=='h'))%mod;
            dp[5][i]=(dp[5][i-1]+dp[4][i-1]*(s[i]=='e'))%mod;
            dp[6][i]=(dp[6][i-1]+dp[5][i-1]*(s[i]=='h'))%mod;
            dp[7][i]=(dp[7][i-1]+dp[6][i-1]*(s[i]=='h'))%mod;
            dp[8][i]=(dp[8][i-1]+dp[7][i-1]*(s[i]=='e'))%mod;
            dp[9][i]=(dp[8][i-1]*(s[i]=='h'))%mod;
            if(dp[9][i])res=res%mod+(dp[9][i]*qmi(a[i+1]))%mod;
            res%=mod;
        }
        cout<<res<<endl;
    }
}

3, String partition problem

1.[NOIP2001] count the number of words - Double DP + string processing

Title Link

Dual DP
The number of strings contained in the first sum[i][j] preprocessing I to j
Judging from the back to the front is convenient to judge whether the i-th character is written with a check function
If x.find (a[i]) is used for the ith character = = 0, return true

The second dp is the maximum
f[i][j] represents the maximum value of the first I characters divided into j segments
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ k ] [ j − 1 ] + s u m [ k + 1 ] [ i ] ) ( j − 1 < k < i ) f[i][j]=max(f[i][j],f[k][j-1]+sum[k+1][i]) (j-1<k<i) f[i][j]=max(f[i][j],f[k][j−1]+sum[k+1][i])(j−1<k<i)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
int sum[N][N];
int f[N][45];
string a[10];
int n,m,K;
string s;
bool check(int l,int r)
{
    string ss=s.substr(l,r-l+1);
    for(int i=1;i<=n;i++)
        if(ss.find(a[i])==0)return true;
    return false;
}
int main()
{
    cin>>n>>K;
    s=" ";
    while(n--)
    {
        string _;
        cin>>_;
        s+=_;
    }
    m=s.size()-1;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int r=m;r>=1;r--)
    {
         for(int l=r;l>=1;l--)
         {
             sum[l][r]=sum[l+1][r];
             if(check(l,r))sum[l][r]++;
         }
        f[r][1]=sum[1][r];
    }
    for(int i=2;i<=m;i++)
        for(int j=2;j<=K;j++)
            for(int k=j-1;k<i;k++)
                f[i][j]=max(f[i][j],f[k][j-1]+sum[k+1][i]);
    cout<<f[m][K]<<endl;
}

2.[SCOI2009] painter

Title Link

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 52;
int f[N][N][N*N][2];
char g[N][N];
//dp[i][j][k][0/1]
//The first i section j is painted k times, and the last section is painted red / blue with the maximum number of correct grids
//Red (0) blue (1)
int main()
{
    int n,m,K;
    cin>>n>>m>>K;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>g[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=1;k<=K;k++)
            {
                if(j==1)
                {
                    f[i][j][k][0]=max(f[i-1][m][k-1][0],f[i-1][m][k-1][1])+(g[i][j]=='0');
                    f[i][j][k][1]=max(f[i-1][m][k-1][0],f[i-1][m][k-1][1])+(g[i][j]=='1');
                }
                else
                {
                    f[i][j][k][0]=max(f[i][j-1][k][0],f[i][j-1][k-1][1])+(g[i][j]=='0');
                    f[i][j][k][1]=max(f[i][j-1][k][1],f[i][j-1][k-1][0])+(g[i][j]=='1');
                }
            }
    cout<<max(f[n][m][K][0],f[n][m][K][1])<<endl;
}

4, String coloring problem

1.[CQOI2007] PAINT - interval DP

Title Link

f[i][j] is the minimum number of coloring times when the left boundary is I and the right boundary is j
If s[i]==s[j]
f [ i ] [ j ] = m i n ( f [ i + 1 ] [ j ] , f [ i ] [ j − 1 ] ) f[i][j]=min(f[i+1][j],f[i][j-1]) f[i][j]=min(f[i+1][j],f[i][j−1])
otherwise
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] ) ( i = < k < j ) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]) (i=<k<j) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j])(i=<k<j)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55;
int n;
char s[N];
int f[N][N];
int main()
{
    cin>> s+1;
    n=strlen(s+1);
    for(int len=1;len<=n;len++)
    {
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1;
            if(len==1)f[i][j]=1;
            else if(s[i]==s[j])f[i][j]=min(f[i+1][j],f[i][j-1]);
            else
            {
                for(int k=i;k<j;k++)f[i][j]=min(f[i][k]+f[k+1][j],f[i][j]);
            }
        }
    }
    cout<<f[1][n]<<endl;
}

2. Little painter

Title Link

Similarly, just limit the length of the interval and add a judgment

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int f[N][N];
int s[N];
int main()
{
    int n,K;
    cin>>n>>K;
    for(int i=1;i<=n;i++)cin>>s[i];
    memset(f,0x3f,sizeof f);
    for(int len=1;len<=n;len++)
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1;
            if(i==j)f[i][j]=1;
            else if(len<=K&&s[i]==s[j])f[i][j]=min(f[i+1][j],f[i][j-1]);
            for(int k=i;k<j;k++)f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
        }
    cout<<f[1][n]<<endl;
}

3.Color Stripe - DP / greedy

Title Link

Greedy writing

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 500010;
signed main()
{
	int n,k;
	cin>>n>>k;
	string s;cin>>s;
	int res=0;
	if(k>2)
	{
		int res=0;
		for(int i=1;i<s.size();i++)
		{
			if(s[i]==s[i-1])
			{
				res++;
				for(int j=0;j<k;j++){
					if('A'+j!=s[i] && 'A'+j!=s[i+1]){
						s[i]='A'+j;
						break;
					}
				}
			}
		}
		cout<<res<<endl;
		cout<<s<<endl;
	}
	else 
	{
		string ss1="A",ss2="B";
		
		int res1=0,res2=0;
		res1+=ss1[0]!=s[0],res2+=ss2[0]!=s[0]; 
		for(int i=1;i<s.size();i++)
		{
			ss1+=ss1[i-1]=='A'?'B':'A';
			ss2+=ss2[i-1]=='A'?'B':'A';
			if(ss1[i]!=s[i])res1++;
			if(ss2[i]!=s[i])res2++;
		}
		if(res1<res2)
		{
			cout<<res1<<endl;
			cout<<ss1<<endl;
		}
		else 
		{
			cout<<res2<<endl;
			cout<<ss2<<endl;
		}
	}
}

DP writing method

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 500010;
int f[N][28];
int pre[N][28];
signed main()
{
	int n,m;
	cin>>n>>m;
	string s;
	cin>>s;
	s=" "+s;
	memset(f,0x3f,sizeof f);
	for(int i=0;i<m;i++)f[0][i]=0;
	int minv=1e7;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			for(int k=0;k<m;k++)
			{
				if(k==j)continue;
				if(j+'A'==s[i])
				{
					if(f[i][j]>f[i-1][k])
					{
						pre[i][j]=k;
						f[i][j]=f[i-1][k];
					}	
				}
				else 
				{
					if(f[i][j]>f[i-1][k]+1)
					{
						pre[i][j]=k;
						f[i][j]=f[i-1][k]+1;	
					}
				}
				
			}
		}
	}
	int res=1e7;
	int t;
	for(int i=0;i<m;i++)
	{
		if(f[n][i]<res)
		{
			res=f[n][i];
			t=i;
		}
	}
	cout<<res<<endl;
	string ss="";
	for(int i=n;i>=1;i--)
	{
		ss+=t+'A';
		t=pre[i][t];
	}
	reverse(ss.begin(),ss.end());
	cout<<ss<<endl;
}

Topics: Algorithm Dynamic Programming