チェアマンツリーの質問とまとめ

月が終わると学期の終わりが忘れられるのではないかと恐れて、Konjacは波を要約します。


最初にテンプレートの質問に来てください:静的間隔でk番目に小さいものを見つけてください

https://www.luogu.com.cn/problem/P3834

ボードの理解:

ボードブログ

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
LL a[maxn],b[maxn],root[maxn];
LL tot=0;
struct Tree{
    LL l,r,rt,sum;
}tree[maxn*20];
LL update(LL pre,LL l,LL r,LL x){
    LL rt=++tot;
    tree[rt].l=tree[pre].l;
    tree[rt].r=tree[pre].r;
    tree[rt].sum=tree[pre].sum+1;
    LL mid=(l+r)>>1;
    if(l<r){
        if(x<=mid){
            tree[rt].l=update(tree[pre].l,l,mid,x);
        }
        else tree[rt].r=update(tree[pre].r,mid+1,r,x);
    }
    return rt;
}
LL query(LL v,LL u,LL l,LL r,LL k){
    if(l==r) return l;
    LL x=tree[tree[u].l].sum-tree[tree[v].l].sum;
    LL mid=(l+r)>>1;
    if(x>=k){
        return query(tree[v].l,tree[u].l,l,mid,k);
    }
    else return query(tree[v].r,tree[u].r,mid+1,r,k-x);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;cin>>n>>m;
  for(LL i=1;i<=n;i++){
      cin>>a[i];
      b[i]=a[i];
  }
  sort(b+1,b+1+n);
  LL siz=unique(b+1,b+1+n)-b-1;///返回不同的数字的个数
  for(LL i=1;i<=n;i++){
    LL x=lower_bound(b+1,b+1+siz,a[i])-b;
    root[i]=update(root[i-1],1,siz,x);
  }
  while(m--){
    LL l,r,k;cin>>l>>r>>k;
    LL t=query(root[l-1],root[r],1,siz,k);
    cout<<b[t]<<endl;
  }
return 0;
}

以下は、静的間隔の質問の適用です。

1.ツリー上の特定のパスでk番目に小さいポイントを見つけます(LCA +チェアマンツリー)

木を頼りに

 SPOJ-COT  

詳細


2.ツリー内の2ポイントパスの異なる色の数を見つけます(ツリー上のmoチーム)

木を頼りにII

接続-COT2 

詳細な説明ここ


3.間隔内の​​k以下の数)[議長ツリー+2点]

詳細


4.チェアマンツリーメンテナンスラインセグメントツリー間隔の変更(永続的なマーク)

月へ

 HDU-4348 

アイデア:


Lautisticycから

レイジーマークはダウンロードせずに1と5の2点に保持し、クエリ時に追加します。

たとえば、6ノードの値を照会するとします。

ノード1から開始して右に移動し、そのレイジーマーク33を追加します。

5番目のノードに到達したら、左に移動してレイジーマーク55を追加します。

最後に、6ノードに達すると、その現在の値は、元の値と追加されたばかりのレイジーマークの合計になります。

間隔クエリの場合、間隔要素の数にレイジーマークを掛ける必要があります。(レイジーマークの定義を参照)


個人的な理解:実際、プロセスは次のようになります。議長ツリーのレイジーマークがダウンロードされて共有ノードに影響を与えるのを防ぐために、マークは永続的になります。

つまり、一定期間、レイジーを維持するだけでダウンロードはしませんが、push_upの維持に注意を払います。

更新では、変更する間隔にタグを付けます。ヒットした後、このポイントの合計は更新されません。ヒットした後、push_up(父親の現在の間隔の合計は、左右の息子だけでなく、左右の息子の左右の息子のタグ*によっても追加されます。対応する間隔)。

次のツリーを作成するには、前のツリーにコピーします。このツリーにタグを付けるときは、今回はこのポイントタグ+ =にタグを付け(前のツリーをコピーしたため)、同じ方法でpush_upします。

クエリの場合、クエリプロセス中の結果間隔に対する現在のツリー間隔のタグの累積の影響に注意します。つまり、LL cnt = tree [now] .add *(QR-QL + 1);

この種のクエリでは、3種類の間隔クエリを簡単に操作できます。これらはすべて左側のサブツリーにあり、すべて右側のサブツリーにあり、間隔にまたがっています。間隔を超える場合は、クエリ間隔を変更する必要があることに注意してください(QR-QL + 1のサイズを変更する必要があるため、変更しないと、毎回最初の大きな間隔が乗算されます)。

