挑战程序设计竞赛 3.3 活用各种数据结构

 

【Summarize】 

  1. 线性维护只能处理部分问题的时候要想到数据拆分,容斥解决问题

  2. 在不断有人被淘汰的序列问题中,查找左右第几个人是谁的时候可以考虑线段树优化

  3. 当问题结果并不同时依赖与两个维度的操作的时候,二维问题可拆分为两个一维问题分别解决

 

POJ 1990:MooFest

/*
    题目大意:给出每头奶牛的位置和至少要多少分贝的音量才能听到谈话
    现在求奶牛两两交流成功需要的距离*分贝的总和。
    题解:我们将奶牛对于需要的分贝排序,那么在计算的时候,
    每头奶牛只要计算和序列前面所有奶牛的答案即可
    那么只要维护前面所有奶牛和其距离之差的绝对值之和即可
    将其拆分为坐标小于该奶牛的坐标之和,和大于该奶牛的部分
    发现只要用线段树维护区间和和区间数的个数即可。
*/
#include <cstdio>
#include <algorithm>
#include <cstring> 
using namespace std;
const int N=20010;
int T[N*4],C[N*4],n,t,c,M;
struct data{int v,x;}p[N];
long long ans;
bool cmp(data a,data b){return a.v<b.v;}
void add(int x,int y){
    T[x+=M]+=y; C[x]++;
    for(x/=2;x;x/=2){
        T[x]=T[x<<1]+T[(x<<1)^1];
        C[x]=C[x<<1]+C[(x<<1)^1];
    }
}
void query(int x,int y){
    t=c=0;
    x+=M-1;y+=M+1; 
    while(x^y^1>0){ 
        if(~x&1)t+=T[x+1],c+=C[x+1]; 
        if(y&1)t+=T[y-1],c+=C[y-1]; 
        x>>=1;y>>=1; 
    }
}
int main(){
    for(M=1;M<N;M<<=1);
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)scanf("%d%d",&p[i].v,&p[i].x);
        sort(p+1,p+n+1,cmp);
        memset(T,0,sizeof(T));
        memset(C,0,sizeof(C));
        for(int i=1;i<=n;i++){
            query(1,p[i].x);
            ans+=1LL*p[i].v*(p[i].x*c-t);
            query(p[i].x,20000);
            ans+=1LL*p[i].v*(t-p[i].x*c);
            add(p[i].x,p[i].x);
        }printf("%lld\n",ans);
    }return 0;
}

POJ 3109:Inner Vertices

/*
    题目大意:在一个棋盘上放满白子,现在把一些白子变成黑子,
    如果一个白子上下左右都有黑子,就会变成黑子,问最终黑子个数
    题解:首先我们在每列的开头和结尾做标记,之后对行线扫描,
    如果是列的开头,那么在该列中标记,如果是列的结尾,则在该列中去除标记
    那么我们只要统计行的开头到行的结尾之间夹着多少列标记,且该列下该行没有原来的黑子
    就是该行新增的黑子数量,对于总的黑子数量,可以用树状数组统计,之后容斥一下列标记即可。
*/
#include <cstdio>
#include <algorithm>
#include <utility>
#define cx first
#define cy second
using namespace std;
typedef long long LL; 
const int MAX_N=100010;
typedef pair<int,int> P;
int t,c[MAX_N],r[MAX_N],N,ys[MAX_N],d[MAX_N],vis[MAX_N];
P p[MAX_N];
bool cmp(int a,int b){return p[a].cy<p[b].cy||p[a].cy==p[b].cy&&p[a].cx<p[b].cx;}
int sum(int x){int s=0;while(x)s+=c[x],x-=(x&-x);return s;}
void add(int x,int v){while(x<=t)c[x]+=v,x+=x&-x;}
int main(){
    scanf("%d",&N);
    for(int i=0;i<N;i++)scanf("%d%d",&p[i].cx,&p[i].cy),r[i]=i;
    sort(p,p+N); sort(r,r+N,cmp);
    ys[r[0]]=1; d[r[0]]=1;
    for(int i=1;i<N;i++){
        ys[r[i]]=ys[r[i-1]];
        if(p[r[i]].cy==p[r[i-1]].cy)continue;
        d[r[i-1]]--; d[r[i]]++;
        ys[r[i]]++;
    }LL ans=N; d[r[N-1]]--; t=ys[r[N-1]];
    for(int i=0,j=0;i<N;){
        for(j=i;j<N&&p[j].cx==p[i].cx;j++)
            if(d[j]<0){vis[ys[j]]=0;add(ys[j],-1);}
        if(ys[i]<ys[j-1]-1){
            ans+=sum(ys[j-1]-1)-sum(ys[i]);
            for(int k=i+1;k<j-1;k++)if(vis[ys[k]])ans--;
        }for(;i<j;i++)if(d[i]>0){add(ys[i],1);vis[ys[i]]=1;}
    }printf("%lld\n",ans);
    return 0;
}

