1
23
2016
0

【bzoj3932】【CQOI2015】任务查询系统

3932: [CQOI2015]任务查询系统

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 943  Solved: 345
[Submit][Status][Discuss]

Description

最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。
超级计算机中的任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。
 

Input

输入文件第一行包含两个空格分开的正整数m和n,分别表示任务总数和时间范围。
接下来m行,每行包含三个空格分开的正整数Si、Ei和Pi(Si≤Ei),描述一个任务。 
接下来n行,每行包含四个空格分开的整数Xi、Ai、Bi和Ci,描述一次查询。查询的参数Ki需要由公式 Ki=1+(Ai*Pre+Bi) mod Ci
计算得到。其中Pre表示上一次查询的结果,对于第一次查询,Pre=1。
 

Output

输出共n行,每行一个整数,表示查询结果。
 

Sample Input

4 3
1 2 6
2 3 3
1 3 2
3 3 4
3 1 3 2
1 1 3 4
2 2 4 3

Sample Output

2
8
11

HINT

 

样例解释

K1 = (1*1+3)%2+1 = 1

K2 = (1*2+3)%4+1 = 2

K3 = (2*8+4)%3+1 = 3

对于100%的数据,1≤m,n,Si,Ei,Ci≤100000,0≤Ai,Bi≤100000,1≤Pi≤10000000,Xi为1到n的一个排列
 

我们对每个时间建一颗线段树,线段树上的区间表示优先级,每个区间记录任务个数和优先级总和。时间i只要在时间i-1的基础上,加上开始时间为i的任务,减去结束时间为i-1的任务,这道题就A啦、、、

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
struct node{ll l,r,num,sum;}tr[10000010];
ll n,m,cnt,n1,a[200010],b[200010],c[200010],d[200010],P[200010],root[200010];
ll edgenum,vet[200010],next[200010],head[200010];
ll t,A,B,C,k,ans;
bool cmp(ll x,ll y){return c[x]<c[y];}
void add(ll u,ll v)
{
    vet[++edgenum]=v;
    next[edgenum]=head[u];
    head[u]=edgenum;
}
void change(ll &p,ll l,ll r,ll x,ll val)
{
    tr[++cnt]=tr[p];p=cnt;
    if (val>0){tr[p].num++;tr[p].sum+=c[P[val]];}else{tr[p].num--;tr[p].sum-=c[P[-val]];}
    if (l==r) return;
    ll mid=(l+r)>>1;
    if (x<=mid) change(tr[p].l,l,mid,x,val);else change(tr[p].r,mid+1,r,x,val);
}
ll ask(ll p,ll l,ll r,ll k)
{
    if (l==r) return tr[p].sum;
    ll mid=(l+r)>>1,tmp=tr[tr[p].l].num;
    if (k<=tmp) return ask(tr[p].l,l,mid,k);else return tr[tr[p].l].sum+ask(tr[p].r,mid+1,r,k-tmp);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++)
    {
        scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
        P[i]=i;
    }
    sort(P+1,P+n+1,cmp);
    for (ll i=1;i<=n;i++) d[P[i]]=i;
    for (ll i=1;i<=n;i++){add(a[i],d[i]);add(b[i]+1,-d[i]);}
    for (ll i=1;i<=m;i++)
    {
        root[i]=root[i-1];
        for (ll e=head[i];e;e=next[e])
            change(root[i],1,m,(vet[e]>0)? vet[e]:-vet[e],vet[e]);
    }
    ans=1;
    for (ll i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld%lld",&t,&A,&B,&C);
        ll k=1+(A*ans+B)%C;
        k=min(k,tr[root[t]].num);
        ans=ask(root[t],1,m,k);
        printf("%lld\n",ans);
    }
}
Category: 主席树 | Tags:
12
27
2015
0

【bzoj3295】【Cqoi2011】动态逆序对

3295: [Cqoi2011]动态逆序对

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 2280  Solved: 725
[Submit][Status][Discuss]

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

Input

输入第一行包含两个整数nm,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。
 

Output

 
输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1
样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。

HINT

N<=100000 M<=50000

树状数组包线段树。。。

我们令删除的数为x,位置在loc。

首先容易想到的就是找到1~(loc-1)大于x的个数,和(loc+1)~n小于x的个数。这就是裸的主席树上加树状数组。但是这样内存是Nlog2N,是会炸的。于是我们换一种维护方式,我们先预处理出每个位置的front和back,front是指位置在其之前且值大于其值的个数,back是指位置在其之后且值小于其值的个数。

