• 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
    ",f[n]);
    28 }
    29 int sb=haha();
    30 int main(){;}
    bzoj1584
  • 相关阅读:
    sharepoint_study_10
    sharepoint_study_9
    sharepoint_study_8
    需要经常读的文章(长期更新)
    sharepoint_study_7
    sharepoint_study_目录学习笔记(长期更新)
    windows_study_2
    sharepoint_study_6
    sharepoint_study_5
    sharepoint_study_4
  • 原文地址:https://www.cnblogs.com/Loser-of-Life/p/7574239.html
Copyright © 2020-2023  润新知