対応する完全なカバレッジ間隔を見つけます。この間隔(この間隔の長さ)のadd *を追加することを忘れないでください。その後、蓄積します

コードが理解しやすい

hduの質問の場合、この質問には複数のグループが必要であり、合計に関連する質問にはlonglongが必要であることに注意してください。Luoguは複数のグループを必要としません。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+50;
typedef int LL;
inline LL read()
{	LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL root[maxn],tot=0;
struct Tree{
    LL lson,rson,add;long long sum;
   /// LL l,r;///节点代表的左右区间
}tree[maxn<<5];
LL a[maxn];
LL build(LL l,LL r){
   LL rt=++tot;
   ///tree[rt].l=l;tree[rt].r=r;
   if(l==r) {
     tree[rt].sum=a[l];
     return rt;
   }
   LL mid=(l+r)>>1;
   tree[rt].lson=build(l,mid);
   tree[rt].rson=build(mid+1,r);
   tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum;
   return rt;
}
LL update(LL pre,LL l,LL r,LL ql,LL qr,long long val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(ql<=l&&qr>=r){
        tree[rt].add+=val;///打lazy标记
        ///tree[rt].sum+=val*(tree[rt].r-tree[rt].l+1);不能先加
        return rt;
    }
    LL mid=(l+r)>>1;
    if(ql<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,ql,qr,val);
    }
    if(qr>mid){
        tree[rt].rson=update(tree[pre].rson,mid+1,r,ql,qr,val);
    }
    LL add1=tree[tree[rt].lson].add;
    LL add2=tree[tree[rt].rson].add;
    tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum+add1*(mid-l+1)+add2*(r-(mid+1)+1);
    return rt;
}
long long query(LL now,LL l,LL r,LL ql,LL qr){
    if(ql<=l&&qr>=r){
        return tree[now].sum+tree[now].add*(r-l+1);
    }
    long long cnt=tree[now].add*(qr-ql+1);
    LL mid=(l+r)>>1;
    if(qr<=mid) return cnt+query(tree[now].lson,l,mid,ql,qr);
    else if(ql>mid) return cnt+query(tree[now].rson,mid+1,r,ql,qr);
    else{
        return cnt+query(tree[now].lson,l,mid,ql,mid)+query(tree[now].rson,mid+1,r,mid+1,qr);///注意由于标记永久化,这里往下搜的ql,qr要变(因为lazy乘要乘长度)
    }
}
int main(void)
{
  char op[10];
  LL n,m;
  while(~scanf("%d%d",&n,&m)){
    LL TIME=0;
    LL tot=0;
    for(LL i=1;i<=n;i++){
        a[i]=read();
    }
    root[0]=build(1,n);
    for(LL i=1;i<=m;i++){
        scanf("%s",op);
        if(op[0]=='C'){LL l,r,d;l=read();r=read();d=read();TIME++;root[TIME]=update(root[TIME-1],1,n,l,r,d); }
        if(op[0]=='Q'){LL l,r;l=read();r=read(); long long ans=query(root[TIME],1,n,l,r);printf("%lld\n",ans); }
        if(op[0]=='H'){LL l,r,t;l=read();r=read();t=read();long long ans=query(root[t],1,n,l,r);printf("%lld\n",ans); }
        if(op[0]=='B'){LL t;t=read();TIME=t;}
    }

  }
return 0;
}

5.間隔内の​​異なる数の数を見つけます

この問題は実際にはTeamMoで解決できますが、nsqrt(n)がスタックしている場合はどうなりますか?まだマスターする必要があります。

P1972 [SDOI2009] HHのネックレス(最近誰かがこの質問に行き詰まったと思いますが、とにかく合格しませんでした。議長の木もこの質問に行き詰まる可能性があるようです)

Dクエリ

 コネクター-DQUERY

アイデア:重量が比較的大きい場合は離散化します。

スキャンシーケンスは、持続可能なラインセグメントツリーを確立します。

各ポジションのチェアパーソンツリーを作成します。posは、単一点の変更の位置に対応します。+ = valを見つけます。

表示されない場合は、現在の位置を割り当てて検索します。それ以外の場合は、最初にroot [i-1]から最後の数値の寄与を差し引き、次にこの数値の寄与を加算します。

同時に、各要素の最後の出現位置が維持されます。

クエリを実行する場合:各議長ツリーは、1からi(現在の位置)までの異なる数値の数を維持します。

それを見つけたら、ルート[r]で探します。ツリー> = lの間隔番号は、1-rのl〜rの寄与です。


