[BZOJ2164]采矿【模拟+树链剖分+线段树】

Online JudgeBzoj2164

Label:模拟,树链剖分,线段树

题目描述

浩浩荡荡的cg大军发现了一座矿产资源极其丰富的城市,他们打算在这座城市实施新的采矿战略。这个城市可以看成一棵有n个节点的有根树,我们把每个节点用1到n的整数编号。为了方便起见,对于任何一个非根节点v,它任何一个祖先的编号都严格小于v。树上的每个节点表示一个矿点,每条边表示一条街道。作为cg大军的一个小队长,你拥有m个部下。你有一张二维的动态信息表,用Ti,j表示第i行第j列的数据。当你被允许开采某个区域时,你可以将你的部下分配至各个矿点。在第i个矿点安排j个人可以获得Ti,j单位的矿产。允许开采的区域是这样描述的:给你一对矿点(u,v),保证v是u的祖先(这里定义祖先包括u本身);u为你控制的区域,可以在以u为根的子树上任意分配部下;u到v的简单路径(不包括u但包括v,若u=v则包括u)为探险路径,在该路径上你可以选择至多一个矿点安排部下。你这次开采的收益为安排有部下的矿点的收益之和。

对于100%的数据

(1≤n≤20000)(1≤m≤50)(1≤C≤2000。)

对于满足2≤i≤n的整数i,(1≤Fi<i)(1≤A,B≤2^{31}-1)(1≤Q≤10000)

输入

输入的第一行包含5个正整数n、m、A、B、Q。n为矿点的个数,m为部下的数量。A、B、Q是与动态信息表有关的数据。第二行包含n-1个正整数,第i个数为Fi+1,表示节点i+1的父亲。接下来需要你用下文的方法依次生成n组数据,每组数据共m个。其中第i组的m个数据为信息表中第i行的m个数据。紧接着一行包含一个正整数C,表示事件的数量。最后给出C行,每行描述一个事件。每个事件会先给出一个0或1的整数。如果该数为0,则后面有一个正整数p,表示动态信息表有更新,你需要生成一组m个数据,来替换信息表中第p行的m个数据。如果该数为1,则后面有两个正整数u、v,表示出现了一个你可以开采的区域,你需要回答这次开采的收益。同一行的各个数之间均用一个空格隔开,没有多余的空格和换行。

数据的生成方法如下:每次生成一组m个从小到大排列的数据,替换动态信息表的一行。其中,从小到大第j个数替换信息表中第j列的数。调用以下代码m次并排序得到一组数据。(注意可能会出现重复的数)函数GetInt A←((A xor B)+(B div X)+(B * X))and Y B←((A xor B)+(A div X)+(A * X))and Y 返回(A xor B)mod Q 其中A、B、Q均用32位有符号整数保存(C/C++的signed long int类型,pascal的longint类型),X=(2^{16})(2的16次方),Y=(2^{31}-1)(2的31次方-1),xor为位异或运算,div为整除运算,and为位且运算,mod为取余运算。由于只保留了低31位,易得我们不用考虑数据的溢出问题。(注意每次A和B都会被改变)

输出

对于每个开采事件(开头为1的事件),输出一行一个整数,为每次的收益。

样例

Input

10 5 1 2 10
1 1 3 3 4 4 6 6 9
4
1 6 3
1 9 1
0 1
1 1 1

Output

1
9
12

题解

题面信息比较乱。下面大致理了一下题意

有一座城市是一个含n个节点的有根树,且保证祖先编号小于子孙编号。

你有m个人,在第i个点安排j个人可以获得Tij的收益(Tij用一张二维表给出)。但你安排的位置有限制。如果给你的矿点为(u,v)(v为u的祖先),则你可以在u的子树中随意安排人;而在vu路径上你最多只能选一个矿点放人(当u!=v时不包括u)。其他任何地方都不能放人。请你求出最大的收益。

第一行是(n,m,A,B,Q)。(其中(A,B,Q)用于生成数据)

第二行给n-1个数,第i个数表示节点i+1的父亲。

