[jzoj]3499. 【NOIP2013模拟联考15】人类基因组(genes) (单调队列、前缀和、线段树解一题)

来源:互联网 发布:网络培训管理系统 编辑:程序博客网 时间:2024/05/17 08:51

Link

https://jzoj.net/senior/#contest/show/1947/0

Problem

给定一个序列A[1..n],每次将前i个数移到末尾时会产生成一个新序列,如果这样产生的n个新序列当中的某序列的任意前缀和大于0,则此序列称为合法序列,求其个数.

Data constraint

对于30%的数据,满足1<=N<=5,000
对于50%的数据,满足1<=N<=10,000
对于100%的数据,满足1<=N<=1,000,0001,000<=Ai<=1,000

Solution

Method-1

我们把序列复制一遍,对应的a[i+n]=a[i].

那么继而求其前缀和,我们发现,每次枚举一个点i,表示断开点.

那么只需求出sum[i]sum[i+n]内的最小前缀和即可.

即满足min{sum[i+p]}>sum[i1].

显然可用线段树或树状数组或优先队列来维护.

Method-2

我们维护一个单调队列.

找出当前所有前缀和的最小值,并从最小值往右取次小值,次次小值……

即设d[i]表示Sum[1..n]中的第i小值,b[i]表示其对应的下标.

保证d[1]=min{sum[1..n]}b[i]<b[i+1].

c[i]表示其应该被加上的对应前缀和.(此处有点难理解)

每次枚举一个截取点i表示,表示把前i1个数扔到末尾.

对于当前最小值d[1],其若对应下标b[1]不小于i,则可以继续”使用”,否则单调的增加j值,保证b[j]>=i

我们设一个可以继续“使用”的最小值为min.

如果mins[i1]再加上对应的前缀和仍大于0,则当前截点i所产生的新序列肯定为合法序列.

对应的前缀和的意思实际上是为每次被扔到末尾的数做准备的.

Code-2

var        a,sum,d,b,c:array[0..2000000] of longint;        n,i,j,ans,t:longint;begin        assign(input,'genes.in'); reset(input);        assign(output,'genes.out'); rewrite(output);        readln(n);        fillchar(d,sizeof(d),$7f);        d[0]:=1;  //序列长度        for i:=1 to n do        begin                read(a[i]);                sum[i]:=sum[i-1]+a[i]; //前缀和                while (sum[i]<d[d[0]]) and (d[0]>0) do dec(d[0]);                inc(d[0]);                d[d[0]]:=sum[i]; //记录最小值,及从最小值往后的次小值,从次小值往后的次次小值……                b[d[0]]:=i;      //记录对应下标                c[d[0]]:=0;      //记录对应的前缀和,只要没被扔到末尾的数,对应前缀和为0.        end;        j:=1;        for i:=1 to n do        begin                while (b[j]<i) and (j<=d[0]) do inc(j);                if d[j]-sum[i-1]+sum[c[j]]>=0 then                        inc(ans);                t:=sum[n];                while (t<d[d[0]]-sum[i]+sum[c[d[0]]]) and (d[0]>0) do dec(d[0]);                inc(d[0]);                d[d[0]]:=t;                b[d[0]]:=i+n;                c[d[0]]:=i;        end;        writeln(ans);        close(input); close(output);end.

Method-3

但其实对于Method2的做法,还有更简洁实现的方法.

我们把a复制一遍,即使a[i+n]=a[i],并求出所有a[1..2n]的前缀和.

那么每次我们就可以通过一个单调队列来判断新增的值的打小了,不需要再记录那么多的数.

Code-3

uses math;var        a,sum:array[0..2000000] of longint;        d:array[0..5000000] of longint;        n,i,ans,l,r:longint;begin        assign(input,'genes.in'); reset(input);        assign(output,'genes.out'); rewrite(output);        readln(n);        for i:=1 to n do        begin                read(a[i]);                a[i+n]:=a[i];        end;        for i:=1 to n*2 do                sum[i]:=sum[i-1]+a[i];        for i:=1 to n-1 do        begin                while (r>0) and (sum[i]<sum[d[r]]) do dec(r);                inc(r);                d[r]:=i;        end;        l:=1;        for i:=1 to n do        begin                while (r>l) and (sum[i+n-1]<sum[d[r]]) do dec(r);                inc(r);                d[r]:=i+n-1;                while (r>l) and (i>d[l]) do inc(l);                if sum[d[l]]-sum[i-1]>=0 then inc(ans);        end;        writeln(ans);        close(input); close(output);end.

Method-4

不用单调队列.

先求出前缀和,设为S.
f[i]表示S[1..i]的最小值对应的下标.
g[i]表示S[i..n]的最小值所对应的下标.

那么每次,枚举一个断点i,只需求[i..n]中的前缀和最小值和[1..i]这些被移到末尾所产生的新的前缀和的最小值,判断即可.

其实这与Method2Method3的想法是一致的,只不过处理起来更简洁明了.

Code-4

var        f,g,s:array[0..1200000]of longint;        n,i,ans,x:longint;begin        assign(input,'genes.in'); reset(input);        assign(output,'genes.out'); rewrite(output);        readln(n);        for i:=1 to n do        begin                read(x);                s[i]:=s[i-1]+x;        end;        f[1]:=1;        for i:=2 to n do                if s[i]<s[f[i-1]] then f[i]:=i else f[i]:=f[i-1];        g[n]:=n;        for i:=n-1 downto 1do                if s[i]<s[g[i+1]] then g[i]:=i else g[i]:=g[i+1];        if s[f[n]]>=0 then ans:=ans+1;        for i:=2 to n do                if (s[f[i-1]]+s[n]-s[i-1]>=0) and (s[g[i]]-s[i-1]>=0) then                        ans:=ans+1; //前一个判断条件是判断被调走的,即[1..i-1]中新产生的前缀和的值,后一个条件则是继续判断没被调走中的最小前缀和值.        writeln(ans);        close(input);close(output);end.
0 0