那么我们需要在主席树上维护就是删去的数。这样的内存是Mlog2N的,就可以解决这道题了。

代码:

 

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m,x,loc,cnt;
int L[35],R[35],front[100010],back[100010],b[100010],root[100010],pos[100010],a[100010];
ll ans;
struct node{int l,r;ll sum;}tr[8000010];
int ask(int x){int ans=0;for (;x;x-=x&-x) ans+=b[x];return ans;}
void add(int x){for (;x<=n;x+=x&-x) b[x]++;}
ll ask2(int l,int r,int x)
{
	if (l>r) return 0;
	l--;
	L[0]=R[0]=0;
	for (int i=l;i;i-=i&-i)
		L[++L[0]]=root[i];
	for (int i=r;i;i-=i&-i)
		R[++R[0]]=root[i];
	l=1;r=n;
	ll sum=0;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (x<=mid)
		{
			for (int i=1;i<=R[0];i++) sum+=tr[tr[R[i]].r].sum;
			for (int i=1;i<=L[0];i++) sum-=tr[tr[L[i]].r].sum;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].l;
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].l;
			r=mid;
		}else
		{
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].r;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].r;
			l=mid+1;
		}
	}
	return sum;
}
ll ask1(int l,int r,int x)
{
	if (l>r) return 0;
	l--;
	L[0]=R[0]=0;
	for (int i=l;i;i-=i&-i)
		L[++L[0]]=root[i];
	for (int i=r;i;i-=i&-i)
		R[++R[0]]=root[i];
	l=1;r=n;
	ll sum=0;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (x>mid)
		{
			for (int i=1;i<=R[0];i++) sum+=tr[tr[R[i]].l].sum;
			for (int i=1;i<=L[0];i++) sum-=tr[tr[L[i]].l].sum;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].r;
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].r;
			l=mid+1;
		}else
		{
			for (int i=1;i<=L[0];i++) L[i]=tr[L[i]].l;
			for (int i=1;i<=R[0];i++) R[i]=tr[R[i]].l;
			r=mid;
		}
	}
	return sum;
}
void change(int &p,int l,int r,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(tr[p].l,l,mid,x);else change(tr[p].r,mid+1,r,x);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		pos[a[i]]=i;
		front[i]=i-1-ask(a[i]);
		ans+=front[i];
		add(a[i]);
	}
	memset(b,0,sizeof(b));
	for (int i=n;i>=1;i--)
	{
		back[i]=ask(a[i]);
		add(a[i]);
	}
	while (m--)
	{
		printf("%lld\n",ans);
		scanf("%d",&x);
		loc=pos[x];
		ans-=front[loc]-ask2(1,loc-1,x);
		ans-=back[loc]-ask1(loc+1,n,x);
		for (;loc<=n;loc+=loc&-loc) change(root[loc],1,n,x);
	}
}
Category: 主席树 | Tags:
12
18
2015
0

【JSOI2015】最小生成树

难以描述的痛苦。。

我们先预处理出边权大于等于x的边形成的最小生成树或最小生成森林,这些边用主席树存。

那么,如何维护呢,我们从边权从大到小枚举,每次在原来基础上新加一条边后,如果产生了环,就把环中最大的边删去。这样就可以用主席树来维护。

然后对于每个询问l,r,我们只要在root[l]这棵主席树上求边权小于等于r的边权和,这很容易吧。。

