线段树合并
T1 魔法少女LJJ
感谢魔法少女,给了我重新做人的机会,我觉得经此一役,我这代码能力直线上升
废话版
查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ls(x) t[x].lson
#define rs(x) t[x].rson
using namespace std;
typedef long long ll;
//先离散化,1到1e9也能跑,但还是离散化一下
//裸的线段树合并,对每一个联通块维护一个权值线段树,并查集维护联通性
//就是感觉挺恶心
const int mx=400000+1000;
struct Node{
int c;
int x,y;
}a[mx];//操作存储
struct Nodee{
int lson;
int rson;
int cn,da;
double loo;
}t[mx*10];//权值线段树
int tot;//权值线段树的节点个数
int drt[mx],dr;//离散化数组
int root[mx];//记录每个点的权值线段树的根
int upda;//操作3,4的更改记录值
int fa[mx];
int n,m;
int num[mx];//操作7,直接记录每个点的所属联通块的大小
double lo[mx];//log储存
inline int read(){
int x=0;
int w=1;
char ch=getchar();
// printf("kkkkkkkkkkkkk\n");
while(isdigit(ch)==false){
if(ch=='-')w=-1;
ch=getchar();
}
while(isdigit(ch)==true){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();//没写,6分钟
}
return x*w;
}
inline void w(int x){
static int sta[35];
int top=0;
do{
sta[top++]=x%10;
x/=10;
}while(x!=0);
while(top)putchar(sta[--top]+48);
}
inline int loc(int x){
return lower_bound(drt+1,drt+dr+1,x)-drt;//这加一减一的就记住
//这个就是对于数值x,直接找出他在drt里的相对大小,也就是所在位置
}
int find(int rt){
// printf("fa[rt=%d]=%d %d\n",rt,fa[rt],fa[3]);
if(fa[rt]==rt)return rt;//{
// printf("kkfa[]=%d\n",rt);
// }
return fa[rt]=find(fa[rt]);//这里前面不写return,没有返回值,20分钟
}
void merg(int x,int y){
int xx=find(x);
// printf("xx=%d\n",xx);
int yy=find(y);
// printf("xx=%d yy=%d \n",xx,yy);
fa[yy]=xx;
}
void Pushup(int rt){
// if(rt==0)printf("rt=%d\n",rt);
/* if(rt==31 || rt==25){
printf("t[%d].cn=%d t[ls=%d].cn=%d t[rs=%d].cn=%d\n",rt,t[rt].cn,ls(rt),t[ls(rt)].cn,rs(rt),t[rs(rt)].cn);
//又是有0的问题,且被加到了一个离谱值,不是零,看错了,25
}*/
t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn);
t[rt].loo=(t[ls(rt)].loo+t[rs(rt)].loo);//这里不要忘了loo
// printf("t[%d].loo=%lf t[ls=%d].loo=%lf t[rs=%d].loo=%lf\n",rt,t[rt].loo,ls(rt),t[ls(rt)].loo,rs(rt),t[rs(rt)].loo);
// printf("t[%d].cn=%d t[l].cn=%d t[r].cn=%d\n",rt,t[rt].cn,t[ls(rt)].cn,t[rs(rt)].cn);
}
void Build(int &rt,int l,int r,int x,int xx){
// printf("l=%d r=%d \n",l,r);
if(rt==0)rt=++tot;
if(l==r){
t[rt].cn=1;
t[rt].da=xx;
t[rt].loo=lo[x];
// printf("rt=%d l=%d x=%d \n",rt,l,x);
}
else {
int mid=(l+r)>>1;
if(x<=mid)Build(ls(rt),l,mid,x,xx);
else Build(rs(rt),mid+1,r,x,xx);
Pushup(rt);
}
}
int query(int rt,int l,int r,int k){
// printf("l=%d r=%d k=%d t[rt=%d].cn=%d\n",l,r,k,rt,t[rt].cn);
//这里有一个rt=0会被赋值的问题,不能盲目等于0,我得看看哪里会更新rt=0
// printf("l=%d r=%d k=%d t[rt=%d].cn=%d\n",l,r,k,rt,t[rt].cn);
//这里注意找的是相对第k小
//它找的是联通块内的第k小,块内本身离散化后的相对大小可能是2 4 5 6
//那此刻k=2,这怎么处理
//所以就要用我们的cn,再次回顾cn的定义,这个区间内有几个数的值在这个区间内
if(!rt)return 0;
if(l==r){
// printf("l=%d\n",l);
// printf("t[rt=%d].da=%d\n",rt,t[rt].da);
return t[rt].da;
}
else {
int mid=(l+r)>>1;
//这里又有一个可能一个数有20个的问题
if(k>t[ls(rt)].cn)return query(rs(rt),mid+1,r,k-t[ls(rt)].cn);//要查的数字在左边,这里k得去减去,cn,对,而且拿左比不好写
//先拿大于左比后面就不用减了,注意体会
else return query(ls(rt),l,mid,k);
}
}
int merge(int rt,int rtt,int l,int r){
// printf("l=%d r=%d rt=%d\n",l,r,rt);
if(!rt)return rtt;
if(!rtt)return rt;
if(l==r){
t[rt].cn+=t[rtt].cn;
t[rt].loo+=t[rtt].loo;
return rt;
}
else {
int mid=(l+r)>>1;
ls(rt)=merge(ls(rt),ls(rtt),l,mid);
rs(rt)=merge(rs(rt),rs(rtt),mid+1,r);
// printf("rt=%d\n",rt);//代码这么长删调试都不好删
Pushup(rt);
}
return rt;
}
void rtz(int rt,int l,int r,int L,int R){//return to zeto
// printf("l=%d r=%d L=%d R=%d t[rt=%d].cn=%d\n",l,r,L,R,rt,t[rt].cn);
if(t[rt].cn==0)return ;//算是一个小优化
if(l==r){
// printf("ll==%d\n",l);
upda+=t[rt].cn;
t[rt].cn=0;
t[rt].loo=0;
return ;
}
else {
int mid=(l+r)>>1;
if(L<=mid)rtz(ls(rt),l,mid,L,R);
if(R>mid)rtz(rs(rt),mid+1,r,L,R);
// if(rt==0)printf("rtkk=%d l=%d r=%d\n",rt,l,r);
Pushup(rt);
}
}
void Plus(int &rt,int l,int r,int x,int w,int y){
if(!rt)rt=++tot;//一小时,话说这是真的不好找,还找错一次
// printf("l=%d r=%d rt=%d t[rt].cn=%d\n",l,r,rt,t[rt].cn);
//这里应该是问题根源了,根点3的下属没有我目前要plus的这个值对应的一个点
//所以它一定会把值给到0,
//plus是关键字,这个注意,不用还真不知道
if(l==r){
t[rt].cn=w;
t[rt].loo=lo[x]*w;
t[rt].da=y;
return ;
}
else {
int mid=(l+r)>>1;
if(x<=mid)Plus(ls(rt),l,mid,x,w,y);
else Plus(rs(rt),mid+1,r,x,w,y);
// if(rt==0)printf("rt=%d\n",rt);
Pushup(rt);
}
}
void Solve(){
m=read();
//因为操作是有顺序的,所以离散建点这里不好处理
//那可以先另存一下,每次用的时候再调lower——bound
//啊这个真烦,我过了三天再看就感觉有点乱了
// printf("222kkkkkkkkkk\n");
for(int i=1;i<=m;++i){
int c=read();
if(c==1){
a[i].x=read();
a[i].c=c;
drt[++dr]=a[i].x;//等于x。不是c.半小时
}
else if(c==7 || c==9){//这里记得else
a[i].x=read();
a[i].c=c;
}
else if(c==3 || c==4){
a[i].x=read();
a[i].y=read();
a[i].c=c;
drt[++dr]=a[i].y;//注意这里是y不是x
}
else {
a[i].x=read();
a[i].y=read();
a[i].c=c;
}
fa[i]=i;
// printf("1k fa[%d]=%d\n",i,fa[i]);
}
//
sort(drt+1,drt+dr+1);
// printf("dr==%d\n",dr);
//int drr=dr;
dr=unique(drt+1,drt+dr+1)-(drt+1);//这个一次就记住
// printf("dr=%d\n",dr);
//离散化之后权值之积的相对大小不会被影响,这是显然的啊
//显然NM,你个智障,得log原数
//这里就是说,不能去重,这样在query的时候不会出现一个数有30个而出错
//然后因为我们用loc找的相对大小,所以不去重不会有问题
//不对,不是这里的问题,t[37].cn=21是它本身就离谱
// printf("dr=%d\n",dr);
/* for(int i=1;i<=dr;++i){
printf("%d\n",drt[i]);
}*/
// printf("t[0].cn=%d\n",t[0].cn);
for(int i=1;i<=m;++i){
int cc=a[i].c;
int x=a[i].x;
int y=a[i].y;
if(cc==1){//建点,直接建立权值线段树
int op=loc(x);
// printf("op=%d\n",op);
//啊啊啊啊啊啊啊啊啊啊啊啊啊啊你是智障嘛,你为什么要把lo[op]=log(x);放在build下面
lo[op]=log(x);
Build(root[++n],1,dr,op,x);
// printf("99999kkkkkkkkk\n");
// num[n]=1;
// printf("x=%d op=%d lo[op]=%lf\n",x,op,lo[op]);
//我这里有问题,这里去build一定是原来的值,因为操作5要求这是原来的值
//a啊啊啊烦死了,后面都要全改
}
//现在统一又有一个新问题了,我的fa存的是离散化后的点的父子关系
//让我看看怎么改
else if(cc==2){//连接两个节点
//这里也得是根啊啊啊啊啊啊啊啊啊啊啊啊啊,半小时
// printf("2kkkkkkkkkk\n");
int xx=find(x);
int yy=find(y);
if(xx==yy)continue;//这个简直坑死了 一小时
//其实这也帮助我深刻理解为什么要用并查集,我一开始只是想着7的简便
//其实它哪里都要用,如果不用并查集,还是一开始那个问题,存储其实好像再开一个也行,不过及时更新,好像也行?
//没有并查集实现起来方便快速就是了
// printf("xx=%d yy=%d\n",xx,yy);
// printf("t[root[xx]=%d].cn=%d t[root[yy]=%d].cn=%d\n",root[xx],t[root[xx]].cn,root[yy],t[root[yy]].cn);
root[xx]=merge(root[xx],root[yy],1,dr);
//
merg(x,y);
// printf("t[root[xx]=%d].cn=%d\n",root[xx],t[root[xx]].cn);
// printf("7777777777kkkkkkkkk\n");
// int xx=find(x);
// int yy=find(y);
/* if(xx!=yy){
num[xx]+=num[yy];
num[yy]=xx;
}*/
}
else if(cc==3){//将一个节点a所属于的联通快内权值小于x的所有节点权值变成x
//意思就是将权值线段树内,所有小于x的点归零,然后将x这个点的cn加上我们归零的几个点
//这里写两个函数,一个用来归零,一个用来加上,好像不太能一起做?
//好像可以直接一个updata?不行,这样可能会有我们没有加上的值
upda=0;
// printf("3kkkkkkkkkkkkkkkk\n");
//这里传进去的也得是它祖宗注意
int xx=find(x);
// printf("xx=%d\n",xx);
//问题在这里,权值小于x,我的离散化数组里可能没有x的对应值,这怎么办
//不对啊,我用的lower_bound啊
//找到了:因为我是离线操作,先去离散了所有的点,所以被卡在线了
//有些操作在建某些点之前,这样的话相对大小就不对了,这个真不好找
//这个没有影响,因为连接也是操作,而且好像如果离散必须离线
//因为把小于x的都更改为x,x的相对大小必定要是在全体的,比它小的
//自然会比它小,一开始还真没想这么多
//这里又有一个问题就是,改了数之后,log也得改,但是说如果是我把y的相对大小这个位置改了
//假设2 6 7 9 56 我要小于8的,它会给出一个3,没问题,它会给出一个4!!!!!
//没问题nm,就是有问题,所以更改不能到y,要到y-1,再加到y
//当然我可以再减一
// printf("loc(y=%d)=%d\n",y,loc(y));
//
lo[loc(y)]=log(y);
rtz(root[xx],1,dr,1,loc(y));//这里直接到x就行,我到时候再加回来
// printf("up=%d\n",upda);
if(upda)Plus(root[xx],1,dr,loc(y),upda,y);
//这里也是,要用相对大小loc找,其实直接存个数组也行,这么写反而慢了
//存数组不太行,想要快速找相对大小你还真这么写才行
}
// 3,4,这得想想 3 4 6 8 9 10 现在 3 给一个7 我让它返回4 lo[4]=8->7
//不是我先写,写完再说,写不了,它这样怎么都会错
//给一种方法:你从一开始你就把会有更改的操作的值也存到drt里,让它去跟一起离散,这样相对大小就绝对正确
//所以以前所有关于这里的都是智障想法
//离散化真的好烦啊
else if(cc==4){//若c=4,之后两个正整数a,x,表示a联通快内原本权值大于x的节点全部变成x
//这个好说了,套我上面那个函数就行
// printf("4kkkkkkkkkkkkk\n");
upda=0;
int xx=find(x);//同上,这就一下体现并查集在这个连通块的优势性了
lo[loc(y)]=log(y);
// printf("loc(y=%d)=%d t[5].cn=%d\n",y,loc(y),t[5].cn) ;
//改为y注意你得改啊,因为还是之前说的,它不一定在已知内
rtz(root[xx],1,dr,loc(y),dr);
// printf("root[xx=%d]=%d up=%d\n",xx,root[xx],upda);
if(upda)Plus(root[xx],1,dr,loc(y),upda,y);
}
else if(cc==5){//若c=5,之后两个正整数a,k,表示询问a所属于的联通块内的第k小的权值是多少
//这就属于权值线段树的经常操作了,一个query
int xx=find(x);//同上
// printf("xx=%d t[root[xx]=%d].cn=%d\n",xx,root[xx],t[root[xx]].cn);
//这里又又又又有一个问题,如果区间只有一个数,要第十小,会是零,不对,不是这个问题好像,
// if(y>t[root[xx]].cn)y=t[root[xx]].cn;
// printf("y=%d\n",y);
// printf("x=%d xx=%d root[xx]=%d t[root[x]].cn=%d y=%d ",x,xx,root[xx],t[root[xx]].cn,y);
int an=query(root[xx],1,dr,y);
// printf("kkkkkkkkkkkk\n");
// printf("k");
w(an);printf("\n");//感觉这样再写一个printf换行好智障啊
}
//6和7这里有一个很大的优化,就涉及我们的并查集了
else if(cc==6){//若c=6,之后两个正整数a,b,表示询问a所属联通快内所有节点权值之积与b所属联通快内所有节点权值之积的大小
//若a所属联通快内所有节点权值之积大于b所属联通快内所有节点权值之积,输出1,否则为0
//这里直接乘起来肯定会爆掉,给一个我目前知识水平完全可以推出的优化
//∏p>∏q⟺log2∏p>log2∏q 这没有问题且显然
//log2∏p=∑log2p 这个显然上数学课是讲过的,所以这样我们就将乘法转换为了加法
//所以说先弄个log数组,再累加,先求出每个数的log,在相加
int xx=find(x);
int yy=find(y);
// printf("xx=%d yy=%d\n",xx,yy);
// printf("t[root[xx]=%d].loo=%lf t[root[yy]=%d].loo=%lf\n",root[xx],t[root[xx]].loo,root[yy],t[root[yy]].loo);
// printf("kkkkkkkkkkkkk");
//这里有一个非常脑瘫的错误就是log的存储数组得开double
if(t[root[xx]].loo>t[root[yy]].loo)printf("1\n");
else printf("0\n");
}
else if(cc==7){//若c=7,之后一个正整数a,表示询问a所在联通块大小
//关于操作7,这里有一个问题就是,因为样例,我去手摸一下,感谢手摸,发现了前面两个问题
//继续手摸,好的样例是默认双向边联通块,我们是不是可以维护一个num
//每次连边去更新维护联通块内点的个数,这里就涉及到并查集优化了
/* //我的想法是每次把链接双方的num值都更改,好像没问题?
//初始num都为1,每次操作2就互相加加,并查集?哦,假设我们要连1,3,此刻1已经联通了7,8,9
//所以说我们显然给1,3,7,8,9都更新挺麻烦的,把3,7,8,9的祖先都设成1,num find(1)加num find(3)
//所以说他们有的把num同时放在线段树里维护我不太理解,我先写,有bug再调*/
//中间那段纯脑瘫想法,也不是脑瘫,那样也能做,我们还是回归权值线段树的定义,它要的个数不就是a经过多次合并后的
//所存储合并结果的那棵树树的cn值吗,你是脑瘫吗 半小时
// printf("%d\n",num[find(x)]);
int xx=find(x);
// printf("xx=%d x=%d root[xx]=%d\n",xx,x,root[xx]);
// printf("k");
printf("%d\n",t[root[xx]].cn);
}
}
// printf("t[0].cn=%d\n",t[0].cn);
// printf("t[rt].cn=%d\n",t[0].cn);
//终于写完了,希望能一遍A,感谢魔法少女,给了我重新做人的机会
//2022.4.8 20:37 样例过了 ok交上去炸了
//2022.4.9 8:27 第二组样例过了 25
//9:00 开始写对拍了 ,通过对数据的分析,大概在操作5上
//9:57 三个点都拍对了,
//A 10:00
}
int main(){
// freopen("c.in","r",stdin);
// freopen("ans.out","w",stdout);
int cn=0;
Solve();
return 0;
}
纯净版
查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ls(x) t[x].lson
#define rs(x) t[x].rson
using namespace std;
typedef long long ll;
const int mx=400000+1000;
struct Node{
int c;
int x,y;
}a[mx];
struct Nodee{
int lson;
int rson;
int cn,da;
double loo;
}t[mx*10];
int tot,drt[mx],dr,root[mx],upda,fa[mx],n,m,num[mx];
double lo[mx];
inline int read(){
int x=0;
int w=1;
char ch=getchar();
while(isdigit(ch)==false){
if(ch=='-')w=-1;
ch=getchar();
}
while(isdigit(ch)==true){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*w;
}
inline void w(int x){
static int sta[35];
int top=0;
do{
sta[top++]=x%10;
x/=10;
}while(x!=0);
while(top)putchar(sta[--top]+48);
}
inline int loc(int x){
return lower_bound(drt+1,drt+dr+1,x)-drt;
}
int find(int rt){
if(fa[rt]==rt)return rt;
return fa[rt]=find(fa[rt]);
}
void merg(int x,int y){
int xx=find(x);
int yy=find(y);
fa[yy]=xx;
}
void Pushup(int rt){
t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn);
t[rt].loo=(t[ls(rt)].loo+t[rs(rt)].loo);
}
void Build(int &rt,int l,int r,int x,int xx){
if(rt==0)rt=++tot;
if(l==r){
t[rt].cn=1;
t[rt].da=xx;
t[rt].loo=lo[x];
}
else {
int mid=(l+r)>>1;
if(x<=mid)Build(ls(rt),l,mid,x,xx);
else Build(rs(rt),mid+1,r,x,xx);
Pushup(rt);
}
}
int query(int rt,int l,int r,int k){
if(!rt)return 0;
if(l==r){
return t[rt].da;
}
else {
int mid=(l+r)>>1;
if(k>t[ls(rt)].cn)return query(rs(rt),mid+1,r,k-t[ls(rt)].cn);
else return query(ls(rt),l,mid,k);
}
}
int merge(int rt,int rtt,int l,int r){
if(!rt)return rtt;
if(!rtt)return rt;
if(l==r){
t[rt].cn+=t[rtt].cn;
t[rt].loo+=t[rtt].loo;
return rt;
}
else {
int mid=(l+r)>>1;
ls(rt)=merge(ls(rt),ls(rtt),l,mid);
rs(rt)=merge(rs(rt),rs(rtt),mid+1,r);
Pushup(rt);
}
return rt;
}
void rtz(int rt,int l,int r,int L,int R){
if(t[rt].cn==0)return ;
if(l==r){
upda+=t[rt].cn;
t[rt].cn=0;
t[rt].loo=0;
return ;
}
else {
int mid=(l+r)>>1;
if(L<=mid)rtz(ls(rt),l,mid,L,R);
if(R>mid)rtz(rs(rt),mid+1,r,L,R);
Pushup(rt);
}
}
void Plus(int &rt,int l,int r,int x,int w,int y){
if(!rt)rt=++tot;
if(l==r){
t[rt].cn=w;
t[rt].loo=lo[x]*w;
t[rt].da=y;
return ;
}
else {
int mid=(l+r)>>1;
if(x<=mid)Plus(ls(rt),l,mid,x,w,y);
else Plus(rs(rt),mid+1,r,x,w,y);
Pushup(rt);
}
}
void Solve(){
m=read();
for(int i=1;i<=m;++i){
int c=read();
if(c==1){
a[i].x=read();
a[i].c=c;
drt[++dr]=a[i].x;
}
else if(c==7 || c==9){
a[i].x=read();
a[i].c=c;
}
else if(c==3 || c==4){
a[i].x=read();
a[i].y=read();
a[i].c=c;
drt[++dr]=a[i].y;
}
else {
a[i].x=read();
a[i].y=read();
a[i].c=c;
}
fa[i]=i;
}
sort(drt+1,drt+dr+1);
dr=unique(drt+1,drt+dr+1)-(drt+1);
for(int i=1;i<=m;++i){
int cc=a[i].c;
int x=a[i].x;
int y=a[i].y;
if(cc==1){
int op=loc(x);
lo[op]=log(x);
Build(root[++n],1,dr,op,x);
}
else if(cc==2){
int xx=find(x);
int yy=find(y);
if(xx==yy)continue;
root[xx]=merge(root[xx],root[yy],1,dr);
merg(x,y);
}
else if(cc==3){
upda=0;
int xx=find(x);
lo[loc(y)]=log(y);
rtz(root[xx],1,dr,1,loc(y));
if(upda)Plus(root[xx],1,dr,loc(y),upda,y);
}
else if(cc==4){
upda=0;
int xx=find(x);
lo[loc(y)]=log(y);
rtz(root[xx],1,dr,loc(y),dr);
if(upda)Plus(root[xx],1,dr,loc(y),upda,y);
}
else if(cc==5){
int xx=find(x);
int an=query(root[xx],1,dr,y);
w(an);printf("\n");
}
else if(cc==6){
int xx=find(x);
int yy=find(y);
if(t[root[xx]].loo>t[root[yy]].loo)printf("1\n");
else printf("0\n");
}
else if(cc==7){
int xx=find(x);
printf("%d\n",t[root[xx]].cn);
}
}
}
int main(){
int cn=0;
Solve();
return 0;
}
T2 大根堆
这个题在机房磨磨唧唧的做了两周,总共六种方法,弄会两种(且对于我目前而言最有价值的两种)
真是挺不容易的,只能说cd大佬太强了,他那一句int res=t[rt].cn真的帅到我了
这个题一点定要自己先去想出纯暴力DP再去想线段树合并优化(或者可以get到启发式合并的树上LIS也行)
如果没有自己思考的DP过程,就直接去看网上的线段树合并题解,这个题就没有意义了
纯暴力DP(60分)
//大根堆 #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int mx=200000+1000; int n,len,dr; int v[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; struct Node{ int from; int to; int next; }e[mx]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然后后面还要处理j>v[u] } } for(int j=n;j>=v[u];--j){ f[u][j]=max(f[u][j],f[u][v[u]-1]+1); //因为先下的叶子,这里自然而然的就做到了初始化 } } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&v[i],&fa[i]); Insert(fa[i],i); //这种明确指明谁就是谁的爹倒是可以只建一条 drt[i]=v[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+dr+1)-drt-1; for(int i=1;i<=n;++i){ v[i]=lower_bound(drt+1,drt+n+1,v[i])-drt; } vis[1]=1;dfs(1); int ans=0; for(int i=1;i<=n;++i){ ans=max(ans,f[1][i]); } //先交一下验证思路 printf("%d\n",ans); } int main(){ Solve(); return 0; }
线段树合并纯净版(标记永久化(我认为最有价值,最帅的方法))
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); if(!r1 || !r2){ if(r1){ t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt)rt=++tot; if(L<=l && R>=r){ t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1);int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }
几个废话版,记录心情
//大根堆 //它最后要一个最多的点数,样例是5 //这里注意,通过观察样例得知,你可以把它当成无向的 //你不是从已知树里找树,而是树中抽数出来构建树 //1.树上差分加线段树合并 //2.启发式合并 //树上差分:一个点的真实权值是一个点子树内所有差分后的权值之和 //ok,为了做这道题,我去A了一道树上差分,还顺带复习了一遍lca //首先便是DP 以当前结点为根的一颗子树,它所能选出的一个大根堆 我们可以去循环权值 // f[u][i] 以u为根,点的权值不大于i,所能找出的一个ans,根据题目的要求,其实每个点都可以做总根 //不对,这里其实没有总根这个概念,看你怎么理解,你把 //其实就是用合并来模拟那个dp,然后特殊化一下就好了,这就是我的理解 //即: f[u][i] =max( sum f[v][i](这是不选) , sum f[v][i-1]+1(这是选上当前根结点) ) //然后就是样例那种,你得去看怎么处理i 大于u的权值的情况 //我们的 sum 就是可以用线段树合并把它的子树合并上来再查询 //这样,你先写纯暴力DP #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; inline int read(){ int w=1; int x=0; char ch=getchar(); while(isdigit(ch)==false){ if(ch=='-')w=-1; ch=getchar(); } while(isdigit(ch)==true){ x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x*w; } void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } /*void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){//这么写主要是防止数据有重边 vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然后后面还要处理j>va[u] } } for(int j=n;j>=va[u];--j){ f[u][j]=max(f[u][j],f[u][va[u]]+1); //因为先下的叶子,这里自然而然的就做到了我想要的初始化 } //现在我们要做的就是用线段树合并去优化这个n*n暴力DP //这个DP主要两个操作,区间和以及区间max,区间和可以用合并并上去,区间max //首先你要明确,线段树的本职工作就是区间max //重新来:区间和的操作,如果单纯开出点来去查,空间炸,所以必然是要一层层合并上去 //那么合并上去,还要有一个区间max,来与当前再次比较一下 //那么好,区间max如果每次都不设lazy,直接更新下去,或者说 //还是,我先写出不考虑lazy的情况,再说解决 //解决方法有三种:二分,差分,lazy永久化 }*/ void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ // printf("r1=%d r2=%d\n",r1,r2); r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1m=%d r2m=%d t[r1=%d].cn=%d t[r2=%d].cn=%d\n",r1max,r2max,r1,t[r1].cn,r2,t[r2].cn); //又是零出问题了,哪里的零? if(r1==0 && r2==0) return r1;//这里,因为两个都零,会进r1的操作,但此刻我们会有值加上 //所以不对 或者可以这么写: /*if(!rt || !p){ if(rt) update(rt, radd);//这里相当与单点merge的递归到了叶子,这里是递归到了区间, if(p) update(p, ladd), rt = p;//加上另外的子树的影响 //不知道y这里有没有用 return; } */ if(!r1){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } if(!r2){ //这里是递归到了叶子结点 //此刻旁边有一个子树,而需要把这个叶子节点的值加到这个子树上,而之前显然是 //没有去加,它直接就跳出了,那么是直接加?加什么? //你再次回归定义,我这里存的是max,而不是sum,所以说,所以说 //如果r2不是纯叶子,它递归到这里我需要给r1加上此刻r2的max! //那么关于lazy的问题我也大概想通了,还是,想,此刻的r2结束了,但r1没有结束 //而线段树本身就是点代表区间,给这个区间加上了r2max,下面的细分也因加上 //但在此刻这里显然是不能加上,所以要用lazy //那上面也是同理 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2);//把双方的lazy各自传下去 // t[r1].cn+=t[r2].cn;//合并应该就是这么莽着加就行了 //其它地方就是这么加没错,不不不,这里应该是此时此刻的双max,这样才完美符合的我的定义 //在上还是在下?回忆普通线段树,在下! ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); t[r1].cn=r1max+r2max; // printf("t[rt=%d].cn=%d\n",r1,t[r1].cn); return r1;//这里千万不要忘 } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=0; res=t[rt].cn; // printf("res=%d\n",res); Pushdown(rt); int mid=(l+r)>>1; //现在就是,我怎么保证查出去是最优解,是max //我们存,就是存的max! if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ //你就把那个图看成区间 if(!rt)rt=++tot; if(L<=l && R>=r){//在j>=va[u]内,可以选根点 t[rt].cn=max(t[rt].cn,w);//去用以根点为根比较更新一下最大值 return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u,int faa){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; //我也不知道现在建立这个东西,其实也可以叫权值线段树 //只不过它存储的是它的最优解 // if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); if(v==faa)continue; /// vis[v]=1; dfs(v,u); root[u]=merge(root[u],root[v],1,dr,0,0); //先合并,就是说,如果这是个叶子结点,那叫无所谓 //如果不是叶子,就是那个每次+=的操作,其实就是一遍遍通过儿子 //把这个点的树合并起来了,那Build好像就没有必要le? //然后去外面建立这个点的线段树 //外面主要是建立叶子的线段树好让它去合并 // } } // printf("u=%d\n",u); //sum也没有处理完,我们也不能只是简单的pushup //怎么办 int an=query(root[u],1,dr,va[u]-1)+1;//查询那个max // printf("an=%d va[u]-1=%d\n",an,va[u]-1); //即:不选这个结点的最大ans //也不是不选,是u是顶层的max //好的现在压力给到build了,我的merge只是简单的把叶子去相加合并,并未进行Pushup //query直接跳跃空查max //现在就差用build把他们串联起来,我就A了 Build(root[u],1,dr,va[u],dr,an);//判不判定是叶子再说,后面? //还是感觉有问题,完全没有处理merge,也没有感觉出它们所说的卡lazy //很显然就是merge这里有问题 } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ int vv,pp; va[i]=read(); fa[i]=read(); if(fa[i]==0)continue; Insert(fa[i],i); Insert(i,fa[i]); //这种明确指明谁就是谁的爹应该可以只建一条 //而树上差分那个板子题没有指明谁是祖先,所以要双向再vis,人为定边 //不对,这个题因为我出来还有一些操作,让我看看,没影响 //首先如果建双向边要排除零 drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+n+1,va[i])-drt; } //dp初始化,每个叶子节点 f[u][>=val[u]]=1; //不好初始化那就先写 vis[1]=1; dfs(1,0); int ans=0; /* for(int i=1;i<=n;++i){ ans=max(ans,f[1][i]); }*/ //先交一下验证思路,50分 有一个点没A,不管了 ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=2000000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); if(!r1 || !r2){ if(r1){ t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); t[r1].cn=r1max+r2max; return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt)rt=++tot; if(L<=l && R>=r){ t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } /*void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然后后面还要处理j>v[u] } } for(int j=n;j>=v[u];--j){ f[u][j]=max(f[u][j],f[u][v[u]-1]+1); //因为先下的叶子,这里自然而然的就做到了我想要的初始化 } }*/ void dfs(int u){ Build(root[u],1,dr,va[u],dr,0); for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); //这个题还剩最后一点遗留问题 //首先我们有一个纯暴力DP //f[u][i]表示以u为根,最大值小于等于i 在这个树上构成一个树形lis的最佳方案个数 //当i大于u结点的权值的时候,u就成为中转点了 //设u结点的权值为w //然后我们仍然易得,当i>=w时(注意这里有等于,因为本身也要给它自己加上) //需要去比较一下 f[u][i] f[u][w-1]+1 //f[u][i] =max( sum f[v][i](这是不选) , sum f[v][i-1]+1(这是选上当前根结点) //f[u][i]的来源是u的子树的f[v][i]的和,因为这是一个树形lis //之后的所有方法本质都是去优化这个DP //只有理解了这些,才能真正理解为什么权值线段树中去维护方案个数而不是结点权值存在个数 // 剩下的明天再说 ,干饭去了 // 然后,那个sum操作,我们是用了合并来进行累加,先递归深搜到这个叶子节点,把 //把它与父亲节点进行层层合并,合并就是max累加,因为我们要的就是max,所以要记录max去从下正好更新了上面 //再有就是,先合并,合并完了之后,出来循环在对这个点进行建立(或者就是建立)线段树 //外面这个操作意义就在于,如果是个叶子,正好建起来,后面去用来合并,如果不是叶子,那就已经被合并出树了 //就处理一下i>=w的问题 如果先建树再合并,一是后面还要等到合并完再去处理一遍i>=w; //二是照我的理解 ,感觉好像也没有什么问题,就是你建了个寂寞,我试试 , root[u]=merge(root[u],root[v],1,dr,0,0); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; //如果真的是这里出了问题,我就从机房跳下去 //我现在的心情已经没有办法表述了 //我从上上周六调到现在,交了20多次 //最后告诉我,离散化细节出问题了。。。。。。。。。。。。。 } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1=%d r2=%d l=%d r=%d r1max=%d r2max=%d\n",r1,r2,l,r,r1max,r2max); //权值线段树它就是我们的DP定义,1节点代表1到9的允许j值,所以它的cn是零,2是1的儿子,它代表6到9的允许j值 //所以它可以是1,所以我们不能在Build时Pushup,所以我们要在merge时(就是DP的累加时)有一个就加一个 //注意r1max,r2max可都时现在的cn,这里仍然没有任何pushup操作 //对 ,这就是,dp转权值线段树的普遍思考 if(!r1 || !r2){ if(r1){ //如果不用lazy,这里再下去跑,把r1的子树都加上r2的值也确实挺麻烦的 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); // printf("t[r1=%d]=%d\n",r1,t[r1].cn); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ //printf("rt=%d l=%d r=%d L=%d R=%d w=%d\n",rt,l,r,L,R,w); if(!rt){ rt=++tot; // printf("rt=%d l=%d r=%d L=%d R=%d w=%d\n",rt,l,r,L,R,w); } if(L<=l && R>=r){ //这里这么做因为这里不仅仅是建,更是合并后的更新 //在建立叶子节点时,R>=这一步会使>=va[a] 的区间都被进行这里的操作符合定义 t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); // printf("root[%d]=%d\n",u,root[u]); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); // printf("an=%d\n",an); // printf("root[%d]=%d va[%d]=%d dr=%d an=%d\n",u,root[u],u,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1=%d r2=%d l=%d r=%d r1max=%d r2max=%d\n",r1,r2,l,r,r1max,r2max); //权值线段树它就是我们的DP定义,1节点代表1到9的允许j值,所以它的cn是零,2是1的儿子,它代表6到9的允许j值 //所以它可以是1,所以我们不能在Build时Pushup,所以我们要在merge时(就是DP的累加时)有一个就加一个 //注意r1max,r2max可都时现在的cn,这里仍然没有任何pushup操作 //反而会将max往下传,进行pushdwon,回顾定义以及存储你会发现这样合情合理 //最后肯定是要全部更新出一轮max,1-9的限制绝对是被限制在1,所以越往下越大,max往下传就行 //对 ,这就是,dp转权值线段树的普遍思考 if(!r1 || !r2){ if(r1){ //如果不用lazy,这里再下去跑,把r1的子树都加上r2的值也确实挺麻烦的 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; //我大概懂这个标记什么意思了,手摸了两个小时,建了两张A4纸的树 //就是说普通线段树,左右儿子是乘二,如果去Pushdown,不管此刻这个儿子在之前有没有被访问过,都会用lazy更新 //但是如果是权值线段树且动态开点,对于这个题而言,还是拿我目前模的这组样例说,在17这个点,代表区间是4-5,值为2 //但是它并没有左右儿子,我去查5,答案显然是2,但是因为17没有左右儿子,所以L<= R>=这个判定一直不会完成,4-5下不去了 //所以会返回0,我目前用的lazy是在维护开的点的lazy,它并不能想普通那样更新没有别需要过的点 //然后又是因为这个题我们的DP定义,4-5的答案,如果下面有儿子可能没有儿子大,但如果没儿子肯定它大,所以只需要一句 // int res=t[rt].cn;就解决问题了 // int res=0; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt){ rt=++tot; } if(L<=l && R>=r){ //这里这么做因为这里不仅仅是建,更是合并后的更新 //在建立叶子节点时,R>=这一步会使>=va[a] 的区间都被进行这里的操作符合定义 t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); // printf("root[%d]=%d\n",u,root[u]); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); // printf("an=%d\n",an); printf("root[%d]=%d va[%d]=%d dr=%d an=%d\n",u,root[u],u,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }
祝您,武运昌隆