[BZOJ1584][Usaco2009 Mar]Cleaning Up 打扫卫生(dp+数学相关优化)

题目描述

传送门

题解

这题 n2 的暴力非常好想,预处理[l,r]有多少种食物sum(l,r),然后 f(i)=min{f(j)+sum(j+1,i)2}(1<=j<i)
然后利用一点数学知识就有一个非常巧妙的优化
这道题的答案是不会超过n的,所以要想最优的话,枚举的sum(l,r)不能超过 n

预处理pre(i),nxt(i)表示与位置i食物相同的前一个\下一个的位置
pos(j)表示[pos(j)+1,i]一共有j种不同的食物
那么 f(i)=min{f(pos(j))+jj}(0<=j<=n)

如何维护pos(j)呢?
记cnt(j)表示[pos(j),i]一共有多少种颜色
当i从i-1转移来时可以通过判断位置i的食物的pre来计算cnt(j)
如果cnt(j)>j即不合法,那么pos(j)要向后移动,直到把某一种颜色在区间中完全删除,这中间可以用nxt来判断

pos(j)单调移动,总时间复杂度 O(nn)

代码

#include
#include
#include
#include
#include
using namespace std;
#define N 50005

int n,m;
int food[N],head[N],pre[N],tail[N],nxt[N],pos[N],cnt[N],f[N];

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i) scanf("%d",&food[i]);
    for (int i=1;i<=n;++i) pre[i]=-1,nxt[i]=n+1;
    for (int i=n;i>=1;--i)
    {
        if (head[food[i]]) pre[head[food[i]]]=i;
        head[food[i]]=i;
    }
    for (int i=1;i<=n;++i)
    {
        if (tail[food[i]]) nxt[tail[food[i]]]=i;
        tail[food[i]]=i;
    }
    memset(f,127,sizeof(f));f[0]=0;
    for (int i=1;i<=n;++i)
    {

        for (int j=1;j*j<=n;++j)
            if (pre[i]<=pos[j]) ++cnt[j];
        for (int j=1;j*j<=n;++j)
            if (cnt[j]>j)
            {
                ++pos[j];
                while (nxt[pos[j]]<=i) ++pos[j];
                --cnt[j];
            }
        for (int j=1;j*j<=n;++j) f[i]=min(f[i],f[pos[j]]+j*j);
    }
    printf("%d\n",f[n]);
}

你可能感兴趣的:(题解,dp,数学相关)