【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
",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
",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
",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
",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
",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
",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
",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
",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
",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;
}