整体二分入门

27 篇文章 0 订阅
25 篇文章 1 订阅

骗分神器!

推荐论文:xhr2013集训队论文

能够整体二分的题目必须满足一下几点(摘自xhr论文大笑

1、询问答案具有可二分性

2、修改对答案的判定相互独立,互不影响

3、如果修改对询问有贡献,则贡献是确定的,且与判定标准为无关

4、贡献满足结合律、交换律,具有可加性

5、题目允许离线(好像现在大部分题目强制在线)

另外一个很重要的一点就是注意些整体二分时必须是的当前步骤的复杂度只与当前待处理序列长度有关

找了几道入门题做了下


1、Tsinsen A1333 矩阵乘法

二分第k小值,每次用二维树状数组维护子矩阵内有多少点,以此决定此次二分的值对于每个询问是大了还是小了,将询问分类,继续向下二分即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define lowbit(x) ((x)&(-(x)))
int sum[505][505];
int n,q,i,j,cnt,ans[100005];
struct digital{ int w,x,y; } dig[250005];
struct arr{ int x1,y1,x2,y2,k,num; } st[100005], tmp[100005];
void add(int x,int y,int val){
  for (int i=x;i<=n;i+=lowbit(i))
    for (int j=y;j<=n;j+=lowbit(j))
      sum[i][j] += val;
}
int query(int x,int y){
  int ret = 0;
  for (int i=x;i>0;i-=lowbit(i))
    for (int j=y;j>0;j-=lowbit(j))
      ret += sum[i][j];
  return ret;
}
int query(int x1,int y1,int x2,int y2)
{ return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1); }
void solve(int l,int r,int L,int R){
  if (l>r || L>R) return;
  /*
  if ((r-l+1)<100/(R-L+1)){
    for (int i=l;i<=r;i++){
      for (int j=L;j<=R;j++)
      if (st[j].x1<=dig[i].x && st[j].x2>=dig[i].x &&
          st[j].y1<=dig[i].y && st[j].y2>=dig[i].y)
        if (--st[j].k==0) ans[st[j].num]=dig[i].w;
    }
    return;
  }
  */
  int mid = (dig[l].w+dig[r].w)/2, cnt=0;
  for (int i=l;i<=r&&dig[i].w<=mid;i++)
    add(dig[i].x,dig[i].y,1), cnt++;
  int cnt1 = L, cnt2 = R;
  for (int i=L;i<=R;i++){
    int t = query(st[i].x1,st[i].y1,st[i].x2,st[i].y2);
    if (t>=st[i].k) tmp[cnt1++] = st[i], ans[st[i].num]=mid;
      else tmp[cnt2] = st[i], tmp[cnt2--].k -= t;
  }
  for (int i=l;i<=r&&dig[i].w<=mid;i++)
    add(dig[i].x,dig[i].y,-1);
  //if (l==r) return;
  for (int i=L;i<=R;i++)
    st[i] = tmp[i];
  if (l+cnt-1!=r || cnt2!=R) solve(l,l+cnt-1,L,cnt2);
  if (l+cnt!=l || cnt1!=L) solve(l+cnt,r,cnt1,R);
}
bool cmp(const digital &a,const digital &b)
  { return a.w<b.w; }
int main(){
  //freopen("mat.in","r",stdin);
  //freopen("mat.out","w",stdout);
  scanf("%d%d",&n,&q);
  for (i=1;i<=n;i++)
    for (j=1;j<=n;j++){
      scanf("%d",&dig[++cnt].w);
      dig[cnt].x = i; dig[cnt].y = j;
    }
  sort(dig+1,dig+cnt+1,cmp);
  for (i=1;i<=q;i++){
    scanf("%d%d",&st[i].x1,&st[i].y1);
    scanf("%d%d",&st[i].x2,&st[i].y2);
    scanf("%d",&st[i].k);
    st[i].num = i;
  }
  solve(1,cnt,1,q);
  for (i=1;i<=q;i++) printf("%d\n",ans[i]);
  return 0;
}

2、POI2011 Meteors

仍然是一道裸题,二分陨石雨场数,用线段树求和,把国家分类。

trick:求和的时候可能会暴long long,注意提前特判退出。

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
#define lowbit(x) ((x)&(-(x)))
#define pb push_back
typedef long long LL;
const int Maxn=300005;
LL sum[Maxn];
int n,m,k,i,x,ans[Maxn];
struct arr { int x,y; } a[Maxn],b[Maxn];
struct star { int l,r,x; } p[Maxn];
vector <int> e[Maxn];

void add(int x,int w){
  for (int i=x;i>0;i-=lowbit(i))
    sum[i]+=(LL)w;
}

void add(int l,int r,int z)
{ add(r,z); add(l-1,-z); }

LL query(int x){
  LL ret = 0;
  for (int i=x;i<=m;i+=lowbit(i))
    ret += sum[i];
  return ret;
}

void solve(int l,int r,int L,int R){
  if (l>r || L>R) return;
  int mid = (l+r)/2;
  for (int i=l;i<=mid;i++)
    if (p[i].l<=p[i].r) add(p[i].l,p[i].r,p[i].x);
      else add(p[i].l,m,p[i].x), add(1,p[i].r,p[i].x);

  int t1 = L, t2 = R;
  for (int i=L;i<=R;i++){
    int len = e[a[i].y].size();
    LL tmp=0;
    for (int j=0;j<len;j++){
      tmp += query(e[a[i].y][j]);
      if (tmp>=a[i].x) break;
    }
    if (tmp>=a[i].x) ans[a[i].y]=mid, b[t1++]=a[i];
      else a[i].x-=tmp, b[t2--]=a[i];
  }
  for (int i=l;i<=mid;i++)
    if (p[i].l<=p[i].r) add(p[i].l,p[i].r,-p[i].x);
      else add(p[i].l,m,-p[i].x), add(1,p[i].r,-p[i].x);
  for (int i=L;i<=R;i++) a[i]=b[i];
  
  if (mid!=r || t2!=R) solve(l,mid,L,t2);
  if (mid+1!=l || t1!=L) solve(mid+1,r,t1,R);
}

