洛谷2943 [USACO09MAR]清理Cleaning Up——转变枚举内容的dp

题目: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;
}
原文地址:https://www.cnblogs.com/Narh/p/9189317.html