线段树和平方分割

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次操作,每次操作可以删除一个数, 然后问你每次删除之前,还有多少个逆序对

 

posted @ 2020-05-26 21:30  ジャスミン  阅读(142)  评论(0编辑  收藏  举报