int main(){
  //freopen("met.in","r",stdin);
  //freopen("met.out","w",stdout);
  scanf("%d%d",&n,&m);
  for (i=1;i<=m;i++){
    scanf("%d",&x);
    e[x].pb(i);
  }
  for (i=1;i<=n;i++){
    scanf("%d",&a[i].x);
    a[i].y = i;
  }
  scanf("%d",&k);
  for (i=1;i<=k;i++)
    scanf("%d%d%d",&p[i].l,&p[i].r,&p[i].x);
  solve(1,k,1,n);
  for (i=1;i<=n;i++)
  if (ans[i]==0) puts("NIE");
    else printf("%d\n",ans[i]);
  return 0;
}

3、zjoi2013 k大数查询

k大和k小是等价的,转化一下求k小

考虑将插入和修改分类。

二分值,将小于其的插入用线段树维护,判断二分值对于每个修改是过大还是过小(保证其查询的为修改之前的插入个数),分类,再分治!

#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
const int Maxn=100005;
int tip[Maxn<<2],sum[Maxn<<2];
int n,m,N,M,type,i,ans[Maxn];
struct arr{ int l,r,k,num; }a[Maxn],b[Maxn],c[Maxn];
 
void push(int p,int l,int r){
  int mid = (l+r)>>1;
  tip[p*2+1] += tip[p];
  tip[p*2+2] += tip[p];
  sum[p*2+1] += tip[p]*(mid-l+1);
  sum[p*2+2] += tip[p]*(r-mid);
  tip[p] = 0;
}
 
void add(int p,int l,int r,int L,int R,int w){
  if (l>R || L>r) return;
  if (L<=l && R>=r){
    sum[p] += w*(r-l+1);
    tip[p] += w;
    return;
  }
  if (tip[p]!=0) push(p,l,r);
  int mid=(l+r)>>1;
  add(p*2+1,l,mid,L,R,w);
  add(p*2+2,mid+1,r,L,R,w);
  sum[p]=sum[p*2+1]+sum[p*2+2];
}
 
int query(int p,int l,int r,int L,int R){
  if (l>R || L>r) return 0;
  if (L<=l && R>=r) return sum[p];
  if (tip[p]!=0) push(p,l,r);
  int mid=(l+r)>>1;
  return query(p*2+1,l,mid,L,R) + query(p*2+2,mid+1,r,L,R);
}
 
void solve(int l,int r,int L,int R){
  if (l>r || L>R) return;
  int minx = n, maxx = -n;
  for (int i=l;i<=r;i++){
    minx = min(minx, a[i].k);
    maxx = max(maxx, a[i].k);
  }
  int mid = (minx+maxx)/2;
  if (minx+maxx<0) mid = (minx+maxx-1)/2;
   
  int cnt1 = L, cnt2 = R;
  for (int i=l,j=L;i<=r||j<=R;){
    if (j>R || (i<=r && a[i].num<b[j].num)){
      if (a[i].k>mid) {i++;continue;}
      add(0,1,n,a[i].l,a[i].r,1);
      i++;
    } else
    if (i>r || (j<=R && a[i].num>b[j].num)){
      int t = query(0,1,n,b[j].l,b[j].r);
      if (t>=b[j].k) ans[b[j].num]=-mid, c[cnt1++]=b[j];
        else b[j].k-=t, c[cnt2--]=b[j];
      j++;
    }
  }
  for (int i=cnt1;i<=R;i++)
  if (cnt1+R-i>i) swap(c[i],c[cnt1+R-i]); else break;
  for (int i=L;i<=R;i++) b[i]=c[i];
   
  int t1 = l;
  for (int i=l;i<=r;i++)
  if (a[i].k<=mid){
    c[t1++] = a[i];
    add(0,1,n,a[i].l,a[i].r,-1);
  }
  int t2 = t1;
  for (int i=l;i<=r;i++)
  if (a[i].k>mid)
    c[t2++] = a[i];
  for (int i=l;i<=r;i++) a[i]=c[i];
   
  if (t1-1!=r || cnt2!=R) solve(l,t1-1,L,cnt2);
  if (t1!=l || cnt1!=L) solve(t1,r,cnt1,R);
}
 
int main(){
  //freopen("sequence.in","r",stdin);
  //freopen("sequence.out","w",stdout);
  scanf("%d%d",&n,&m);
  for (i=1;i<=m;i++){
    scanf("%d",&type);
    if (type==1){
      scanf("%d%d%d",&a[N].l,&a[N].r,&a[N].k);
      a[N].k = -a[N].k;
      a[N++].num = i;
    } else
    {
      scanf("%d%d%d",&b[M].l,&b[M].r,&b[M].k);
      b[M++].num = i;
    }
  }
  for (i=1;i<=m;i++) ans[i]=-n-1;
  solve(0,N-1,0,M-1);
  for (i=1;i<=m;i++)
  if (ans[i]!=-n-1)
    printf("%d\n",ans[i]);
  return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值