线段树和平方分割
POJ 2104 K-th Number
题意:给出一段数列,让你求[L,R]区间内第k大的数字
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAXN = 100010; const int N = MAXN*40; int n,m,q,tot; int T[MAXN],A[MAXN],t[MAXN]; int lson[N],rson[N],sum[N]; vector<int>V; int getid(int x) //离散化 { return lower_bound(V.begin(),V.end(),x)-V.begin()+1; } int build(int l,int r) //建立一棵空树 { int rt = tot++; sum[rt] = 0; if(l!=r){ int mid=(l+r)>>1; lson[rt] = build(l,mid); rson[rt] = build(mid+1,r); } return rt; } int update(int rt,int pos) //把数组中的元素一次加入新的线段树中 { int nrt = tot++; int tmp = nrt; sum[nrt] = sum[rt]+1; int l=1,r=m; while(l<r) { int mid = (l+r)>>1; if(pos<=mid) { lson[nrt] = tot++; rson[nrt] = rson[rt]; nrt = lson[nrt]; rt = lson[rt]; r = mid; }else { rson[nrt] = tot++; lson[nrt] = lson[rt]; nrt = rson[nrt]; rt = rson[rt]; l=mid+1; } sum[nrt] = sum[rt]+1; } return tmp; } int query(int lrt,int rrt,int k) { int l=1,r=m; while(l<r) { int mid = (l+r)>>1; int cnt = sum[lson[rrt]] - sum[lson[lrt]]; if(cnt>=k) { r = mid; lrt = lson[lrt]; rrt = lson[rrt]; } else { l = mid+1; k-=cnt; lrt = rson[lrt]; rrt = rson[rrt]; } } return l; } int main() {//freopen("in.txt","r",stdin); scanf("%d%d",&n,&q);tot=0; for(int i=1;i<=n;i++) { scanf("%d",&A[i]); V.push_back(A[i]); } sort(V.begin(),V.end()); V.erase(unique(V.begin(),V.end()),V.end()); m=V.size(); T[0] = build(1,m); for(int i=1;i<=n;i++) { T[i] = update(T[i-1],getid(A[i])); } while(q--) { int x,y,k; scanf("%d%d%d",&x,&y,&k); printf("%d\n",V[query(T[x-1],T[y],k)-1]); } return 0; }
POJ 2528 Mayor's posters
题意:贴海报,海报可以覆盖,会给出你每张海报的长度及起始位置,然后问你最后还能看到几张海报。
#include<math.h> #include<string.h> #include<stdio.h> #include<algorithm> using namespace std; const int maxn=10005; int n; int vis[maxn<<3],sum[maxn<<4]; int li[maxn*2],ri[maxn*2],lsh[maxn<<2]; void pushdown(int rt) { sum[rt<<1]=sum[rt]; sum[rt<<1|1]=sum[rt]; sum[rt]=-1; } void update(int L,int R,int C,int l,int r,int rt) { if(L<=l&&r<=R) { sum[rt]=C; return ; } if(sum[rt]!=-1) pushdown(rt); int m=(l+r)>>1; if(m>=R) update(L,R,C,l,m,rt<<1); else if(L>m) update(L,R,C,m+1,r,rt<<1|1); else update(L,m,C,l,m,rt<<1),update(m+1,R,C,m+1,r,rt<<1|1); } int ans; void query(int l,int r,int rt) { if(!vis[sum[rt]]&&sum[rt]!=-1) { ans++; vis[sum[rt]]=1; return ; } if(l==r) { return ; } if(sum[rt]!=-1) pushdown(rt); int m=(l+r)>>1; query(l,m,rt<<1); query(m+1,r,rt<<1|1); } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d",&n); memset(sum,-1,sizeof(sum)); memset(vis,0,sizeof(vis)); int tot=0; for(int i=0;i<n;i++) { scanf("%d%d",&li[i],&ri[i]); lsh[tot++]=li[i]; lsh[tot++]=ri[i]; } sort(lsh,lsh+tot); int mm=unique(lsh,lsh+tot)-lsh; int tt=mm; for(int i=1;i<tt;i++) { if(lsh[i]-lsh[i-1]>1) lsh[mm++]=lsh[i-1]+1; } sort(lsh,lsh+mm); for(int i=0;i<n;i++) { int x=lower_bound(lsh,lsh+mm,li[i])-lsh; int y=lower_bound(lsh,lsh+mm,ri[i])-lsh; update(x,y,i,0,mm-1,1); } ans=0; query(0,mm-1,1); printf("%d\n",ans); } }
POJ 3264 Balanced Lineup
题意:找到一段数字里最大值和最小值的差
#include <cstdio> #include <algorithm> using namespace std; const int MAX_N = 5e4 + 5; const int INF = 0x3f3f3f3f; typedef pair<int, int> P; P dat[4 * MAX_N];//存储线段树的全局数组 int n; //初始化 void init(int N) { n = 1; while (n < N) n <<= 1;//简单起见,把元素个数扩大到2的幂 for (int i = 0; i < 2 * n - 1; ++i) { dat[i].first = INF;//存储区间最小值 dat[i].second = -INF;//存储区间最大值 } } //把第k个值更新为x void update(int k, int x) { k += n - 1; dat[k] = P(x, x); while (k > 0) {//向上更新 k = (k - 1) / 2; dat[k].first = min(dat[2 * k + 1].first, dat[2 * k + 2].first); dat[k].second = max(dat[2 * k + 1].second, dat[2 * k + 2].second); } } //查询 P query(int a, int b, int k, int l, int r) {//k是节点编号 if (a <= l && r <= b) return dat[k]; if (a > r || b < l) return P(INF, -INF); P vl = query(a, b, 2 * k + 1, l, (l + r) / 2); P vr = query(a, b, 2 * k + 2, (l + r) / 2 + 1, r); return P(min(vl.first, vr.first), max(vl.second, vr.second)); } int main() { int N, Q; scanf("%d%d", &N, &Q); init(N); for (int i = 0; i < N; ++i) { int x; scanf("%d", &x); update(i, x); } for (int i = 0; i < Q; ++i) { int a, b; scanf("%d%d", &a, &b); P p = query(a - 1, b - 1, 0, 0, n - 1); printf("%d\n", p.second - p.first); } return 0; }
还有一种基于稀疏表的 RMQ 的方法
预处理: 预处理是采用dp的思想,用f[i][j]表示区间[i,i+2^j-1]中的最大值 (即从i开始,长度为2^j的闭区间)。开始时,f[i][0] 就是区间[i][i]的值, 即f[i][0] = num[i],好了,初始值找到了,下面是状态转移方程: f[i][j] = max (f[i][j-1],f[i+2^(j-1)][j-1])。即把[i,i+2^j-1]区间分为 [i,i+2^(j-1)-1]和[j+2^(j-1),j+2^(j-1)+2^(j-1)-1]两个等长度的区间(区间长度都是2^(j-1)), 有了初始值和状态转移方程,我们可以自底向上递推出所有的f[i][j]的值。 边界值的处理: 由于区间最大长度为n,所以二维边界最大值为log(n)/log(2.0); 一维边界为i+2^j-1<=n。 查询: 假设要查询区间[a,b]的最大值,由于区间的长度很可能不是2的整数幂,所以 我们要把区间划分为长度为2的整数幂的两部分,而且这两个的并集必须是[a,b]。 为了实现这个方案,我们需要先求出一个最大k,使得2^k<=(b-a+1),这样就可以把 区间分为两部分[a,a+2^k-1]和[b-2^k+1,b],使它们既能不超过[a,b]区间的范围,又能 把区间全部覆盖。于是,[a,b]区间的最大值就等于上述两个区间的最大值中最大的那个。
#include <stdio.h> #include <math.h> #define MAXN 100 #define max(a,b) (a > b ? a : b) const int num[MAXN]; int dp[MAXN][20]; void create_max (int n) { int i,j,t; for (i = 1;i <= n;i++) { dp[i][0] = num[i]; } t = (int)(log(n) / log(2.0)); for (j = 1;j <= t;j++) { for (i = 1;i + (1 << j) - 1 <= n;i++) { dp[i][j] = max(dp[i][j-1],dp[i+(1 << (j-1))][j-1]); } } } int get_max (int a,int b) { int k = log(b - a + 1) / log(2.0); return max(dp[a][k],dp[b-(1 << k) + 1][k]); } int main () { int i,j,n,ans,a,b; scanf ("%d",&n); for (i = 1;i <= n;i++) { scanf ("%d",num+i); } create_max (n); scanf ("%d%d",&a,&b); ans = get_max (a,b); printf ("%d\n",ans); return 0; }
POJ 3368 Frequent values
题意:一个不降序列,让我们去区间询问区间最长连续相等序列。
// #include<bits/stdc++.h> #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> // for memset #include <vector> // push_back() // vector<int>().swap(v); #include <set> //multiset set<int,greater<int>> //big->small #include <map> #include <stack> // top() #include <queue> // front() // priority_queue<T,vector<T>,greater<T> > #include <cmath> // auto &Name : STLName Name. #include <utility> #include <sstream> #include <string> // __builtin_popcount (ans); // 获取某个数二进制位1的个数 #include <cstdlib> // rand() #define IOS ios_base::sync_with_stdio(0); cin.tie(0) #define lowbit(x) (x&(-x)) using namespace std; typedef long long ll; const int maxn = 100010; int num[maxn],cnt[maxn],dp[maxn][25]; int n,q,x,y; void ST(int n) { for(int i=1;i<=n;i++) dp[i][0]=cnt[i]; for(int j=1;j<=24;j++) for(int i=1;i+(1<<j)-1<=n;i++) dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } int query(int i,int j) { int k=log(j-i+1.0)/log(2.0); return max(dp[i][k],dp[j-(1<<k)+1][k]); } int main(void) { while(~scanf("%d%d",&n,&q)&&n) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { scanf("%d",&num[i]); if(i==1) cnt[i]=1; else if(num[i]==num[i-1]) cnt[i]=cnt[i-1]+1; else cnt[i]=1; } ST(n); while(q--) { scanf("%d%d",&x,&y); int now=x; while(now<=y&&num[now-1]==num[now]) now++; int ans=0; if(now<=y) ans=query(now,y); ans=max(ans,now-x); printf("%d\n",ans); } } return 0; }
POJ 1201 Intervals
题意:一个整数集合Z有n个区间,每个区间有3个值,ai,bi,ci代表,在区间[ai,bi]上至少有ci个整数属于集合Z,ci可以在区间内任意取不重复的点。现在要满足所有区间的约束条件,问最少可选多少个点。
思路:差分约束
// #include<bits/stdc++.h> #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> // for memset #include <vector> // push_back() // vector<int>().swap(v); #include <set> //multiset set<int,greater<int>> //big->small #include <map> #include <stack> // top() #include <queue> // front() // priority_queue<T,vector<T>,greater<T> > #include <cmath> // auto &Name : STLName Name. #include <utility> #include <sstream> #define IOS ios_base::sync_with_stdio(0); cin.tie(0) #define lowbit(x) (x&(-x)) using namespace std; typedef long long ll; const int maxn = 50005; const int inf = 0x3f3f3f3f; struct Edge { int v; int cost; Edge(int _v=0,int _cost=0):v(_v),cost(_cost){} }; vector<Edge> E[maxn]; void addedge(int u,int v,int w) { E[u].push_back(Edge(v,w)); } bool vis[maxn]; int cnt[maxn]; int dist[maxn]; bool SPFA(int start,int n,int *dist) { memset(vis,false,sizeof(vis)); for(int i=1;i<=n;i++) dist[i]=-inf; vis[start]=true; dist[start]=0; queue<int>que; while(!que.empty()) que.pop(); que.push(start); memset(cnt,0,sizeof(cnt)); cnt[start]=1; while(!que.empty()) { int u=que.front(); que.pop(); vis[u]=false; for(int i=0;i<E[u].size();i++) { int v=E[u][i].v; if(dist[v]<dist[u]+E[u][i].cost) { dist[v]=dist[u]+E[u][i].cost; if(!vis[v]) { vis[v]=true; que.push(v); if(++cnt[v]>n) return false; } } } } return true; } void init() { for(int i=0;i<maxn;i++) E[i].clear(); } int main(void) { int n; while(~scanf("%d",&n)) { init(); int a,b,c,s=inf,e=-1; for(int i=0;i<n;i++) { scanf("%d%d%d",&a,&b,&c); addedge(a,b+1,c); s=min(s,a); e=max(e,b+1); } for(int i=s;i<e;i++) { addedge(i,i+1,0); addedge(i+1,i,-1); } SPFA(s,e,dist); printf("%d\n",dist[e]); } return 0; }
还有一种方法就是贪心算法,用线段树优化
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define MMAX 50005 using namespace std; int sum[MMAX*4],setv[MMAX*4]; int t,_sum; struct node { int l,r,v; } line[MMAX]; bool cmp(node a,node b) { return a.r<b.r; } void build(int rt,int L,int R) { if(L==R) { sum[rt]=1; } else { int M=(L+R)/2; build(rt*2,L,M); build(rt*2+1,M+1,R); sum[rt]=sum[rt*2]+sum[rt*2+1]; } } void update(int rt,int L,int R,int ql,int qr) { if(t==0) return ; // if(sum[rt]==0) return ; if(ql<=L&&qr>=R&&sum[rt]&&t>=sum[rt]) { t-=sum[rt]; sum[rt]=0; return ; } if(L==R) return ; if(sum[rt]==0) { int lc=rt*2,lr=rt*2+1; sum[lr]=sum[lc]=0; } int M=(L+R)/2; if(qr>M) update(rt*2+1,M+1,R,ql,qr); if(ql<=M) update(rt*2,L,M,ql,qr); sum[rt]=sum[rt*2+1]+sum[rt*2]; } void query(int rt,int L,int R,int ql,int qr) { if(sum[rt]==0) return ; if(ql<=L&&qr>=R) { _sum+=sum[rt]; } else { if(sum[rt]==0) { int lc=rt*2,lr=rt*2+1; sum[lr]=sum[lc]=0; } int M=(L+R)/2; if(ql<=M) query(rt*2,L,M,ql,qr); if(qr>M) query(rt*2+1,M+1,R,ql,qr); } } int main() { int n; while(~scanf("%d",&n)) { memset(setv,-1,sizeof(setv)); int Max=0; for(int i=0; i<n; i++) { scanf("%d%d%d",&line[i].l,&line[i].r,&line[i].v); Max=max(Max,line[i].r); } build(1,0,Max); sort(line,line+n,cmp); for(int i=0; i<n; i++) { _sum=0; query(1,0,Max,line[i].l,line[i].r); // cout<<_sum<<endl; _sum=line[i].v-(line[i].r-line[i].l+1-_sum); if(_sum<=0) continue; t=_sum; update(1,0,Max,line[i].l,line[i].r); } _sum=0; query(1,0,Max,0,Max); printf("%d\n",Max-_sum+1); } return 0; }
POJ 3470 Walls
题意:有N堵水平于坐标轴的墙,以及M只小鸟,小鸟一定会朝离自己最近的墙飞去撞晕自己,且只能飞向平行于坐标轴的 4个方向,撞到墙的边缘也算合理冲撞(即是说可以往延长线飞)。求各堵墙上各撞了几只小鸟。N, M <= 50000。
思路:我们将所有的二维坐标离散,对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; }
UVA 11990 ''Dynamic'' Inversion
题意:给你n个数,然后有m次操作,每次操作可以删除一个数, 然后问你每次删除之前,还有多少个逆序对