POJ 2155:Matrix

/*
    题目大意:要求维护两个操作,矩阵翻转和单点查询
    题解:树状数组可以处理前缀和问题,前缀之间进行容斥即可得到答案。
*/
#include <cstring>
#include <cstdio>
const int N=1010;
using namespace std;
int c[N][N],n,m,T;
char op[2];
void add(int x,int y){
    int i,j,k;
    for(i=x;i<N;i+=i&-i)
    for(j=y;j<N;j+=j&-j)
    c[i][j]^=1;
}
int sum(int x,int y){
    int i,j,ret=0;
    for(i=x;i;i-=i&-i)
    for(j=y;j;j-=j&-j)
    ret^=c[i][j];
    return ret;
}
int main(){
    scanf("%d",&T);
    for(int cas=0;cas<T;cas++){
        if(cas)puts("");
        memset(c,0,sizeof(c));
        scanf("%d%d",&n,&m);
        while(m--){
            int x,y,x1,y1;
            scanf("%s%d%d",op,&x,&y);
            if(op[0]=='Q')printf("%d\n",sum(x,y));
            else{
                scanf("%d%d",&x1,&y1);
                add(x,y); add(x1+1,y);
                add(x,y1+1); add(x1+1,y1+1);
            }
        }
    }return 0;
}

POJ 2886:Who Gets the Most Candies

/*
    题目大意:一些人站成一个圈,每个人手上都有一个数字,
    指定从一个人开始淘汰,每次一个人淘汰时,将手心里写着的数字x展示
    如果x是正数,则淘汰右手边第x个人,否则淘汰左手边地-x个人。
    每个人淘汰的时候将获得积分,积分的多少取决于他是第几个淘汰的,
    积分为淘汰的顺序数拥有的因子数量
    输出积分最高的人和其积分
    题解:首先,我们计算出第几个淘汰的人分数最高,那么我们只要模拟到这个人淘汰即可
    在处理中,寻找下一个人时我们可以用线段树求kth的方法求出这个人的位置,顺序处理即可。
*/
#include <cstdio>
#include <cstring> 
using namespace std;
const int N=500010;
int n,k,T[N*4],m,id,ans[N];
struct data{int val;char name[20];}p[N];
void build(int x,int l,int r){
    T[x]=r-l+1;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
}
int update(int key,int x,int l,int r){
    T[x]--; if(l==r)return l;
    int mid=(l+r)>>1;
    if(T[x<<1]>=key)return update(key,x<<1,l,mid);
    return update(key-T[x<<1],x<<1|1,mid+1,r);
}
void GetID(){   
    memset(ans,0,sizeof(ans));
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j+=i)ans[j]++;
    }int mx=ans[id=1];
    for(int i=2;i<=n;i++)if(ans[i]>mx){mx=ans[i];id=i;}
}
int main(){
    while(~scanf("%d%d",&n,&k)){
        build(1,1,n); GetID();
        for(int i=1;i<=n;i++)scanf("%s%d",p[i].name,&p[i].val);
        int mod=T[1],pos=0;
        p[0].val=0; m=id;
        while(m--){
            if(p[pos].val>0)k=((k-1+p[pos].val-1)%mod+mod)%mod+1;
            else k=((k-1+p[pos].val)%mod+mod)%mod+1;
            pos=update(k,1,1,n); mod=T[1];
        }printf("%s %d\n",p[pos].name,ans[id]);
    }
}