コードを組み合わせてシミュレートする手段は次のとおりです。

現在の加重ラインセグメントツリーは、1極(リサイクルr)にいくつの異なる数があるかを維持します。

たとえば、シーケンス1 2 3 4の場合、最後の加重ラインセグメントツリーには4つの1があり、1〜4の4つの異なる番号を表します。2〜4のいくつかを探している場合は、最後のツリーの間隔の合計を検索し、それが2であり、対応する答えが2であることを確認します。

たとえば、シーケンス1 2 2 2 3 4. 3番目の加重ラインセグメントツリーでは、最初に2番目のツリーからpos = 2(-1)の影響を差し引きました。このとき、一時的な加重ラインセグメントツリーリーフノード2の値は0です。次に、3番目のツリーにpos = 3を追加します。このとき、3番目のリーフノード3の重みは1です。このとき、3番目の加重線セグメントツリーの1の合計は2です。これは、1〜3に2つの異なる数値があることを意味します。3番目のツリーの[2、3]にいくつの異なる数があるかを見つけた場合、間隔の合計は1です。l> = 2の場合。

もっと賢い

コード:ここ


6.間隔mexを見つける

P4137Rmq問題/ mex

ダークエクスプロージョン-3585

アイデア:とても賢い。まず、値が大きすぎるため、離散化します。シーケンスの場合、考えられる答えはa [n + 1]、0であるため、離散化するときにa [i] +1を離散化に入れる必要があります。 b []配列。【答えが出てくるかもしれないので】

離散化と重複排除後の新しいサイズでは、チェアマンツリーが確立されます。

離散化された数値については、加重線セグメントツリーが出現順に確立されます。この間隔でこの番号の最新の位置を維持します。次に、push_upは各ノードの最小位置を維持します。

次に、i番目の加重ラインセグメントツリーの場合、root [i [にあります。つまり、1-iでは、各番号の最新の位置にあります。

たとえば、1 2 3 4 1.4行目のセグメントツリーと5行目のセグメントツリーの場合。4番目に早い番号の位置はpos = 1であるため、root [4]の左の息子の最小値は1です。この時点で[2,4]に移動すると、左のサブツリーのノードがl = 2未満であるため、次のようになります。左側のサブツリーは探し続け、最終的に1を返します

次に、5行目のセグメントツリーでは、番号1の葉の重みが5に変更され、root [5]の左の息子の最小値は1ではなく2になります。このとき、[2,5]を探し、次に事前に離散化され、表示されない番号5を表し、その重みが0であるリーフノードが見つかるまで、最初に右の息子に移動します。2341に表示されない最小の番号に対応するのは0です。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=4e5+1000;
typedef long long LL;
LL a[maxn],b[maxn];///离散化
LL cnt=0;
struct Tree{
    LL lson,rson,val;
}tree[maxn<<5];///nlogn
LL root[maxn];
void push_up(LL p){
    tree[p].val=min(tree[tree[p].lson].val,tree[tree[p].rson].val);
}
///k表示输入的数的大小,val表示这次进来的这个数的pos
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++cnt;///先复制
    tree[rt].val=tree[pre].val;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r) {///叶子节点返回
        tree[rt].val=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid) tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL rt,LL l,LL r,LL x){
    if(l==r) {
        return b[l];
    }
    LL mid=(l+r)>>1;
    if(x<=tree[tree[rt].lson].val){
        return query(tree[rt].rson,mid+1,r,x);
    }
    else return query(tree[rt].lson,l,mid,x);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  ///freopen("P4137_2.in","r",stdin);
  ///freopen("myoutput2.out","w",stdout);
  LL n,m;cin>>n>>m;
  LL idx=0;
  b[++idx]=0;

  for(LL i=1;i<=n;i++) {
    cin>>a[i];
    b[++idx]=a[i];b[++idx]=a[i]+1;///注意离散化的方式
  }
  sort(b+1,b+1+idx);
  LL siz=unique(b+1,b+1+idx)-b-1;
  for(LL i=1;i<=n;i++){
    LL k=lower_bound(b+1,b+1+siz,a[i])-b;
   /// debug(k);
    root[i]=update(root[i-1],1,siz,k,i);

  }
  while(m--){
    LL l,r;cin>>l>>r;
    LL ans=query(root[r],1,siz,l);
  ///  debug(ans);
    cout<<ans<<endl;
  }
return 0;
}

7.間隔内で> = kより大きい最初の出現数を見つけます

