Vijos 3764 牛奶题

【问题描述】 
       这是一道魔改的奶牛题,所以被称为牛奶题。 
  wdz的牛奶厂里面有N桶排成一排的牛奶,每一桶牛奶都有一个品质值。我们为wdz定义如下的一种操作:选择两桶相邻的品质值相同(设为X)的牛奶,通过奥妙重重的步骤,合并为一桶品质值为X+1的牛奶并放在原来的位置(此时原来的两桶牛奶已经被取走)。 
  wdz想要通过若干次上述操作得到一桶品质值尽可能大的牛奶(即最大化 所有牛奶品质值中的最大值 ),但是wdz日理万机非常繁忙,所以他请你来帮忙求出这个最大值。 
【输入格式】 
       输入的第1行一个非负整数n,意义见题目描述。第2到n+1行共n个正整数,第i个数表示第i桶牛奶的品质值,数组保证最初的品质值均为1到40之间的整数(也就是说在操作之后最大值超过40是被允许的)。 
【输出格式】 
       输出一行一个整数表示答案。 
【输入样例】 









【输出样例】 

【数据范围】 
        对于30%的数据,保证n<=2,000 

        对于100%的数据,保证n<=300,000,最初给出的品质值均为不超过40的正整数。



由于对品质值小的桶进行合并不会限制对品质值大的桶的合并,可以发现合并操作是无后效性的,自然而然地考虑贪心,由小到大合并,每次合并连续相等的一段桶,按长度为奇数/偶数分类讨论。

设合并前桶的品质值为X:

(1)如果连续项长度为偶数(假设有2*k桶),两两合并成k桶,每桶品质值为X+1即可。(每次合并品质值只+1,还能合并的情况之后再考虑)

(2)如果连续项长度为奇数(假设有2*k+1桶),则无论怎样合并,都会单独剩下一桶无法合并,又由于我们是按桶的品质由小到大合并的,则剩下的这一桶以后也不会再被合并了,所以以它作为分隔,其左右两边的桶被划分成两个互不影响的部分。现在的问题是我们合并出了k桶品质值为X+1的桶,我们应该将它们放在分隔桶的右边还是放在左边?事实证明我们事先是不知道该放在哪边的, 所以我们可以选择一种很巧妙的放置方式------我们在左右两边都各放置k桶品质值为X+1, 虽然实际上这是不可能的,但由于分隔桶的划分,这样做对最后的答案并没有影响。

上述操作可以用链表实现,最后遍历所有的桶找出品质值最大的那一桶即可。

另:牛奶桶可能的极大品质值为40+log2(300000),约为58。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=300000+5;
const int inf =1e8;
int N;

struct Chain
{
	int from;
	int v;
	int to;
}Bucket[maxn];

void init()
{
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&Bucket[i].v); 
		Bucket[i].from=i-1;
		Bucket[i].to=i+1;
	}
	Bucket[N+1].from=N; //(1)
	return;
}

void solve()
{
   int i,k,e,mid,cnt,ans;
   for(i=1;i<=57;i++)
   {
   	  for(k=1;k<=N;k=Bucket[k].to)
   	  if(Bucket[k].v==i)
   	  {
   	  	 e=k,cnt=0;
   	  	 while(e<=N&&Bucket[e].v==i) 
		 {
			cnt++;
			e=Bucket[e].to;
		 }
		 e=Bucket[e].from; //如果不写(1)这一步,(2)这一步后e=0,将进入死循环。 
		 if(cnt%2)
		 {
		 	mid=k;
		 	for(int j=1;j<=(cnt-1)/2;j++) mid=Bucket[mid].to;
		 	for(int p=k;p!=mid;p=Bucket[p].to) Bucket[p].v++;
		 	for(int p=e;p!=mid;p=Bucket[p].from) Bucket[p].v++;
		 }
		 else
		 {
		   	mid=k;
		   	for(int j=1;j<cnt/2;j++) mid=Bucket[mid].to;
		   	Bucket[mid].to=Bucket[e].to;
		   	Bucket[Bucket[e].to].from=mid;
		   	mid=Bucket[mid].to;
		   	for(int p=k;p!=mid;p=Bucket[p].to) Bucket[p].v++;
		 }
		 k=e;
   	  }
   }  
   ans=0;
   for(k=1;k<=N;k++)
   ans=max(ans,Bucket[k].v);
   printf("%d\n",ans);
   return;
}

int main()
{
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	init();
	solve();
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值