bzoj1584【Usaco2009 Mar】Cleaning Up 打扫卫生

1584: [Usaco2009 Mar]Cleaning Up 打扫卫生

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 390   Solved: 265
[ Submit][ Status][ Discuss]

Description

有N头奶牛,每头那牛都有一个标号Pi,1 <= Pi <= M <= N <= 40000。现在Farmer John要把这些奶牛分成若干段,定义每段的不河蟹度为:若这段里有k个不同的数,那不河蟹度为k*k。那总的不河蟹度就是所有段的不河蟹度的总和。 

Input

第一行:两个整数N,M 

第2..N+1行:N个整数代表每个奶牛的编号 

Output

一个整数,代表最小不河蟹度 

Sample Input

13 4
1
2
1
3
2
2
3
4
3
4
3
1
4

Sample Output

11

HINT

Source

Gold




思路很棒的DP题

可以发现每一段中不同的数的个数不超过sqrt(n)个。

所以我们只需要记录每个点为结尾,一段序列中不同的数的个数不超过j的左端点的位置-1,pos[j]。通过pos数组进行DP转移,可以将复杂度从O(n^2)将为O(sqrt(n)n)。f[i]=min{f[pos[j]]+j*j}。

下面我们考虑右端点右移,也就是i++时,如何更新pos数组。

为了快速更新,我们还需要记录每个数值i的最后一个位置pre[i],和以pos[j]为左端点的序列中不同的数的个数cnt[j]。

当i++后,如果pre[a[i]]≤pos[j],那么cnt[j]++,说明序列中加入一个新的元素。

然后我们找出所有cnt[j]>j的序列,也就是不满足条件的序列,适当地调整左端点pos[j],使其满足cnt[j]≤j。其中对于左端点的调整只需要暴力即可,这是均摊O(1)的。

所以这道题最终的时间复杂度为O(sqrt(n)n)。

P.S.可能写的不太清楚…如果不懂还是看程序吧 QAQ

P.P.S.题目样例有问题...应该是10...坑爹啊 =。=




#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define maxn 40005
using namespace std;
int n,m;
int a[maxn],f[maxn],pre[maxn],pos[maxn],cnt[maxn];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int main()
{
	n=read();m=read();m=int(sqrt(n));
	F(i,1,n) a[i]=read();
	F(i,0,n) f[i]=i;
	memset(pre,-1,sizeof(pre));
	F(i,1,n)
	{
		F(j,1,m) if (pre[a[i]]<=pos[j]) cnt[j]++;
		pre[a[i]]=i;
		F(j,1,m) if (cnt[j]>j)
		{
			int t=pos[j]+1;
			while (pre[a[t]]>t) t++;
			pos[j]=t;
			cnt[j]--;
		}
		F(j,1,m) f[i]=min(f[i],f[pos[j]]+j*j);
	}
	printf("%d\n",f[n]);
}


你可能感兴趣的:(dp,bzoj)