这样本题就结束了。orz Gold_7 first blood!!!

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,q,A,p,d,edgenum,cnt,Max,l,r,ans;
int vet[2000010],next[2000010],head[2000010],a[2000010],f[2000010],flag[2000010],root[2000010],e[2000010],dis[2000010];
struct tree{int l,r,sum;}tr[20000010];
struct node{int x,y,z;}ed[2000010];
bool cmp(const node a,const node b){return a.z<b.z;}
int getfather(int x){if (x!=f[x]) f[x]=getfather(f[x]);return f[x];}
void change(int &p,int l,int r,int x,int val)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum+=val;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) change(tr[p].l,l,mid,x,val);else change(tr[p].r,mid+1,r,x,val);
}
int ask(int p,int l,int r,int x)
{
    if (r<=x) return tr[p].sum;
    int mid=(l+r)>>1;
    if (x<=mid) return ask(tr[p].l,l,mid,x);else return ask(tr[p].l,l,mid,x)+ask(tr[p].r,mid+1,r,x);
}
void add(int u,int v,int w)
{
    edgenum++;
    vet[edgenum]=v;
    next[edgenum]=head[u];
    head[u]=edgenum;
    dis[edgenum]=w;
}
int dfs(int u,int t,int fa)
{
    if (u==t&&fa!=-1) return 1;
    for (int e=head[u];e;e=next[e])
    {
        int v=vet[e];
        if ((e+1)/2==fa) continue;
        if (dfs(v,t,(e+1)/2)){flag[(e+1)/2]=1;return 1;}
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf("%d%d%d",&ed[i].x,&ed[i].y,&ed[i].z);
    sort(ed+1,ed+m+1,cmp);
    for (int i=1;i<=n;i++) f[i]=i;
    for (int i=1;i<=m;i++) e[i]=ed[i].z;
    for (int i=m;i>=1;i--)
    {
        root[i]=root[i+1];
        if (getfather(ed[i].x)!=getfather(ed[i].y))
        {
            f[getfather(ed[i].x)]=getfather(ed[i].y);
            a[++a[0]]=i;
            change(root[i],1,m,i,ed[i].z);
        }else
        {
            Max=-1;
            edgenum=0;
            for (int j=1;j<=n;j++) head[j]=0;
            for (int j=1;j<=a[0];j++)
            {
                add(ed[a[j]].x,ed[a[j]].y,ed[a[j]].z);
                add(ed[a[j]].y,ed[a[j]].x,ed[a[j]].z);
            }
            add(ed[i].x,ed[i].y,ed[i].z);
            add(ed[i].y,ed[i].x,ed[i].z);
            for (int j=1;j<=edgenum/2;j++)
                flag[j]=0;
            dfs(ed[i].x,ed[i].x,-1);
            for (int j=1;j<=edgenum/2;j++)
            if (flag[j]) Max=max(Max,dis[j*2]);
            for (int j=1;j<=a[0];j++)
                if (ed[a[j]].z==Max&&flag[j])
                {
                    change(root[i],1,m,a[j],-ed[a[j]].z);
                    for (int k=j;k<a[0];k++) a[k]=a[k+1];
                    a[0]--;
                    break;
                }
            a[++a[0]]=i;
            change(root[i],1,m,i,ed[i].z);
        }
    }
    scanf("%d%d",&q,&A);
    while (q--)
    {
        scanf("%d%d",&p,&d);
        l=ans*A+p;r=l+d;
        l=lower_bound(e+1,e+m+1,l)-e;
        r=upper_bound(e+1,e+m+1,r)-e-1;
        ans=ask(root[l],1,m,r);
        printf("%d\n",ans);
    }
}
Category: 主席树 | Tags:
12
6
2015
0

【bzoj1901】Zju2112 Dynamic Rankings

1901: Zju2112 Dynamic Rankings

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 5687  Solved: 2361
[Submit][Status][Discuss]

Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Input

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Output

 

Sample Input

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

Sample Output

3
6

HINT

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

升级版的主席树。本道题在普通的主席树上增加了修改这个操作,然而普通的主席树是无法接受的。因此我们想办法去改变主席树,考虑到一个数字序列,维护前缀我们用的是树状数组。以此类比,我们是否可以把一棵棵树当做一个个的数,然后用树状数组去维护,显然这是可行的,因此每改变一棵树,就要操作log次,因此修改的操作就变成了log2n的,对于本题的数据范围可以说轻取,询问操作也同理,一次询问需要log2n的复杂度。因此我们用nlog2n的总复杂K.O.了本题。

代码:

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,tot,cnt,t1,t2;
int a[10010],p[20010],x[10010],y[10010],z[10010],root[10010],L[10010],R[10010];
struct node{int l,r,sum;}tr[5000010];
char s[10];
void change(int l,int r,int &p,int x,int val)
{
    tr[++cnt]=tr[p];p=cnt;tr[p].sum+=val;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) change(l,mid,tr[p].l,x,val);else change(mid+1,r,tr[p].r,x,val);
}
int ask(int l,int r,int k)
{
    if (l==r) return l;
    int sum1=0,sum2=0;
    for (int i=1;i<=t1;i++)
        sum1+=tr[tr[L[i]].l].sum;
    for (int i=1;i<=t2;i++)
        sum2+=tr[tr[R[i]].l].sum;
    int mid=(l+r)>>1;
    if (sum2-sum1>=k)
    {
        for (int i=1;i<=t1;i++)
            L[i]=tr[L[i]].l;
        for (int i=1;i<=t2;i++)
            R[i]=tr[R[i]].l;
        return ask(l,mid,k);
    }else
    {
        for (int i=1;i<=t1;i++)
            L[i]=tr[L[i]].r;
        for (int i=1;i<=t2;i++)
            R[i]=tr[R[i]].r;
        return ask(mid+1,r,k-sum2+sum1);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        p[++tot]=a[i];
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s);
        if (s[0]=='C'){scanf("%d%d",&x[i],&y[i]);p[++tot]=y[i];}else
        {scanf("%d%d%d",&x[i],&y[i],&z[i]);}
    }
    sort(p+1,p+tot+1);
    tot=unique(p+1,p+tot+1)-p-1;
    for (int i=1;i<=n;i++)
    {
        a[i]=lower_bound(p+1,p+tot+1,a[i])-p;
        for (int j=i;j<=n;j+=((j)&(-j)))
            change(1,tot,root[j],a[i],1);
    }
    for (int i=1;i<=m;i++)
    {
        if (z[i])
        {
            x[i]--;
            t1=t2=0;
            for (int j=x[i];j;j-=((j)&(-j)))
                L[++t1]=root[j];
            for (int j=y[i];j;j-=((j)&(-j)))
                R[++t2]=root[j];
            printf("%d\n",p[ask(1,tot,z[i])]);
        }else
        {
            y[i]=lower_bound(p+1,p+tot+1,y[i])-p;
            for (int j=x[i];j<=n;j+=((j)&(-j)))
                change(1,tot,root[j],a[x[i]],-1);
            for (int j=x[i];j<=n;j+=((j)&(-j)))
                change(1,tot,root[j],y[i],1);
            a[x[i]]=y[i];
        }
    }
}
Category: 主席树 | Tags:
11
29
2015
0