POJ 3264:Balanced Lineup

/*
    题目大意:求区间最大值和最小值的差值
    题解:线段树维护区间极值即可。
*/
#include <cstdio>
#include <algorithm>
#include <cstring> 
#include <climits>
using namespace std;
const int N=1000010;
int T[N*4],C[N*4],n,M,m;
struct data{int v,x;}p[N];
long long ans;
bool cmp(data a,data b){return a.v<b.v;}
void add(int x,int y){
    T[x+=M]=y; C[x]=y;
    for(x/=2;x;x/=2){
        T[x]=max(T[x<<1],T[(x<<1)^1]);
        C[x]=min(C[x<<1],C[(x<<1)^1]);
    }
}
int query(int x,int y){
    int t=INT_MIN,c=INT_MAX;
    x+=M-1;y+=M+1; 
    while(x^y^1>0){ 
        if(~x&1)t=max(T[x+1],t),c=min(C[x+1],c); 
        if(y&1)t=max(T[y-1],t),c=min(C[y-1],c); 
        x>>=1;y>>=1; 
    }return t-c;
}
int main(){
    scanf("%d%d",&n,&m);
    for(M=1;M<n;M<<=1);
    for(int i=0;i<=M+n;i++)C[i]=INT_MAX,T[i]=INT_MIN;
    for(int i=1;i<=n;i++){
        int x; scanf("%d",&x);
        add(i,x); 
    }
    while(m--){
        int l,r; scanf("%d%d",&l,&r);
        printf("%d\n",query(l,r));
    }return 0;
}

POJ 3368:Frequent values