P3567 [POI2014] KUR-宅配便

CF840Dデスティニー

他の人によるフェージングの説明:https//www.luogu.com.cn/problem/solution/CF840D


まず、トピックを実行する必要があります。\ texttt {P3567 [POI2014] KUR-Couriers} P3567 [POI2014] KUR-Couriers

その質問はK = 2K = 2ですが、この質問はK \ in [2,5]K∈[2,5]です。

どうやるか?その質問の実践を考慮して、議長ツリーは[1、x] [1、x]の範囲内にある数を維持します。

次に、ラインセグメントツリーが2つに分割されます。最初に、左側のサブツリーの合計が\ text {sum} _ {lson} \ leq \ frac 12 \ text {sum}sumlson≤21sumの場合、\ text {sum} _ {rson} \ geq \ frac 12 \ text {sum}sumrson≥21sum。したがって、そのサブツリーに移動します。

この質問をどうするか?私たちは、に頼る暴力。\ {テキストのsu} \ {テキストメートル} _ {LSON} \ GEQ \ FRAC 1K \ {テキストのsu} \ {テキスト}メートルsumlsonの≥K1の合計は、左のサブツリーに進みます。左側のサブツリーに回答がない場合(つまり、\ geq \frac1K≥K1のみの場合)、右側のサブツリーに移動します。

時間の複雑さが間違っているように見えますが、実際にはO(nK \ log_2 n)O(nKlog2 n)です。

以下は複雑さの分析です。

\ text {su} \ text {m} _ {lson} \ geq x \ frac 1K \ text {su} \ text {m}(x \ in N ^ *)sumlson≥xK1sum(x∈N ∗)、次に\ text {su} \ text {m} _ {rson} \ leq(Kx)\ frac {1} {K} \ text {su} \ text {m}sumrson≤(K−x)K1合計。左側のサブツリーには最大xxの回答があり、検索は最大x \ log_2nxlog2 n回です。同様に、右側は最大(Kx)\ log_2n(K-x)log2n回検索されます。


個人的な理解:実際には、間隔内の数の出現数を維持することであり、ノードの数が非常に多いためですが、均等に分割できるため、ノード内のリーフノードの出現数がk以上であることを意味するわけではありません。それで私たちはそれを激しく見つけに行きました。左の息子の頻度が> = kの場合は、激しく検索します。見つかった場合は検索を終了します。見つからない場合は、右の息子で十分かどうかによって異なります。十分な数がある場合は、検索を終了します。

P3567 [POI2014] KUR-宅配便

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=5e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
LL a[maxn],root[maxn];
struct Tree{
    LL lson,rson,sum;
}tree[maxn<<5];
void push_up(LL p){
    tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    ///tree[rt]=tree[pre];
    tree[rt].sum=tree[pre].sum;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r){
        tree[rt].sum+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
    LL sum=tree[now].sum-tree[pre].sum;
    if(sum<=x) return -1;
    if(l==r) return l;
    LL mid=(l+r)>>1;
    LL ans=-1;
    if(tree[tree[now].lson].sum-tree[tree[pre].lson].sum>x){
        ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
        if(ans>0){
            return ans;
        }
    }
    if(tree[tree[now].rson].sum-tree[tree[pre].rson].sum>x){
        ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
        if(ans>0){
            return ans;
        }
    }
    return ans;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;n=read();m=read();
  for(LL i=1;i<=n;i++) a[i]=read();
  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i],1);
  }
  while(m--){
    LL l,r;l=read();r=read();
    LL k=(r-l+1)/2;
    LL ans=query(root[r],root[l-1],1,n,k);
    if(ans==-1){
        puts("0");
    }
    else printf("%lld\n",ans);
  }
return 0;
}

CF840Dデスティニー

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=3e5+1000;
typedef long long LL;
inline LL read()
{
	LL x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
LL tot=0;
struct Tree{
    LL lson,rson,val;
}tree[maxn<<5];
LL a[maxn],root[maxn];
void push_up(LL p){
    tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt].val=tree[pre].val;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r){///叶子递归边界
        tree[rt].val+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
    if(tree[now].val-tree[pre].val<=x) return -1;
    if(l==r){
        return l;
    }
    LL res=1e18;
    LL mid=(l+r)>>1;
    if(tree[tree[now].lson].val-tree[tree[pre].lson].val>x){
      LL ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
      if(ans>0){
         res=min(res,ans);
      }
    }
    if(tree[tree[now].rson].val-tree[tree[pre].rson].val>x){
      LL ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
      if(ans>0){
         res=min(res,ans);
      }
    }
    return res;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;
  n=read();m=read();
  for(LL i=1;i<=n;i++){
      a[i]=read();
  }
  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i],1);
  }
  while(m--){
     LL l,r,k;l=read();r=read();k=read();
     LL want=(r-l+1)/k;
     LL shu=query(root[r],root[l-1],1,n,want);
     if(shu==1e18) puts("-1");
     else printf("%lld\n",shu);
  }
