1. 程式人生 > >洛谷2943 [USACO09MAR]清理Cleaning Up——轉變枚舉內容的dp

洛谷2943 [USACO09MAR]清理Cleaning Up——轉變枚舉內容的dp

min string size math 記錄 clas 出現 memset span

題目:https://www.luogu.org/problemnew/show/P2943

一下想到n^2。然後不會了。

看過TJ之後似乎有了新的認識。

  n^2的冗余部分在於當後面那部分的種類數一樣時,只需用最前面的dp轉移過來即可。

  所以如果枚舉的是後面那部分的種類數,對於每個種類數記錄一下最前面的dp,也許會好一些。

但是種類也有n種,怎麽辦?

  考慮是否需要枚舉全部從1~n。

  k*k是一個比較大的數。發現一定有一種情況使得時間花費為n(即全部單個選),所以只需要枚舉k*k<=n的種類數即可。

結果知道了思路卻還是寫不出來。

  關鍵在於怎麽在新入一個值之後維護“後面那部分有 j 個種類”。

  1)樹狀數組。就用HH的項鏈那樣的思想。

      結果發現不能很快找到新的對應地方。

  2)(參考TJ)記錄pre,當pre[ i ]<=zy[ j ]的時候zy[ j+1 ]=zy[ j ]。並且倒序什麽的。

      發現不對。因為轉移給 j+1 後不一定是最靠前的位置。

  3)(再次參考TJ)記錄pre和nxt,當pre[ i ]<=zy[ j ]的時候將zy[ j ]++到合適的第一個位置。

      結果還是不行。因為 j 在前面的一些時候其實是不滿 j 個種類的。貿然++會使位置偏大。

        那記錄一個cnt表示現在已經出現了幾個種類,按這個枚舉 j 呢?結果還是不行。

          因為就算上界改為現在出現了幾個種類,在最開始的時候一些 j 其實還是不滿 j 個種類。

  4)(再再次參考TJ)記錄cnt[ j ],當 cnt[ j ] > j 的時候進入while。

      當不行的時候就是要多記錄一些東西才行呢……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=4e5+5,M=200;
int n,m,a[N],dp[N],pre[N],nxt[N],pos[N],zy[N],cnt[N];
int rdn() { int ret=0;char ch=getchar(); while(ch>9||ch<0)ch=getchar(); while(ch>=0&&ch<=9)(ret*=10)+=ch-0,ch=getchar(); return ret; } int main() { n=rdn();m=rdn(); memset(dp,1,sizeof dp);dp[0]=0; for(int i=1;i<=n;i++) { a[i]=rdn();pre[i]=pos[a[i]];nxt[pre[i]]=i;pos[a[i]]=i;nxt[i]=n+1; } for(int i=1;i<=n;i++) for(int j=1;j*j<=n;j++) { if(pre[i]<=zy[j])cnt[j]++;//不要cnt[zy[j]] if(cnt[j]>j) { cnt[j]--;while(nxt[zy[j]+1]<=i)zy[j]++;zy[j]++; } dp[i]=min(dp[i],dp[zy[j]]+j*j); } printf("%d",dp[n]); return 0; }

洛谷2943 [USACO09MAR]清理Cleaning Up——轉變枚舉內容的dp