/*
    题目大意:有一个有序序列,要求区间查询出现次数最多的数
    题解:维护每个区间左端点和右端点,以及左右的长度,还有区间的答案
    每次线段合并的时候,对区间数据进行合并即可。
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=100010;
struct data{int a,b,l,r,val;}T[N*4];
int num[N],n,q,l,r;
data combine(data &l,data &r){
	  data ans; 
    ans.a=l.a; ans.b=r.b;
    ans.l=l.a==r.a?l.l+r.l:l.l;
    ans.r=r.b==l.b?l.r+r.r:r.r;
    ans.val=max(l.val,r.val);
    if(l.b==r.a)ans.val=max(ans.val,l.r+r.l);
    return ans;
}
void build(int x,int l,int r){
    if(l==r){T[x]=(data){num[l],num[r],1,1,1};return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    T[x]=combine(T[x<<1],T[x<<1|1]);
}
data query(int L,int R,int x,int l,int r){
    if(r<L||R<l){data u;return u;}
    if(L<=l&&r<=R)return T[x];
    int mid=(l+r)>>1;
    data vl=query(L,R,x<<1,l,mid),vr=query(L,R,x<<1|1,mid+1,r);
    if(mid<L)return vr;
    if(mid>=R)return vl;
    return combine(vl,vr);
}
int main(){
    while(scanf("%d",&n)&&n){
        scanf("%d",&q);
        for(int i=1;i<=n;i++)scanf("%d",&num[i]);
        build(1,1,n);
        while(q--){
            scanf("%d%d",&l,&r);
            printf("%d\n",query(l,r,1,1,n).val);
        }
    }return 0;
}

POJ 3470:Walls

/*
    题目大意:给出几面墙,均垂直于x轴或者y轴,给出一些鸟的位置(二维坐标点),
    鸟只会垂直x轴或者y轴飞行,并且会撞上最近的墙,问每面墙最后会有几只鸟撞上去。
    题解:我们将所有的二维坐标离散,对xy方向分别进行扫描线,
    以y轴方向为例,我们先从y最小的线开始扫,
    如果是墙,那么在线段树中更新其在x轴上的分布位置
    如果是鸟的坐标,那么在线段树中查找其位置,更新其答案。
    之后从y最大的开始反向扫描,更新,x方向也同理。
*/
#include <cstdio>
#include <algorithm> 
#include <cstring>
#include <climits>
using namespace std;
const int N=50010;
int n,m,wall,tot,dis[N],v[N],w[N],T[10*N],*arr;
int x[3*N],y[3*N],ry[3*N],rx[3*N],r[3*N],xs[3*N],ys[3*N],px[3*N],py[3*N];
bool cmp(int a,int b){return arr[a]<arr[b];}
void compress(int *x,int *r,int n,int *a,int *mp){
    for(int i=0;i<n;i++)r[i]=i; 
    arr=x; sort(r,r+n,cmp);
    int m=-1; a[r[0]]=++m; mp[m]=x[r[0]];
    for(int i=1;i<n;i++){
        int cur=r[i],lst=r[i-1];
        if(x[cur]==x[lst])a[cur]=m;
        else a[cur]=++m,mp[m]=x[cur];
    }
}
int query(int q,int x,int l,int r){
    if(T[x]!=-2)return T[x];
    int mid=(l+r)>>1;
    if(q<mid)return query(q,x<<1|1,l,mid);
    return query(q,x+1<<1,mid,r);
}
void update(int a,int b,int x,int l,int r,int val){
    if(r<=a||b<=l)return;
    else if(a<=l&&r<=b)T[x]=val;
    else{
        if(T[x]!=-2){
            T[x+1<<1]=T[x<<1|1]=T[x];
            T[x]=-2;
        }int mid=(l+r)>>1;
        update(a,b,x<<1|1,l,mid,val); 
        update(a,b,x+1<<1,mid,r,val);
    }
}
void scan(int k,int *ys,int *xs,int *py,int W){
    if(k<wall){
        int _k=k^1;
        if(xs[_k]>=xs[k])update(xs[k],xs[_k]+1,0,0,W,k/2); 
    }else{
        int t=query(xs[k],0,0,W);
        if(~t){
            int d=min(abs(py[ys[k]]-py[ys[t*2]]),abs(py[ys[k]]-py[ys[t*2+1]]));
            k-=wall; if(d<dis[k])dis[k]=d,v[k]=t;
        }
    }
}
void fly(int *ry,int *ys,int *xs,int *py,int W){
    T[0]=-1; for(int i=0;i<tot;i++)scan(ry[i],ys,xs,py,W);
    T[0]=-1; for(int i=tot-1;i>=0;i--)scan(ry[i],ys,xs,py,W); 
}
int main(){
    scanf("%d%d",&n,&m);
    wall=2*n; tot=wall+m;
    for(int i=0;i<tot;i++)scanf("%d%d",x+i,y+i);
    compress(x,rx,tot,xs,px);
    compress(y,ry,tot,ys,py);
    fill(dis,dis+m,INT_MAX);memset(w,0,n);
    fly(ry,ys,xs,py,xs[rx[tot-1]]+1);
    fly(rx,xs,ys,px,ys[ry[tot-1]]+1);
    for(int i=0;i<m;i++)++w[v[i]];
    for(int i=0;i<n;i++)printf("%d\n",w[i]);
    return 0;
}

POJ 1201:Intervals

/*
    题目大意:告诉你一个区间至少要选定的数字的个数,给出n个区间的需求
    问最少选取几个数字可以满足所有的需求
    题解:对于区间[a,b]建立不等式Sb+1-Sa>=c,最后要求最小化Smax,
    结合基础条件Si+1-Si>=0,Si-Si+1>=-1,构造差分约束系统求最长路就是答案。
*/
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200010,inf=~0U>>2,M=200000;
int time[N],q[N],size,h,t,n,ed,dis[N],in[N],nxt[N],w[N],v[N],g[N],ma,mi,u,e,cost;
void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
int spfa(int S){
    for(int i=0;i<=n;i++)dis[i]=-inf,in[i]=0,time[i]=0;
    time[S]=1,dis[S]=0,in[S]=1;
    int i,x,size; q[h=t=size=1]=S;
    while(size){
        for(i=g[x=q[h]],h=(h+1)%M,size--;i!=-1;i=nxt[i])if(dis[x]+w[i]>dis[v[i]]){
            dis[v[i]]=dis[x]+w[i];
            if(!in[v[i]]){
                time[v[i]]++,t=(t+1)%M,size++,in[q[t]=v[i]]=1;
                if(time[v[i]]>n)return -1;
            }
        }in[x]=0;
    }if(dis[n]==-inf)return -2;
    return dis[n];
}
int main(){
    while(~scanf("%d",&n)){
        ed=0;memset(g,-1,sizeof(g));
        ma=-inf; mi=inf;
        for(int i=1;i<=n;i++){
            scanf("%d%d%d",&u,&e,&cost);
            add(u,e+1,cost);
            ma=max(e+1,ma); mi=min(u,mi);
        }for(int i=mi;i<ma;i++)add(i,i+1,0),add(i+1,i,-1); 
        n=ma; printf("%d\n",spfa(mi));
    }return 0;
}