return 0;
}

8.チェアマンツリーを構築するための2つのポイント

サインオンフェンス CodeForces-484E

P2839【全国研修チーム】中

比較ルーチン:

説明は次のとおりです。

Okasaki_Ushio

 

中央値を見つける一般的な方法があります。中央
値を二分し、xを現在の二分数とし、midをシーケンスの中央値とします
。xより大きい数を1に設定し、xより小さい数を-1に設定します。
1 / -1のシーケンス全体を
合計します。Sum> 0の場合、1の数が-1より大きいことを意味します。つまり、xより大きい数がxより小さい数より大きいことを意味し、
x <= mid、または
その逆、x> midを示します。


この質問に戻ると、[b + 1、c-1]は必要な間隔であり、[a、b]はサフィックスを選択し、[c、d]はプレフィックスを選択します。
中央値をできるだけ大きくするには、Sumをできるだけ大きくする必要があります。
[b + 1、c-1]は固定されており、[a、b]の後の[c、d]のプレフィックスは相互に影響を与えないため、間隔[a、b]で最大のサフィックスを選択し、間隔[c、d]で選択します。最大プレフィックス。

この時点で、特定のアイデアがあります。
ラインセグメントツリーは、間隔の最大プレフィックス、最大サフィックス、間隔の合計、および各二分法を維持し、ラインセグメントツリーを使用してそれが実行可能かどうかを判断します。


ただし、通常のラインセグメントツリーを使用する
場合は、クエリごとに間隔を1 / -1にリセットする必要があるため、クエリ時間の複雑さがO(nlogn)とTLEであるのは不思議です

永続的なラインセグメントツリーを使用して、各二分法の後に1 / -1シーケンスを前処理することを検討してください。各クエリは、履歴バージョンをチェックすることです。
全体の複雑さは、O(nlogn + qlog2n)とACです。


個人的な理解:小さいものから大きいものへの重みに従ってシーケンスをソートした後、最初に空のツリーを確立します。これは、右側の数字がすべて彼よりも大きいことを意味します。この時点で、ラインセグメントツリーの間隔の合計は1 -1のみであり、このシーケンスが確立されます。

最初の位置である答えについては、ラインセグメントツリーの間隔と単調性が小さいものから大きいものまであるため、現在のツリーの合計が> = 0の場合。これは、過去に正しいものを見つけることができることを意味するため、見つけた数が問題を満たします。満たす必要のある条件の最大数。見つけたら、この番号を配列に戻し、対応する番号サイズを出力します。

