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(N\cdot M^2)\)。再去考虑uv路径,直接最暴力的\(O(N)\)往上跳,枚举在当前点\(x\)排布的人数\(i\),用\(T[x][i]\)来更新答案,\(ans=max(ans,dp[u][m-i]+T[x][i])\)。

综上,最暴力的去搞,单次操作的复杂度为\(O(N\cdot 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\n",tmp.a[m]);
}
}
}

[BZOJ2164]采矿【模拟+树链剖分+线段树】的更多相关文章

  1. jzoj4918. 【GDOI2017模拟12.9】最近公共祖先 (树链剖分+线段树)

    题面 题解 首先,点变黑的过程是不可逆的,黑化了就再也洗不白了 其次,对于\(v\)的祖先\(rt\),\(rt\)能用来更新答案当且仅当\(sz_{rt}>sz_{x}\),其中\(sz\)表 ...

  2. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

  3. 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树

    [BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...

  4. BZOJ2243 (树链剖分+线段树)

    Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...

  5. POJ3237 (树链剖分+线段树)

    Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...

  6. bzoj4034 (树链剖分+线段树)

    Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...

  7. HDU4897 (树链剖分+线段树)

    Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...

  8. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  9. 【POJ3237】Tree(树链剖分+线段树)

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  10. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

随机推荐

  1. python open函数关于w+ r+ 读写操作的理解(转)

    r 只能读 (带r的文件必须先存在)r+ 可读可写 不会创建不存在的文件.如果直接写文件,则从顶部开始写,覆盖之前此位置的内容,如果先读后写,则会在文件最后追加内容.w+ 可读可写 如果文件存在 则覆 ...

  2. Bugs Integrated, Inc.

    Bugs Integrated, Inc. 给出一个\(n\times m\)的矩形网格图,给出其中K个障碍物的位置,求其中最多能摆的\(2\times 3\)的矩形的个数,\(n\leq 150,m ...

  3. LeetCode第九题—— Palindrome Number(判断回文数)

    题目描述 Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same ...

  4. php 引用文件

    require_once :为了避免重复加载文件. 用意:加载文件一次. require_once() 语句在脚本执行期间包括并运行指定文件.此行为和require()语句类似,唯一区别是:如果该文件 ...

  5. Delphi定时模拟键盘按键例程

    delphi模拟键盘按键实例delphi模拟键盘按键实例,只是模拟一个按键的例子而已.到一定时间按下模拟按下一个按键,delphi7编译通过. 10秒点击一下H键,其他键你们去找数值替换吧,网上大把的 ...

  6. iOS ARC下命名规则

    当我在ARC模式下写以下代码的时候,编译器报错 Semantic Issue: Property's synthesized getter follows Cocoa naming conventio ...

  7. 同步异步,异步回调,线程队列,线程时间Event

    同步异步-阻塞非阻塞 阻塞-非阻塞 指的是程序的运行状态 阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞. 非阻塞:程序在正常运行没有遇到IO操作,或者通过 ...

  8. mysql中的字符串截取和替换

    -- 替换 replace(字段名,"需要替换的字符","替换的字符") mysql里replace不支持正则匹配 mysql> set @needRep ...

  9. Android Support 包的作用、用法

    1, Android Support V4, V7, V13是什么?本质上就是三个java library. 2,  为什么要有support库?如果在低版本Android平台上开发一个应用程序,而应 ...

  10. os.path.dirname(__file__)使用、Python os.path.abspath(__file__)使用

    python中的os.path.dirname(__file__)的使用 - CSDN博客https://blog.csdn.net/u011760056/article/details/469698 ...