1. 程式人生 > >洛谷2187 小Z的筆記(DP)

洛谷2187 小Z的筆記(DP)

題目

由於沒有好好聽課,小Z的筆記全都記的雜亂無章,出現了好多錯誤的地方。小Z的筆記是如此的糟糕,以至於他只記了一句例句,而且自己還不知道是什麼意思……然後在老師講語法的時候,小Z又零星的記了幾個字母對,老師說,這幾個字母對是絕對不能相鄰的,而且相鄰是不關心字母的順序的,比如老師說,“ab”不能相鄰,那麼相同的,“ba”也不能相鄰。
現在小Z到家了,打開了上課的筆記,然後他發現筆記有很多自相矛盾的地方:為什麼下面的不能相鄰的字母對會出現在上面的例句裡面呢?糾結再三,小Z覺得下面的東西相對比較簡單,所以記錯的概率比較小……他決定在上面的例句裡面擦掉幾個字母,使得句子變得合法。
但是小Z還有其他作業要做呢,來不及整理筆記了,就把這個艱鉅的任務留給了大家,請問大家,小Z最少要擦掉幾個字母,才能使得上面的例句合法?

題解

DP
設f[i]表示前i個最少擦除個數,則有

f[i]=\min_{j<i,v[s[i],s[j]]=true}\left\{f[j]+i-j\right\},其中v[i][j]表示i和j可以放在一起。

這樣做時間複雜度為O(N^2)。
我們發現其實j從1列舉到i-1,枚舉了這麼多,其實關注的就是26個字母最後出現的那個f[j]。
設g[c]表示以字母c結尾的最優結果。
回到DP方程上看,我們分離開來看f[i]=\min\left\{(f[j]-j)+i\right\},我們令g[c]以字母c結尾的最小f[j]-j。
所以f的轉移方程就是f[i]=\min\left\{g[c]+i\}

總結

一道很好的根據公式進行優化的題,同時還精簡了轉移的決策,做到加速。

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,m;
char s[maxn];
bool v[30][30];
int f[maxn],g[maxn];

int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    scanf("%d",&m);
    memset(v,true,sizeof(v));
    for(int i=1;i<=m;i++)
    {
        char c[3];
        scanf("%s",c);
        c[0]-='a';c[1]-='a';
        v[c[0]][c[1]]=v[c[1]][c[0]]=false;
    }
    for(int i=1;i<=n;i++)
    {
        int c=s[i]-'a';
        f[i]=i;
        for(int j=0;j<26;j++) if(v[c][j])
        {
            f[i]=min(f[i],g[j]+i-1);
        }
        g[c]=min(g[c],f[i]-i);
    }
    printf("%d\n",f[n]);
    return 0;
}