P2839【全国研修チーム】中

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e4+1000;
typedef long long LL;
inline LL read()
{	LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
struct Tree{
    LL lson,rson,pre,suf,sum;///前缀最大,后缀最大,区间和;
}tree[maxn<<6];
LL root[maxn];
struct P{
    LL x,pos;
}a[maxn];
void push_up(LL p){
    tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
    tree[p].pre=max(tree[tree[p].lson].pre,tree[tree[p].lson].sum+tree[tree[p].rson].pre);
    tree[p].suf=max(tree[tree[p].rson].suf,tree[tree[p].rson].sum+tree[tree[p].lson].suf);
}
LL build(LL l,LL r){
    LL rt=++tot;
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=1;
        return rt;
    }
    LL mid=(l+r)>>1;
    tree[rt].lson=build(l,mid);
    tree[rt].rson=build(mid+1,r);
    push_up(rt);
    return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query_sum(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].sum;
    }
    LL ans=0;
    LL mid=(l+r)>>1;
    ///debug(mid);
    if(L<=mid)  ans+=query_sum(tree[rt].lson,l,mid,L,R);
    if(R>mid) ans+=query_sum(tree[rt].rson,mid+1,r,L,R);
    return ans;
}
LL query_premax(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].pre;
    }
    LL mid=(l+r)>>1;
   /// debug(mid);
    if(R<=mid){
        return query_premax(tree[rt].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query_premax(tree[rt].rson,mid+1,r,L,R);
    }
    else return max(query_premax(tree[rt].lson,l,mid,L,R),query_sum(tree[rt].lson,l,mid,L,R)+query_premax(tree[rt].rson,mid+1,r,L,R));
}
LL query_sufmax(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].suf;
    }
    LL mid=(l+r)>>1;
    ///debug(l);debug(r);
    if(R<=mid){
        return query_sufmax(tree[rt].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query_sufmax(tree[rt].rson,mid+1,r,L,R);
    }
    else return max(query_sufmax(tree[rt].rson,mid+1,r,L,R),query_sum(tree[rt].rson,mid+1,r,L,R)+query_sufmax(tree[rt].lson,l,mid,L,R));
}
bool cmp(P A,P B){
    return A.x<B.x;
}
LL n;
LL test[5];
bool check(LL x,LL a,LL b,LL c,LL d){
    LL sum=0;
   /// cout<<"a="<<a<<" "<<"b="<<b<<" "<<"c="<<c<<" "<<"d="<<d<<endl;
    if(! ( (c-1)-(b+1)<=0 ))  sum+=query_sum(root[x],1,n,b+1,c-1);
   /// cout<<"fuck"<<endl;
    sum+=query_sufmax(root[x],1,n,a,b);
    sum+=query_premax(root[x],1,n,c,d);
    if(sum>=0) return true;
    else return false;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  n=read();
  ///题目是从下标0开始的,所以最后的询问算区间的时候要+1
  for(LL i=1;i<=n;i++){
      a[i].x=read();
      a[i].pos=i;
  }
  sort(a+1,a+1+n,cmp);
  root[1]=build(1,n);
  for(LL i=2;i<=n+1;i++){
    root[i]=update(root[i-1],1,n,a[i-1].pos,-1);
  }
  LL Q;Q=read();
  LL last=0;
  while(Q--){
    test[0]=read();test[1]=read();test[2]=read();test[3]=read();
    for(LL i=0;i<4;i++) test[i]=(test[i]+last)%n;
    sort(test,test+4);
    for(LL i=0;i<4;i++){
        test[i]++;
    }
    LL l=1;LL r=n;
    while(l<r){
        LL mid=(l+r+1)>>1;
        if(check(mid,test[0],test[1],test[2],test[3])) l=mid;
        else r=mid-1;
    }
    last=a[l].x;
    printf("%lld\n",a[l].x);

  }
return 0;
}

 

CF484Eサインオンフェンス

アイデア:この質問は非常に似ています。ただし、小さいものから大きいものへと設定すると、境界が固定されます。

シミュレーション後にこのバグを見つけました。私が成長したとき、私は最初にすべて1でツリーを前処理し、次に現在の番号を0に割り当て、2つの分割されたl = midでそれを見つけたからです。

しかし、私たちが探している間隔の長さの合計には、この点が含まれている必要があります。しかし、0を割り当てました。

したがって、0個のツリーすべての処理を開始し、値を1に割り当てて、二分法r = midを見つけます。

連続する最長の1がである場合はクエリに注意し、間隔を超える場合は慎重に処理してください。より良い処理方法は、スパンが完了した後、次のステップでプレフィックスとサフィックスの最大値を処理し、間隔の最長のプレフィックスと検出される間隔の長さを最小にしてから結合することです。フェッチ後、最大値を取得するために左右の間隔の最大値。

 

コードを具体的に理解するのは簡単です

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}