UVA11990:”Dynamic“ Inversion

/*
    题目大意:给出一个数列,每次删去一个数,求一个数删去之前整个数列的逆序对数。
    题解:一开始可以用树状数组统计出现的逆序对数量
    对于每个删去的数,我们可以用线段树求出它在原序列中的逆序对贡献
    在线段树的每个区间有序化数据,就可以二分查找出这个数在每个区间的位置,
    这样就处理出了划分出的区间的贡献,先用答案减去这一部分
    接下来考虑已经删去部分的容斥,我们发现只要对删去部分再做一次类似的操作,
    将这一部分跟当前删去数求一次贡献就是刚才多减去的部分,将这部分的答案再加回去。
    这个可以在线段树上查找的同时用树状数组维护。
    这样子就能处理每一次的删数操作了。
*/
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200010;
int n,m,c[30][N],a[30][N],arr[N],id[N];
long long ans;
void add(int c[],int x,int v,int n){while(x<=n)c[x]+=v,x+=x&-x;}
int sum(int c[],int x,int n=0){int s=0;while(x>n)s+=c[x],x-=x&-x;return s;}
void build(int x,int l,int r,int d){
    int mid=(l+r)>>1;
    for(int i=l;i<=r;i++)a[d][i]=a[d-1][i],c[d][i]=0;
    if(l==r)return;
    build(x<<1,l,mid,d+1);
    build(x<<1|1,mid+1,r,d+1);
    sort(a[d]+l,a[d]+r+1);
}
int find(int L,int R,int d,int v){
    int l=L,r=R;
    while(l<r){
        int mid=(l+r)>>1;
        if(a[d][mid]>=v)r=mid;
        else l=mid+1;
    }if(a[d][l]>v)l--;
    return l;
}
void query(int x,int l,int r,int L,int R,int v,int d,int f){
    int mid=(l+r)>>1;
    if(L<=l&&r<=R){
        int k=find(l,r,d,v),t=sum(c[d],k,l-1);
        if(!f){k=r-k;t=sum(c[d],r,l-1)-t;}
        else k-=l-1;
        ans-=k-t; return;
    }if(l>=r)return;
    if(L<=mid)query(x<<1,l,mid,L,R,v,d+1,f);
    if(R>mid)query(x<<1|1,mid+1,r,L,R,v,d+1,f);
}
void update(int x,int l,int r,int s,int v,int d){
    int mid=(l+r)>>1;
    if(l==r){add(c[d],l,1,r);return;}
    if(l>=r)return;
    if(s<=mid)update(x<<1,l,mid,s,v,d+1);
    else update(x<<1|1,mid+1,r,s,v,d+1);
    int k=find(l,r,d,v);
    add(c[d],k,1,r);
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        ans=0; memset(arr,0,sizeof(arr));
        for(int i=1;i<=n;i++){
            scanf("%d",&a[0][i]); id[a[0][i]]=i;
            ans+=i-1-sum(arr,a[0][i]);
            add(arr,a[0][i],1,n);
        }build(1,1,n,1);
        while(m--){
            int k;
            scanf("%d",&k);
            printf("%lld\n",ans);
            if(ans){
                query(1,1,n,1,id[k]-1,k,1,0);
                query(1,1,n,id[k]+1,n,k,1,1);
                update(1,1,n,id[k],k,1);
            }
        }
    }return 0;
}
posted @ 2017-01-24 22:50  forever97  阅读(181)  评论(0编辑  收藏  举报