bzoj1584[Usaco2009 Mar]Cleaning Up 打扫卫生


/*
n*n的算法 比较容易想到 特判的好cena的 70分 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[40005],f[40005];
int main()
{
    //freopen("cleanup.in","r",stdin);
    //freopen("cleanup.out","w",stdout);
    int n,m,i,j,k;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        f[i]=i;
    }
    for(i=1;i<=n;i++)
    {
        bool p[40005]={0};
        int q=0;
        f[i]=f[i-1]+1;
        for(j=i;j>=1;j--)
        {
            if(p[a[j]]==0)
            {
                p[a[j]]=1;
                q++;
            }
            f[i]=min(f[i],f[j-1]+q*q);
            if(q*q>=i)break;
        }
    }
    printf("%d",f[n]);
    return 0;
}

 


/*
n*sqrt(n): 维护这么几个值 pos[j] 当前位置到向前的某个位置不同的数字个数为j 并且最靠左 pre[a[i]] a[i]最后一次出现的位置 cnt[j] pos[j]+1到i序列不同的数字个数 因为如果某个序列不同的数字个数>sqrt(n) 那么分成n段最优 所以研究i向前到m=sqrt(n)个不同的数字的区间 方程为f[i]=min(f[i],f[pos[j]]+j*j) 关键是维护好这几个值 */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #define maxn 40010 using namespace std; int a[maxn],pre[maxn],cnt[maxn],f[maxn],pos[maxn]; int n,m; int main() { //freopen("cleanup.in","r",stdin); //freopen("cleanup.out","w",stdout); scanf("%d%d",&n,&m); m=sqrt(n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); f[i]=i; } memset(pre,-1,sizeof(pre)); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) if(pre[a[i]]<=pos[j])//当前序列里没有出现过 cnt[j]++;//个数++ pre[a[i]]=i;//更新a[i]最后一次出现的位置 for(int j=1;j<=m;j++) if(cnt[j]>j)//区间左端点右移 { int t=pos[j]+1; while(pre[a[t]]>t)t++;//知道移动的是不在当前区间里的数 pos[j]=t; cnt[j]--; } for(int j=1;j<=m;j++) f[i]=min(f[i],f[pos[j]]+j*j); } printf("%d\n",f[n]); return 0; }

 

posted @ 2016-05-08 16:03  一入OI深似海  阅读(211)  评论(0编辑  收藏  举报