struct P{
   LL x,pos;
}a[maxn],b[maxn];
struct Tree{
    LL lson,rson,pre,suf,sum;///pre-前缀最长连续1长度,suf后缀最长连续1长度,sum该区间最长连续1的长度
    LL l,r;///该节点代表的区间
}tree[maxn<<5];
LL tot=0;
LL root[maxn];
bool cmp(P A,P B){
    return A.x>B.x;
}
LL rmax(LL A,LL B,LL C){
    return max(A,max(B,C));
}
void push_up(LL rt){
    tree[rt].pre=tree[tree[rt].lson].pre;
    tree[rt].suf=tree[tree[rt].rson].suf;
    tree[rt].sum=rmax(tree[tree[rt].lson].sum,tree[tree[rt].rson].sum,tree[tree[rt].lson].suf+tree[tree[rt].rson].pre);
    if(tree[tree[rt].lson].pre==tree[tree[rt].lson].r-tree[tree[rt].lson].l+1) tree[rt].pre+=tree[tree[rt].rson].pre;
    if(tree[tree[rt].rson].suf==tree[tree[rt].rson].r-tree[tree[rt].rson].l+1) tree[rt].suf+=tree[tree[rt].lson].suf;
}
LL build(LL l,LL r,LL val){
   LL rt=++tot;
   tree[rt].l=l;tree[rt].r=r;
   if(l==r){
    tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
    return rt;
   }
   LL mid=(l+r)>>1;
   tree[rt].lson=build(l,mid,val);
   tree[rt].rson=build(mid+1,r,val);
   push_up(rt);
   return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r) return tree[now].sum;
    LL mid=(l+r)>>1;
    if(R<=mid){
       return query(tree[now].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query(tree[now].rson,mid+1,r,L,R);
    }
    else {
            LL ans1=query(tree[now].lson,l,mid,L,R);
            LL ans2=query(tree[now].rson,mid+1,r,L,R);
            LL res=max(ans1,ans2);
            LL ll=min(tree[tree[now].lson].suf,mid-L+1);///细节
            LL rr=min(tree[tree[now].rson].pre,R-mid);///细节
            res=max(res,ll+rr);
            return res;
    }
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n;n=read();
  for(LL i=1;i<=n;i++){
      a[i].x=read();
      a[i].pos=i;
  }
  sort(a+1,a+1+n,cmp);

  root[0]=build(1,n,0);///开始建全0空树

  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i].pos,1);
  }
  LL Q;Q=read();
  while(Q--){
     LL l,r,k;l=read();r=read();k=read();

     LL L=1;LL R=n;
     while(L<R){
        LL mid=(L+R)>>1;
        LL t=query(root[mid],1,n,l,r);

        if(t>=k)  R=mid;
        else L=mid+1;

     }
     printf("%lld\n",a[L].x);
  }
return 0;
}

Weng_Weijieの説明:

この質問は[全国トレーニングチーム]ミドルと非常によく似ています

二分された答えを考えてください

問題は、mid以上でk以上の長さのサブインターバルがあるかどうかを判断することです。

中程度から1以上、中程度から0未満、つまり、クエリ間隔内のすべての1の最長間隔がkを超えるかどうかを設定します。

これは、プレフィックスとサフィックスの最長間隔を維持することにより、間隔の追加をサポートできます。

各二分法の中央は異なるため、椅子ツリーを使用して、各中央のラインセグメントツリーを維持できます。


ちょっと変わった質問:

ICPCアジア徐州2019年予選I.クエリ

質問の意味:間隔[l、r]でmin(a [i]、a [j])= gcd(a [i]、a [j])(l <= i <j <= r)を満たすペアを見つけます。数。

アイデア:実際には、別の数値の倍数である間隔で、ある数値の順序付けられた対数を見つけることです。これは順列であるため、そのような対数がnlognを超えないことは簡単にわかります。

nlognの証明は、古典的な数理論のトリックです。現在の数の倍数であるシーケンス内の数は、均等に分割可能でなければなりません。

次に\ sum_ {i = 1} ^ {n}(\ left \ lfloor n \ right \ rfloorn / i)、n + n / 2 + n / 3 + ... n / n = n *(1 + 1/2 + 1/3 + .... 1 / n)とそれに続く高調波レベルとして計算されるものがあり ます番号== logn。証拠は私のブログの1つに転載されました。削減と進行の複雑さの証拠を探す

次に、各数値の係数を前処理できます。

次に、議長ツリーを確立します。各議長ツリーは、因子対数の数値ステータスとして1-iに格納され、r番目の行セグメントツリーでクエリ間隔内の位置の番号を直接見つけます。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef int LL;
LL a[maxn],p[maxn];
LL tot=0,root[maxn];
vector<LL>v[maxn];
struct Tree{
    LL lson,rson,val;
}tree[maxn*20*10];
inline void push_up(LL p){
    tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
inline LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;///此题要对root[i]这棵树本身进行多次修改
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].val+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
inline LL query(LL now,LL l,LL r,LL L,LL R){
     if(L<=l&&R>=r){
        return tree[now].val;
     }
     LL ans=0;
     LL mid=(l+r)>>1;
     if(L<=mid) ans+=query(tree[now].lson,l,mid,L,R);
     if(R>mid) ans+=query(tree[now].rson,mid+1,r,L,R);
     return ans;
}
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
int main(void)
{
  LL n,m;n=read();m=read();
  for(LL i=1;i<=n;i++){
    a[i]=read();
    p[a[i]]=i;
  }
  ///nlogn
  for(LL i=1;i<=n;i++){
    for(LL j=i*2;j<=n;j+=i){
       if(p[i]<p[j]) v[p[j]].push_back(p[i]);
       if(p[i]>p[j]) v[p[i]].push_back(p[j]);
    }
  }
  for(LL i=1;i<=n;i++){
    root[i]=root[i-1];///先复制
    for(auto u:v[i]){
      root[i]=update(root[i],1,n,u,1);
    }
  }
  while(m--){
      LL l,r;l=read();r=read();
      LL ans=query(root[r],1,n,l,r);
      printf("%d\n",ans);
  }
return 0;
}

