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

 

转载于:https://www.cnblogs.com/Loser-of-Life/p/7574239.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值