【BZOJ4576】【BZOJ4580】【Usaco2016 Open】262144 贪心

XJB贪心做法的奇妙威力。。。

源程序只用改一下数组大小的双倍经验!人生第一个(暂时的)RK1截图留念。

题目大意:给定一个长度为n(n<=2^18)的序列,初始元素值均为1到40之间的整数,每次操作可以将两个相邻的并且大小相同的正整数替换成一个比原数大一的正整数。要求最大化最终数列中的最大值。并输出最大值

例如给定一个序列: 1,1,1,2   我们可以先将第二个1和第三个1合并替换成2 。序列变成:1,2,2。再将两个二合并,序列变成1,3.由此我们得到最大值3.

当然我们也可以将第一个1和第二个1合并,序列变成2,1,2.然而这样我们就不能再合并了,最大值只有二,不是最优情况。

————————————只想看题目意思不想看题解的小伙伴请就此打住————————————

首先,贪心的基本思路是从最小的1开始,将能合并的项先全部合并,这样我们在合并更大项的时候就不用考虑比他小的项能否会对当前的选择有影响。

不妨设我们现在正在合并数字X

情况一: 有一段偶数个(不妨设为2*k+个)连续的X,这种情况不需要怎么思考,直接变成k个X+1。(不要考虑4个X变成X+1这些问题,那是我们接下来合并X+1的时候处理的问题)

情况二:有一段奇数个(不妨设为2*k+1个)连续的X,这种情况处理比较巧妙,我们先将左边k个变成X+1,把最中间的数变成-1(起分割作用) ,再将右边的k个数变成X+1.

下面是解释时间:

首先,假设我们有奇数个连续的X,不管我们怎么合并,这一段左边的数字将永远不可能和这一段右边的数字合在一起(此时我们已经将小于X的数字处理完了),因为一定会有一个单的X卡在中间,这也意味着,左右两边变成了两个互不相干的子问题。

本来我们应该纠结新合成的k个X+1应该放在左边还是右边,但是由于题目只要求最大化最大值,我们不妨先同时放在两边,等某一边出现了最大值我们再将另外一边的k个X+1删掉(只是再思考的过程中删掉),对结果和规则都没有影响。

由于上面的话我自己都读不懂。。。我们举一个浅显易懂的例子

3,2,1,1,1,2,3,4

我们现在讨论中间三个1的合并方式,我们目前不知道合并到左边还是右边最优,所以我们在两边同时试一下:

3,2,2,1,2,2,3,4

以1 作为分隔符划分成两个子问题,分别合并后变成

4,1 ,5

右边比较大,所以我们发现刚才放在右边比较好,把左边还原

3,2,1,5

但是我们只要求最大值,所以不用还原,输出来就是了

同时为了方便,我们可以把分隔符统一成-1即可

/************************************************************** 
    Problem: 4576 
    User: RicardoWang 
    Language: C++ 
    Result: Accepted 
    Time:432 ms 
    Memory:3616 kb 
****************************************************************/
  
#include<cstdlib> 
#include<cstdio> 
#include<iostream> 
#include<cstring> 
#include<cmath> 
#include<algorithm> 
#include<queue> 
#include<vector> 
using namespace std; 
#define maxn 300005 
int A[maxn],n,B[maxn],cnt,ans; 
void _read(int &x) 
{ 
    char ch=getchar(); bool flag=false; x=0; 
    while(ch<'0' || ch>'9'){if(ch=='-')flag=true; ch=getchar();} 
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} if(flag)x=-x; return ; 
} 
void Init() 
{ 
    _read(n); for(int i=1;i<=n;i++)_read(A[i]); ans=A[1]; return ; 
} 
void work() 
{ 
    cnt=0;  bool flag; int p,q; 
    n++; A[n]=-1; 
    for(int now=1;;now++) 
    { 
        flag=false; cnt=0; 
        for(int i=1;i<=n;i++)if(A[i]>=now){flag=true; break;} 
        if(flag)ans=now; else break; 
        p=n+1; q=0; 
        for(int i=1;i<=n;i++) 
        { 
            if(A[i]!=now) 
            { 
                if(p<=q) 
                { 
                    if((q-p+1)%2==0) 
                    { 
                        for(int j=1;j<=(q-p+1)/2;j++) 
                        { 
                            B[++cnt]=now+1; 
                        } 
                    } 
                    else
                    { 
                        for(int j=1;j<=(q-p)/2;j++) 
                        { 
                            B[++cnt]=now+1; 
                        } 
                        B[++cnt]=-1; 
                        for(int j=1;j<=(q-p)/2;j++) 
                        { 
                            B[++cnt]=now+1; 
                        } 
                    } 
                    p=n+1;q=0; 
                } 
                if(!(A[i]==-1 && cnt > 0 && B[cnt]==-1))B[++cnt]=A[i]; 
            } 
            else 
            { 
                p=min(p,i); q=max(q,i); 
            } 
        } 
        B[++cnt]=-1; 
        for(int i=1;i<=cnt;i++)A[i]=B[i]; n=cnt; 
    } 
    printf("%d\n",ans); 
    return ; 
} 
int main() 
{ 
    //freopen("in.txt","r",stdin); 
    Init(); 
    work(); 
    return 0; 
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值