K番目に大きい動的間隔(テンプレート)

ツリー配列は、議長ツリーを設定します。特定のプロセスについては、別のブログを参照してください。学んだ後、私は新しい理解を得ました。

P2617動的ランキング(k番目に大きい動的間隔ツリー配列セットチェアマンツリー)の詳細な説明

特別な理解:

更新プロセス中、静的な議長ツリーを照会するプロセスでは、コードは次のようになります。新しい議長ツリーを開くことができるたびに、更新は前のものとは異なるログの長いチェーンであり、残りは直接ポイントバックされます。大丈夫。

ただし、すべての状況が新しいわけではないことに注意してください。この質問の状況は、元のノードの更新を上書きすることです。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
struct Tree{
    LL l,r,rt,sum;
}tree[maxn*4*35];
struct operation{bool b;LL l,r,k;LL pos,t;}q[maxn];
LL n,m,a[maxn],root[maxn],o[maxn*2],tot=0,len=0;
LL temp[2][30],cnt[10];
void modify(LL &now,LL l,LL r,LL x,LL val){
    if(!now) now=++tot;
    tree[now].sum+=val;
    if(l==r) return;
    LL mid=(l+r)>>1;
    if(x<=mid) modify(tree[now].l,l,mid,x,val);
    else modify(tree[now].r,mid+1,r,x,val);
}
void pre_modify(LL pos,LL val){
    LL x=lower_bound(o+1,o+1+len,a[pos])-o;
    for(LL i=pos;i<=n;i+=lowbit(i)){
        modify(root[i],1,len,x,val);///处理出需要修改哪log棵主席树
    }
}
LL query(LL l,LL r,LL k){
    if(l==r) return l;
    LL mid=(l+r)>>1;LL sum=0;
    for(LL i=1;i<=cnt[1];i++) sum+=tree[tree[temp[1][i]].l].sum;
    for(LL i=1;i<=cnt[0];i++) sum-=tree[tree[temp[0][i]].l].sum;
    if(k<=sum){
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].l;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].l;
        }
        return query(l,mid,k);
    }
    else{
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].r;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].r;
        }
        return query(mid+1,r,k-sum);
    }
}
LL pre_query(LL l,LL r,LL k){
    memset(temp,0,sizeof(temp));
    cnt[0]=cnt[1]=0;
    for(LL i=r;i;i-=lowbit(i)) temp[1][++cnt[1]]=root[i];
    for(LL i=l-1;i;i-=lowbit(i)) temp[0][++cnt[0]]=root[i];
    return query(1,len,k);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  cin>>n>>m;
  for(LL i=1;i<=n;i++){
    cin>>a[i];
    o[++len]=a[i];
  }
  for(LL i=1;i<=m;i++){
    char op;cin>>op;
    if(op=='Q'){
        q[i].b=1; cin>>q[i].l>>q[i].r>>q[i].k;
    }
    else{
        q[i].b=0;
        cin>>q[i].pos>>q[i].t;
        o[++len]=q[i].t;
    }
  }
  sort(o+1,o+len+1);
  len=unique(o+1,o+len+1)-o-1;
  for(LL i=1;i<=n;i++){
    pre_modify(i,1);
  }
  for(LL i=1;i<=m;i++){
    if(q[i].b==1){
        cout<<o[pre_query(q[i].l,q[i].r,q[i].k)]<<endl;
    }
    else{
        pre_modify(q[i].pos,-1);
        a[q[i].pos]=q[i].t;
        pre_modify(q[i].pos,1);
    }
  }
return 0;
}

要約:議長ツリーの保守には、まだ多くの考慮が必要です。ただし、一般的に確立された後は、root [r]またはroot [r] -root [l]で検索する必要があるようです。残りは蓄積と思考を見ることであり、特定のルーチンと固定された質問タイプがあります。

(学期の終わりに元気になることを願っています

おすすめ

転載: blog.csdn.net/zstuyyyyccccbbbb/article/details/110563563