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(){;}
只要是活着的东西,就算是神我也杀给你看。