洛谷2187 小Z的筆記(DP)
阿新 • • 發佈:2018-11-10
題目
由於沒有好好聽課,小Z的筆記全都記的雜亂無章,出現了好多錯誤的地方。小Z的筆記是如此的糟糕,以至於他只記了一句例句,而且自己還不知道是什麼意思……然後在老師講語法的時候,小Z又零星的記了幾個字母對,老師說,這幾個字母對是絕對不能相鄰的,而且相鄰是不關心字母的順序的,比如老師說,“ab”不能相鄰,那麼相同的,“ba”也不能相鄰。
現在小Z到家了,打開了上課的筆記,然後他發現筆記有很多自相矛盾的地方:為什麼下面的不能相鄰的字母對會出現在上面的例句裡面呢?糾結再三,小Z覺得下面的東西相對比較簡單,所以記錯的概率比較小……他決定在上面的例句裡面擦掉幾個字母,使得句子變得合法。
但是小Z還有其他作業要做呢,來不及整理筆記了,就把這個艱鉅的任務留給了大家,請問大家,小Z最少要擦掉幾個字母,才能使得上面的例句合法?
題解
DP
設f[i]表示前i個最少擦除個數,則有
,其中v[i][j]表示i和j可以放在一起。
這樣做時間複雜度為O(N^2)。
我們發現其實j從1列舉到i-1,枚舉了這麼多,其實關注的就是26個字母最後出現的那個f[j]。
設g[c]表示以字母c結尾的最優結果。
回到DP方程上看,我們分離開來看,我們令g[c]以字母c結尾的最小f[j]-j。
所以f的轉移方程就是。
總結
一道很好的根據公式進行優化的題,同時還精簡了轉移的決策,做到加速。
程式碼
#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; }