[USACO09MAR]清理Cleaning Up(dp)

题意翻译

很久很久以前,约翰只会做一种食品;而现在约翰能给他的NNN (1≤N≤40000)(1 \leq N \leq 40000)(1≤N≤40000)头奶牛供应MMM (1≤M≤N)(1 \leq M \leq N)(1≤M≤N)种不同的食品。但奶牛们非常挑剔,iii号奶牛只吃食品PiP_iPi​ (1≤Pi≤M)(1 \leq P_i \leq M) (1≤Pi​≤M)

每天,奶牛们按一定编号排队进自助餐厅用餐。不幸的是,这么多各类的食品会让清扫工作变得非常不方便。如果约翰在一次用餐中供应了KKK种食品,他之后就需要K2K^2K2的时间进行清扫。

为了减少清扫的时间,约翰决定把排好队的奶牛划分成若干组。每一组包含一段连续的奶牛。每一次,只让一组奶牛进入餐厅。这样,他可以让清扫所需的总用时变得最小。请计算这个最小用时。

样例解释:

如此划分:1\2\1\3\22\34343\1\4```

首先分成多少个组不重要。

我们考虑n最大为40000,如果把n分成n个组,那么最小用时为n.所以一个组不能超过√n,如果超过√n个,那么最小用时就大于n...,

所以一个组不能超过√n。所以我们可以枚举以某个点为最后一组的终点。考虑dp啊。

dp[i] = min(dp[i],dp[pos[j] - 1] + j * j); pos[j]表示:第i个位置之前第j个不同的数出现的最早位置。

转移有了,怎么求pos[j]?

nex[i]:记录下一个a[i]出现的位值。

pre[i]:记录上一个a[i]出现的位值。

cnt[j]:从pos[j]到i([pos[j],i])这段区间有多少个不同的数。 

last[a[i]]:记录上一个a[i]出现的位置。//用这个来算出前面两个。

对于第i个数,如果上一个a[i]出现的位值<pos[j],那么i 一定是在[pos[j],i]这段区间第一次出现,cnt[j]++;

如果cnt[j] > j,说明pos[j]要往后移,移到cnt[j] == j (其实只进了一个,所以cnt[j]最大比j大1,所以只要找到第一个nex[k]>i,的位置k就好。)

#include<cstdio>
#include<cmath>
int n,m,pos[205],f[40005],p[40005],last[40005],pre[40005],nex[40005],cnt[205];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&p[i]);
        pre[i]=last[p[i]];
        nex[last[p[i]]]=i;
        last[p[i]]=i;
        f[i]=1e9;nex[i]=n+1;
    }
    int t=sqrt(n);for(int i=1;i<=t;i++)pos[i]=1;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=t;j++)
     {
            if(pre[i]<pos[j])cnt[j]++; 
            if(cnt[j]>j)
            {
                cnt[j]--;
                while(nex[pos[j]]<i)pos[j]++;
                pos[j]++;
            }
            f[i]=min(f[pos[j]-1]+j*j,f[i]);
     }
    printf("%d\n",f[n]);
}//别人家的代码。

然而我自己也瞎搞了一番,优先队列复杂度拜拜了,然而洛谷上却能A。。。。。

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
int n,m,ha,shu;
int a[40005], nex[40005], pre[40005], vis[40005];
long long dp[40005]; 
priority_queue<int>q;
priority_queue<int>q1;
struct node
{
	int wei;
	bool operator <(const node a)const 
    {
	  return wei > a.wei;
	 }
};
priority_queue<node>qq;
void read(int &x)
{
	x = 0; int f = 0; char c = getchar();
	while(c < '0' || c > '9')
   {
   	 if(c == '-') f = 1; c = getchar();
   }
   while(c >= '0' && c <= '9')
   {
   	x = x * 10 + c - '0'; c = getchar();
   }
   if(f) x = -x;
}
int main()
{
//	freopen("cleanup.10.in","r",stdin);
	//freopen("cleanup.out","w",stdout);
	read(n);read(m);
    int ha = sqrt(n);
    for(int i = 1; i <= n; i++)
     {
     	 read(a[i]);
     	if(pre[a[i]]) nex[pre[a[i]]] = i;
     	 pre[a[i]] = i;
     }
    for(int i = 1; i <= n; i++)
    {
    	dp[i] = 1e18;
    if(!nex[i]) nex[i] = n + 1;	
    if(vis[a[i]] == 0 && shu < ha)
        {
        	shu++;
     	 q.push(i);	vis[a[i]] = 1;
         }
	else if(vis[a[i]] == 0)
	   {
	   	qq.push((node){i});	vis[a[i]] = 1;
	   }  
    }
    if(shu == 1) 
    {
    	cout << 1;
	return 0;
    } 
	if(qq.size() == 0) qq.push((node){n+1});
    dp[n+1] = 1e18;dp[1] = 0;
   int ri = 0;
    for(int i = 1; i <= n; i++)
	{
		int ee =  min(shu,ha);
		ri = max(ri,ee);
		 shu = 0;
		  node haha =  qq.top(); int ma;
      ma = haha.wei;//q.pop();	
      for(int j = ee; j >= 1; j--)
		 {
		 	if(i % 2)
			 {
			 int he = q.top();
		 	   q.pop();	
		 	   dp[ma] = min(dp[i] + j *j, dp[ma]);
			   ma = he;
			   shu++;
			   if(he != i) q1.push(he);
			   else  
			   {
			   	node hihi =  qq.top();
			   	int hi =  hihi.wei;
			   	if(nex[i] < hi)q1.push(nex[i]);else {
			   		q1.push(hi);qq.pop();qq.push((node){nex[i]});
			   	}
			   }
			 }
			 else
			 {
			 	int he = q1.top();
		 	   q1.pop();	
		 	   dp[ma] = min(dp[i] + j *j, dp[ma]);
			   shu++;
			   ma = he;
			   if(he != i) q.push(he);
			    else  
			   {
			   	node hihi =  qq.top();
			   	int hi =  hihi.wei;
			   	if(nex[i] < hi)q.push(nex[i]);else {
			   		q.push(hi);qq.pop();qq.push((node){nex[i]});
			   	}
			   }
			 }
		 }
	} 
	cout << dp[n+1];
	return 0;
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值