Online Judge:Bzoj2164
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)上存两个值,sum
和ma
(注意到都是用上面的结构体(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]);
}
}
}