第三行一个整数(C),表示操作数。

接下来的(C)行,如果改行开头数字为(0)请你按题面要求更新(Tij)表,如果为(1)后面的一对整数((u,v))表示当前可开采矿点。

对于每一个(1)操作,请输出当时的最大收益。至于数据生成的方式看原题面。

(n,m)的数据范围其实不太大。(1≤n≤20000)(1≤m≤50)(1≤C≤2000。)

我们先按暴力思路模拟一遍,不考虑更新什么的,每次询问都重新做一遍。

对于当前的询问((u,v)):先去解决(u)的子树,这一部分用树形Dp解决,定义状态(dp[x][i])表示在以(x)为根的子树中排布了(i)个人的最大收益(由于n,m乘积不大,空间没什么问题)。转移时用背包Dp的方式转移就好了,这一遍树形Dp下来时间复杂度为(O(Ncdot M^2))。再去考虑uv路径,直接最暴力的(O(N))往上跳,枚举在当前点(x)排布的人数(i),用(T[x][i])来更新答案,(ans=max(ans,dp[u][m-i]+T[x][i]))

综上,最暴力的去搞,单次操作的复杂度为(O(Ncdot M^2+N cdot M))


这样子看,单次操作的时间复杂度似乎还过得去,重点就是有多组操作、还会随时更新(Tij)表。

既然是树上问题,很容易想到用线段树去维护。

按上面的暴力思路再走一遍。对于询问((u,v)),先来解决(u)的子树。仍然考虑树形Dp,只是将所有值封装起来放节点上,用(dfs)将子树表示在区间上,用线段树完成区间的合并操作。

搞一个结构体(Mine)来封装。

struct Mine{
    int a[52];//x.a[i] (1<=i<=m) 表示在x这个点放i个人的最大收益
    void init(){//初始化,更新数据
        for(int i=1;i<=m;++i)a[i]=getnum();
		sort(a+1,a+m+1);
    }
    Mine operator +(const Mine &b)const{//运算符重载,表示合并操作
        Mine res;memset(res.a,0,sizeof(res.a));
        for(register int i=1;i<=m;++i)for(register int j=0;j<=i;++j)
        res.a[i]=max(res.a[i],a[j]+b.a[i-j]);
		return res;
    }
};

对应的建树操作如下,线段树上每个节点(node)上存两个值,summa(注意到都是用上面的结构体(Mine)表示的)。

sum是现在求子树要用的,ma是待会弄uv路径用的。

对于线段树上的每个节点(node),设其对应区间为([l,r]),则sum是dfs序在[l,r]间的所有树上的节点合并后的结果,也就是只在这些点中放人的最大收益;而ma是在这些点中的一个点,放人的最大收益(你大概知道接下来怎么求路径上的贡献了,就是树剖往上跳,然后线段树区间找个最值ma)。

struct node{
    Mine ma,sum;
}b[N<<2];

inline Mine Max(Mine x,Mine y){
    for(int i=1;i<=m;++i)x.a[i]=max(x.a[i],y.a[i]);
    return x;
}

inline void Up(int o){
    b[o].ma=Max(b[o<<1].ma,b[o<<1|1].ma);
    b[o].sum=b[o<<1].sum+b[o<<1|1].sum;
}

void build(int o,int l,int r){
	if(l==r){
		b[o].ma=b[o].sum=st[l];
		return;
	}
	int mid=l+r>>1;
	build(o<<1,l,mid);build(o<<1|1,mid+1,r);
	Up(o);
}

那么对于u的子树,每次操作时查询个区间和sum就好了。

而求uv路径的贡献,就是上面剧透的那样,直接套个树链剖分(O(logN))的时间内往上跳,然后查询区间最值,并更新答案。

完整代码如下,注意细节,注意卡常。

#include<bits/stdc++.h>
#define N 20010
using namespace std;
const int X=1<<16,Y=(1<<31)-1;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
int n,m,A,B,C;


struct edge{
	int to,nxt;
}e[N<<1];
int head[N],cnt;
inline void link(int u,int v){
	e[++cnt].to=v,e[cnt].nxt=head[u];
	head[u]=cnt;
}

