• Noip模拟考第三题——饥饿游戏


    饥饿游戏

    (hungry.pas/c/cpp)

    【问题描述】

    Chanxer饿了,但是囊中羞涩,于是他去参加号称免费吃到饱的“饥饿游戏”。

    这个游戏的规则是这样的,举办者会摆出一排 个食物,希望你能够一口就吃完。

    然而Chanxer却不这么想,比起数量,他更看重质量,对于没一个食物,都会有一个喜爱值,Chanxer希望能够吃到最美味的那一段。

    注意,因为只能吃一口,因此Chanxer只有一次机会,即只能吃连续的一段,因为他的嘴够大,因此无论这一段有多长,他都可以吃到。

    不过Chanxer是一个喜新厌旧的男人,对于多个喜爱值相同的食物,他会只算作一次,比如: ,2出现了3次,但只算一次,于是这一段的喜爱值是 。

    现在Chanxer对于吃哪一段有很多想法,每一个想法他都想要试试,但是由于只有一次机会,所以他想要你先帮他算算这些他想要吃的段带给他的喜爱值是多少。

     

    【输入】

    输入文件名为hungry.in,共 行。

    第一行包含一个整数 n。

    接下来一行,n个整数,第i个数表示第i个食物的喜爱值a。

    接下来一行包含一个整数 m ,表示Chanxer的询问个数。

    接下来 m行,每行两个数 l,r,表示Chanxer要询问这个区间的喜爱值【注意是指这个区间内的最优值】。

     

    【输出】

    输出文件名为hungry.out,共M行。

    每行一个整数表示此次询问的答案。

     

    【输入输出样例】

    hungry.in

    hungry.out

    9

    4 -2 -2 3 -1 -4 2 2 -6

    3

    1 2

    1 5

    4 9

    4

    5

    3

     

    【数据说明】

    对于 的数据满足,n,m<=100;

    对于 的数据满足, n,m<=100,000,|ai|<=100,000。

     

     

    【分析】这题当场最高30分....结果还没有solution ! 无奈只能Orz std的代码了... 然而std的代码高能——使用zkw线段树....

      多亏我当初还是看过一些zkw线段树...于是拉上Zero去黑板上进行模拟运算....画了两棵巨大的线段树...终于半猜半模拟的弄懂了std的思路,爽啊!

      

      正解的具体思路:

      1.发现每次询问[L,R]中的最优值,无非是另一个直接取的一个小的区间 [l,r]满足 l>=L && r<=R。

      2.因为相同的数在一段区间内只会加一次,所以每次在两个相邻的相同的数之间加入这个数。

      

      【经过这两个比较核心的思路】

      STD设计了一个线段树来每次获得最优值。

      每次移动右端点,每次将右端点的值添加进线段树更新一段区间[pre[i]+1,i]的最优值。

      其中每个叶子节点带有两个属性:g[i],f[i]

        g[i]表示从i到当前枚举的最右端点能获得的最优值[但是要求必须选择i这个节点作为左端点],右端点是不固定的。

        f[i]表示从i到当前枚举的最右端点的总和[要求左端点为i,右端点为当前枚举的最右端点],左右端点都是固定的。

        所以可以想到g[i]每次都是由f[i]更新的。

      但是每次这样传到每个叶节点太慢了,于是利用线段树的方法,每次给一段区间添加一个值时,使用lazy_tag,也就是说每个非叶节点多了两个属性: gd[i],fd[i]

      而每个非叶节点还表示这个区间内元素,所以它们也具有一个g[],f[]的意义

        g[i]表示以i能控制的区间内的任意元素作为左端点到当前枚举右端点能获得的最优值 [即i控制区间内g[]的最优值]

        f[i]表示以i能控制的区间内的任意元素作为左端点,当前枚举右端点作为区间右端点能获得的最优值 [即i控制区间内f[]的最优值]

        gd[i]表示对于i能控制的区间内曾经可以添加的最多的值 [也就是没来得及下传的最好的一次 tag]

        fd[i]表示对于i控制的区间内到现在为止累积的值 [也就是每次累加得到的值而已]

        所以也可以想到gd[i]是由fd[i]更新的。

      可以发现每次再添加tag的时候需要下传一些 tag,和线段树一样。

      下传的时候更新下面节点的tag,gd[]由gd[fa]+fd[]更新,fd变成fd[fa]+fd[]

      下传时还要更新下面节点的g[]和f[],其中g[]由gd[fa]+f[]更新,f[]变成fd[fa]+f[]

      

      现在每个答案就比较好求了,因为代表的意义有当前右端点存在的影响,需要离线处理,将询问按右端点排序,然后枚举右端点,如果右端点与某个r重合,查询[l,r]内的最优g[]即可。

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<algorithm>
     4 
     5 using namespace std;
     6 
     7 typedef long long ll;
     8 const int nmax=100000,tmax=1<<18,shift=nmax;
     9 
    10 int n,m;
    11 int M=1,H=1;
    12 int a[nmax+18],last[nmax*2+18],pre[nmax+18];
    13 ll f[tmax+18],g[tmax+18],fd[tmax+18],gd[tmax+18],ans[nmax+18];
    14 int l[nmax+18],r[nmax+18],p[nmax+18];
    15 
    16 bool cmp(int i,int j){
    17     return r[i]<r[j];
    18 }
    19 
    20 inline int max(int a,int b) {return a>b?a:b;}
    21 
    22 inline void update(ll &a,ll b){if(a<b) a=b;}
    23 
    24 inline void take(int i){
    25     f[i]=max(f[i<<1],f[i<<1|1]),g[i]=max(g[i<<1],g[i<<1|1]);
    26 }
    27 
    28 inline void give(int i,int j){
    29     //fd[i]和gd[i]在于之前添加时可能没有顾及到下面的节点,于是偷懒加上tag表示这里可以下传的数目;
    30     //其中gd[i]表示的是历史上给i的子树能添加的最大值,fd[i]表示总计能给它们添加多少 
    31     update(g[i],f[i]+gd[j]),f[i]+=fd[j];
    32     update(gd[i],fd[i]+gd[j]),fd[i]+=fd[j];
    33 }
    34 
    35 void pd(int i){
    36     for(int h=H-1,j;h;--h)//枚举i的所有祖先将它们的 tag下传 
    37         if(fd[j=i>>h] || gd[j])
    38             give(j<<1,j),give(j<<1|1,j),fd[j]=gd[j]=0;
    39 }
    40 
    41 void add(int l,int r,int x){
    42     //f[i]表示计算从i到当前枚举值的整段之和
    43     //g[i]表示必须选择i为左端点的情况下,到当前枚举值间的最优值。
    44     //若g[i]不为叶子节点,则g[i]表示以其代表区间内的任意元素作为左端点的最优值
    45     //f[i]不为叶节点时等于示以其代表区间内的任意元素作为左端点且当前枚举点为右端点时的最优值 
    46     for(pd(l+=M-1),pd(r+=M+1)/*传递(L,R)之间的所有标记*/;l^r^1;take(l>>=1),take(r>>=1)/*这里只能传到 L,R会合之下的两端*/){
    47         
    48         //因为不能每次传到底,所以给包含区间加上tag标记 
    49         if(~l&1)                                    
    50             update(g[l^1],f[l^1]+=x),update(gd[l^1],fd[l^1]+=x);
    51         if(r&1)
    52             update(g[r^1],f[r^1]+=x),update(gd[r^1],fd[r^1]+=x);
    53     }
    54     for(int tmp1,tmp2;l>>=1;){//所以这里需要往上带到根节点去 
    55         tmp1=f[l],tmp2=g[l];
    56         take(l);
    57         if(f[l]==tmp1 && g[l]==tmp2) break;
    58     }
    59 }
    60 
    61 ll getmax(int l,int r){
    62     //每次从l,r中选任意一个节点作为左端点,右端点不固定时的最优值即为答案。 
    63     ll Ans=0;
    64     for(pd(l+=M-1),pd(r+=M+1);l^r^1;l>>=1,r>>=1){
    65         if(~l&1) update(Ans,g[l^1]);
    66         if(r&1) update(Ans,g[r^1]);
    67     }
    68     return Ans;
    69 }
    70 
    71 int main(){
    72     freopen("hungry.in","r",stdin);
    73     freopen("hungry.out","w",stdout);
    74     scanf("%d", &n);
    75     while(n>=M-2) M<<=1,++H;
    76     for(int i=1;i<=n;i++)
    77         scanf("%d",a+i),pre[i]=last[a[i]+shift],last[a[i]+shift]=i;//pre[i]表示上一个和它相同数所在的位置 
    78     scanf("%d",&m);
    79     for(int i=1;i<=m;i++)
    80         scanf("%d%d",l+i,r+i),p[i]=i;
    81     sort(p+1,p+m+1,cmp);//给右区间排序,因为g[x]表示的是从x开始,必须选x到当前枚举值之间的最优值,而当前枚举值相当于区间的右端点 
    82 
    83     for(int i=1,j=1;i<=n && j<=m;i++)
    84         for(add(pre[i]+1,i,a[i])/*往两个相同的数之间加,这样可以使重复的数只加一次*/;j<=m && r[p[j]]<=i;j++) 
    85                ans[p[j]]=getmax(l[p[j]],r[p[j]]);
    86     
    87     for(int i=1;i<=m;i++)
    88         printf("%I64d
    ",ans[i]);
    89     return 0;
    90 }
    View Code

      Orz 人类智慧的伟大...+数据结构的巧妙结合...

  • 相关阅读:
    Listener:监听器
    go语言基础数据类型
    Envoy基于文件系统的RDS动态配置
    git常用操作
    c语言 assert(转)
    浅谈可重入函数与不可重入函数【转】
    静态全局变量和全局变量的区别(转)
    c语言 排序(转)
    c语言刷 队列题记录
    c语言刷 链表题记录
  • 原文地址:https://www.cnblogs.com/Robert-Yuan/p/4860206.html
Copyright © 2020-2023  润新知