1. 程式人生 > >PTA L3-020 至多刪三個字符 (序列dp/序列自動機)

PTA L3-020 至多刪三個字符 (序列dp/序列自動機)

一行 const def pro typedef -o 推導 -i long

給定一個全部由小寫英文字母組成的字符串,允許你至多刪掉其中 3 個字符,結果可能有多少種不同的字符串?

輸入格式:

輸入在一行中給出全部由小寫英文字母組成的、長度在區間 [4, 1] 內的字符串。

輸出格式:

在一行中輸出至多刪掉其中 3 個字符後不同字符串的個數。

輸入樣例:

ababcc

輸出樣例:

25

提示:

刪掉 0 個字符得到 "ababcc"。

刪掉 1 個字符得到 "babcc", "aabcc", "abbcc", "abacc" 和 "ababc"。

刪掉 2 個字符得到 "abcc", "bbcc", "bacc", "babc", "aacc", "aabc", "abbc", "abac" 和 "abab"。

刪掉 3 個字符得到 "abc", "bcc", "acc", "bbc", "bac", "bab", "aac", "aab", "abb" 和 "aba"。

解法:

前置技能:求一個序列中所有的不同子序列個數。

eg:FZU - 2129

設dp[i]為序列a的前i個元素所組成的不同子序列個數,則有狀態轉移方程:$dp[i]=\left\{\begin{matrix}\begin{aligned}&2dp[i-1]+1,pre[a[i]]=-1\\&2dp[i-1]-dp[pre[a[i]]-1],pre[a[i]]\neq -1\end{aligned}\end{matrix}\right.$

其中pre[a[i]]表示a[i]前面第一個和a[i]相同的元素的下標。

解釋:第i個元素a[i]有兩種選擇:選或不選。

若不選a[i],則dp[i]繼承dp[i-1]的全部子序列,因此有dp[i]+=dp[i-1]。

若選a[i],則dp[i]在dp[i-1]的全部子序列的尾部填加了個元素a[i],因此仍有dp[i]+=dp[i-1]。但這樣會有很多重復的序列,因此要去重,即去掉前面和a[i]相同的元素之前的序列(因為它們加上a[i]形成的序列已經被算過了),因此有dp[i]-=dp[pre[a[i]]-1]。特別地,如果a[i]前面沒有與a[i]相同的元素,那麽沒有重復的序列,並且a[i]自己單獨形成一個新序列,此時dp[i]++。

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 typedef long long ll;
 5 typedef double db;
 6 const int N=1e6+10,mod=1e9+7;
 7 int a[N],n,dp[N],pre[N];
 8 int main() {
 9     while(scanf("%d",&n)==1) {
10         memset(pre,-1,sizeof pre);
11         for(int i=1; i<=n; ++i)scanf("%d",&a[i]);
12         dp[0]=0;
13         for(int i=1; i<=n; ++i) {
14             dp[i]=(ll)dp[i-1]*2%mod;
15             if(~pre[a[i]])dp[i]=((ll)dp[i]-dp[pre[a[i]]-1])%mod;
16             else dp[i]=(dp[i]+1)%mod;
17             pre[a[i]]=i;
18         }
19         printf("%d\n",(dp[n]+mod)%mod);
20     }
21     return 0;
22 }

回到正題,此題是上題的升級版,等價於求一個長度為n的序列中長度為n,n-1,n-2,n-3的不同子序列個數之和。

基本思路是一致的,只需要在上述代碼的基礎上稍作改動即可。

設dp[i][j]為前i個元素刪了j個元素所形成的子序列個數,則有$dp[i]=\left\{\begin{matrix}\begin{aligned}&dp[i-1][j-1]+dp[i-1][j],pre[a[i]]=-1,j\neq i-1\\&dp[i-1][j-1]+dp[i-1][j]+1,pre[a[i]]=-1,j=i-1\\&dp[i-1][j-1]+dp[i-1][j]-dp[pre[a[i]]-1][j-(i-pre[a[i]])],pre[a[i]]\neq -1\end{aligned}\end{matrix}\right.$

推導過程類似,註意j的變化即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 typedef long long ll;
 5 typedef double db;
 6 const int N=1e6+10;
 7 char a[N];
 8 int n,pre[300];
 9 ll dp[N][4];
10 int main() {
11     memset(pre,-1,sizeof pre);
12     scanf("%s",a+1),n=strlen(a+1);
13     for(int i=1; i<=n; ++i) {
14         for(int j=0; j<=3; ++j) {
15             if(j>0)dp[i][j]+=dp[i-1][j-1];
16             dp[i][j]+=dp[i-1][j];
17             if(~pre[a[i]]&&j>=i-pre[a[i]])dp[i][j]-=dp[pre[a[i]]-1][j-(i-pre[a[i]])];
18             else if(i==j+1)dp[i][j]++;
19         }
20         pre[a[i]]=i;
21     }
22     printf("%lld\n",dp[n][0]+dp[n][1]+dp[n][2]+dp[n][3]);
23     return 0;
24 }

還有另一種解法是利用序列自動機,很簡單,設go[i][j]為第i個元素後第一個元素j出現的位置,先用類似dp的方式建立自動機,則問題轉化成了一個DAG上的dp問題。

但是由於序列自動機空間消耗較大,直接dfs可能會爆內存,比如這樣:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef unsigned long long ll;
 4 const int N=1e6+10;
 5 string s;
 6 int n;
 7 set<string> st;
 8 string subs(string& s,int l,int r) {return l>r?"":s.substr(l,r-l+1);}
 9 
10 int main() {
11     cin>>s,n=s.length();
12     for(int i=0; i<n; ++i)
13         for(int j=i; j<n; ++j)
14             for(int k=j; k<n; ++k)
15                 st.insert(subs(s,0,i-1)+subs(s,i+1,j-1)+subs(s,j+1,k-1)+subs(s,k+1,n-1));
16     printf("%d\n",st.size()+1);
17     return 0;
18 }

解決方法是自底而上,一遍dp一遍更新go數組,成功AC:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 typedef double db;
 5 const int N=1e6+10,M=26;
 6 char s[N];
 7 int n,go[M];
 8 ll dp[N][4];
 9 int main() {
10     scanf("%s",s),n=strlen(s);
11     dp[n][0]=dp[n][1]=dp[n][2]=dp[n][3]=1;
12     for(int i=n-1; i>=0; --i) {
13         go[s[i]-a]=i+1;
14         for(int j=0; j<=3; ++j) {
15             dp[i][j]=(j+(n-i)<=3);
16             for(int k=0; k<M; ++k)if(go[k]&&j+go[k]-i-1<=3)dp[i][j]+=dp[go[k]][j+go[k]-i-1];
17         }
18     }
19     printf("%lld\n",dp[0][0]);
20     return 0;
21 }

雖然序列自動機的功能比較強大,但時間和空間的消耗都與元素集合的大小有關,因此當元素集合過大的時候,可能就並不吃香了~~

PTA L3-020 至多刪三個字符 (序列dp/序列自動機)