inline int getnum(){
    A=((A^B)+B/X+B*X)&Y;
	B=((A^B)+A/X+A*X)&Y;
	return (A^B)%C;
}
struct Mine{
    int a[52];
    void init(){
        for(int i=1;i<=m;++i)a[i]=getnum();
		sort(a+1,a+m+1);
    }
    Mine operator +(const Mine &b)const{
        Mine res;memset(res.a,0,sizeof(res.a));
        for(register int i=1;i<=m;++i)for(register int j=0;j<=i;++j)
        res.a[i]=max(res.a[i],a[j]+b.a[i-j]);
		return res;
    }
}st[N];
inline Mine Max(Mine x,Mine y){
    for(int i=1;i<=m;++i)x.a[i]=max(x.a[i],y.a[i]);
    return x;
}

struct node{
    Mine ma,sum;
}b[N<<2];

inline void Up(int o){
    b[o].ma=Max(b[o<<1].ma,b[o<<1|1].ma);
    b[o].sum=b[o<<1].sum+b[o<<1|1].sum;
}

void build(int o,int l,int r){
	if(l==r){
		b[o].ma=b[o].sum=st[l];
		return;
	}
	int mid=l+r>>1;
	build(o<<1,l,mid);build(o<<1|1,mid+1,r);
	Up(o);
}
void change(int o,int l,int r,int x){
    if(l==r){
		b[o].ma.init();
		b[o].sum=b[o].ma;
		return;
	}
    int mid=l+r>>1;
    if(x<=mid)change(o<<1,l,mid,x);
    else change(o<<1|1,mid+1,r,x);
	Up(o);
}
Mine asksum(int o,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr)return b[o].sum;
	int mid=l+r>>1;
    if(qr<=mid)return asksum(o<<1,l,mid,ql,qr);
    if(ql>mid)return asksum(o<<1|1,mid+1,r,ql,qr);
    return asksum(o<<1,l,mid,ql,mid)+asksum(o<<1|1,mid+1,r,mid+1,qr);	
}
Mine askma(int o,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr)return b[o].ma;
	int mid=l+r>>1;
    if(qr<=mid)return askma(o<<1,l,mid,ql,qr);
    if(ql>mid)return askma(o<<1|1,mid+1,r,ql,qr);
    return Max(askma(o<<1,l,mid,ql,mid),askma(o<<1|1,mid+1,r,mid+1,qr));
}

int L[N],R[N],fa[N],dfn=0;
int son[N],dep[N],top[N],sz[N];
void dfs(int x){
    sz[x]=1;
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
		fa[y]=x;dep[y]=dep[x]+1;
		dfs(y);
        sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
    }
}
void redfs(int x,int tp){
	L[x]=++dfn;top[x]=tp;
    if(son[x])redfs(son[x],tp);
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
		if(y!=son[x])redfs(y,y);
    }
	R[x]=dfn;
}
inline Mine jump(int x,int y){
	Mine res;memset(res.a,0,sizeof(res));
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        res=Max(res,askma(1,1,n,L[top[x]],L[x]));
		x=fa[top[x]];
    }
	if(L[x]>L[y])swap(x,y);
    res=Max(res,askma(1,1,n,L[x],L[y]));
	return res;
}
int main(){
	n=read(),m=read(),A=read(),B=read(),C=read();
	for(int i=2;i<=n;i++){
		int u=read();link(u,i);
	}
	
	dfs(1);redfs(1,1);
	
	for(int i=1;i<=n;i++)st[L[i]].init();
	
	build(1,1,n);
	
	int T=read();
    while(T--){
        int op=read(),x=read();
		if(op==0)change(1,1,n,L[x]);
		else{
			int y=read();
			Mine tmp=asksum(1,1,n,L[x],R[x]);
			if(x!=y)tmp=tmp+jump(fa[x],y);
			printf("%d
",tmp.a[m]);
		}	
	}
}
原文地址:https://www.cnblogs.com/Tieechal/p/11549623.html