bzoj1584 打扫卫生 dp

链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1584

题意:找到某种分割序列方法,使得每一段中所含数的种类平方之和最小。

考试时一时脑残连暴力$dp$都没写出来……

首先暴力dp应该都写得出来……$f[i]=min(f[j]+(cnt[j~i])^2)$

正解有个比较智障的优化……首先可以想到答案不会差过$n^2$(最差就是每一个一段),因此,我们只需要记录每段中有$1,2,3……sqrt(n)$个不同元素的情况,找到这些段开始的位置的前一个位置,记作$pos[j]$,那么,$f[i]=min(f[pos[j]]+j*j)$。

下面重点问题就变为$i$改变时如何修改$pos$数组。为方便我们再记录每种数字出现的上个位置$pre[j]$和每一段中有的数字种类$cnt[j]$。

首先,如果$pre[a[i]]<=pos[j]$,则$cnt[j]++$。

然后对于每一个$cnt[j]>j$的情况,暴力右移左端点,如果这时$pre[a[pos[j]]]==pos[j]$,$cnt[j]--$。

问题得解。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 using namespace std;
 7 const int maxn=40005;
 8 int a[maxn],pos[maxn],cnt[maxn],pre[maxn],n,m,f[maxn];
 9 int haha()
10 {
11     scanf("%d%d",&n,&m);
12     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
13     memset(f,0x3f,sizeof(f));f[0]=0;int num=(int)sqrt(n);
14     for(int i=1;i<=n;i++)
15     {
16         for(int j=1;j<=num;j++)
17             if(pre[a[i]]<=pos[j])cnt[j]++;
18         pre[a[i]]=i;
19         for(int j=1;j<=num;j++)
20             while(cnt[j]>j)
21             {
22                 pos[j]++;
23                 if(pre[a[pos[j]]]==pos[j])cnt[j]--;
24             }
25         for(int j=1;j<=num;j++)f[i]=min(f[i],f[pos[j]]+j*j);
26     }
27     printf("%d\n",f[n]);
28 }
29 int sb=haha();
30 int main(){;}
bzoj1584

 

posted @ 2017-09-22 16:37  ccc000111  阅读(151)  评论(0编辑  收藏  举报