【bzoj2588】【SPOJ10628】Count on a tree

2588: Spoj 10628. Count on a tree

Time Limit: 12 Sec  Memory Limit: 128 MB
Submit: 2926  Solved: 667
[Submit][Status][Discuss]

Description

给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。

Input

第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v,k),表示一组询问。

Output

M行,表示每个询问的答案。

Sample Input

8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
0 5 2
10 5 3
11 5 4
110 8 2

Sample Output

2
8
9
105
7

HINT

HINT:
N,M<=100000
暴力自重。。。

Source

又一道主席树。

我们把Ti这棵线段树表示i到根节点的线段树,那么Ti可以由Tfa[i]得到,处理好线段树后,就可以解决询问了,对于每个询问u,v,k,只要根据tr[p1].sum+tr[p2].sum-tr[lca].sum-tr[fa[lca]].sum二分答案就可以了。

完美解决。

代码:

 

#include<cstdio>
#include<algorithm>
#define N 100010
using namespace std;
int n,n1,m,u,v,x,y,k,cnt,edgenum,ans;
int a[N],b[N],vet[2*N],next[2*N],head[N],f[N],d[N],son[N],num[N],top[N],root[N];
struct node{int l,r,sum;}tr[20*N];
void add(int u,int v)
{
	edgenum++;
	vet[edgenum]=v;
	next[edgenum]=head[u];
	head[u]=edgenum;
}
void dfs(int u,int fa,int dep)
{
	f[u]=fa;
	d[u]=dep;
	num[u]=1;
	int maxnum=0;
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=fa)
		{
			dfs(v,u,dep+1);
			num[u]+=num[v];
			if (num[v]>maxnum){maxnum=num[v];son[u]=v;}
		}
	}
}
void dfs(int u,int number)
{
	top[u]=number;
	if (!son[u]) return;
	dfs(son[u],number);
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=f[u]&&v!=son[u]) dfs(v,v);
	}
}
int lca(int u,int v)
{
	while (top[u]!=top[v])
	{
		if (d[top[u]]<d[top[v]]) swap(u,v);
		u=f[top[u]];
	}
	if (d[u]<d[v]) return u;else return v;
}
void change(int l,int r,int &p,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(l,mid,tr[p].l,x);else change(mid+1,r,tr[p].r,x);
}
int ask(int x,int y,int k)
{
	int p1=root[x],p2=root[y],p3=root[lca(x,y)],p4=root[f[lca(x,y)]];
	int l=1,r=n;
	while (l<r)
	{
		int mid=(l+r)>>1;
		int t=tr[tr[p1].l].sum+tr[tr[p2].l].sum-tr[tr[p3].l].sum-tr[tr[p4].l].sum;
		if (t>=k) {p1=tr[p1].l;p2=tr[p2].l;p3=tr[p3].l;p4=tr[p4].l;r=mid;}else
		{p1=tr[p1].r;p2=tr[p2].r;p3=tr[p3].r;p4=tr[p4].r;k-=t;l=mid+1;}
	}
	return l;
}
void dfs(int u)
{
	root[u]=root[f[u]];
	change(1,n,root[u],a[u]);
	for (int e=head[u];e;e=next[e])
	{
		int v=vet[e];
		if (v!=f[u]) dfs(v);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	n1=unique(b+1,b+n+1)-b-1;
	for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n1+1,a[i])-b;
	for (int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	dfs(1,0,1);
	dfs(1,1);
	dfs(1);
	while (m--)
	{
		scanf("%d%d%d",&x,&y,&k);
		ans=b[ask(x^ans,y,k)];
		printf("%d",ans);
		if (m) printf("\n");
	}
}
Category: 主席树 | Tags:
11
28
2015
1

初探主席树

先膜拜一下初三大爷zyd,毕竟是他的点拨,再膜拜一下即将摘金牌的lyz大爷,毕竟也有他的帮助。

好了,现在开始讲主席树。听说这东西很多别名,比如,可持久化线段树函数式版本的线段树……他的主要思想就是存许多棵线段树,存的是一段数字区间出现次数。

对于每个前缀1...i,我们建一棵线段树Ti,存的是数字区间出现的次数。但这会MLE,然后我们又可以发现,Ti与Ti-1两棵线段树就相差了i这个点的值,所以只改变了logn个点,因此,Ti只要在Ti-1的基础上在最多建logn个,其他的直接指向Ti-1上的点就可以了,这样就解决了内存的问题,因此,主席树成功建完。

现在要用主席树来解决实际问题啦

一个经典问题是给你n个数,每次询问区间l-r第k大值。

假设我们现在已经离线处理好所有的线段树,我们只要在Tr与Tl-1两棵树上解决这个问题,我们令root[i]为Ti的根节点编号,令p1为当前在Tl-1上的节点编号,p2为当前在Tr上的节点编号,由于任意两棵线段树的大小形态都是完全一样的,因此p1,p2表示的数字区间是完全一样。我们令tr[p].sum为编号为p的数字区间出现次数,tr[p].l为p的左儿子的编号,tr[p].r为p的右儿子的节点编号。如果num[tr[p2].l]-num[tr[p1].l]>=k那么第k大数在左儿子的区间里,那么就往左儿子递归找第k大,否则就往右儿子递归找第(k-num[tr[p2].l]+num[tr[p1].l])大(因为要把左儿子的次数减掉)。因此这个经典问题就在nlogn的复杂度里完美解决了。

最后附上这个题的代码。

 

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,cnt,a[100010],b[100010],root[100010],n1,x,y,k;
struct node{int l,r,sum;}tr[400010];
void change(int l,int r,int &p,int x)
{
	tr[++cnt]=tr[p];p=cnt;tr[p].sum++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) change(l,mid,tr[p].l,x);else change(mid+1,r,tr[p].r,x); 
}
int ask(int p1,int p2,int l,int r,int k)
{
	if (l==r) return l;
	int t=tr[tr[p2].l].sum-tr[tr[p1].l].sum;
	int mid=(l+r)>>1;
	if (t>=k) return ask(tr[p1].l,tr[p2].l,l,mid,k);else return ask(tr[p1].r,tr[p2].r,mid+1,r,k-t);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	n1=unique(b+1,b+n+1)-b-1;
	for (int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n1+1,a[i])-b;//离散化
	for (int i=1;i<=n;i++){root[i]=root[i-1];change(1,n,root[i],a[i]);} //构建主席树
	while (m--)
	{
		scanf("%d%d%d",&x,&y,&k);
		printf("%d\n",b[ask(root[x-1],root[y],1,n,k)]);
	}
}
Category: 主席树 | Tags:

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com