Portal -->bzoj2759

Solution

  哇我感觉这题真的qwq是很好的一题呀qwq

  很神qwq反正我真的是自己想怎么想都想不到就是了qwq

  

  首先先考虑一下简化版的问题应该怎么解决:

  1、如果说我知道\(x_1\equiv k_1*x_2+b_1(mod\ 10007)\),并且\(x_2\)已知,那么显然有当\(k_1=0\)时有\(x_1=x_2\);\(k_1=1\)且\(b_1=0\)时有无数组解,\(k_1=1\)且\(b_1\)不为\(0\)时无解;\(k_1>1\)时逆元求解(因为\(10007\)是质数嘛)

  2、如果说我知道\(x_1\equiv k_1*x_2+b_1(mod\ 10007)\)和\(x_2\equiv k_2*x_1+b_1(mod \ 10007)\),那么怎么求\(x_1\)呢

  有一种很粗暴的方法就是直接把第一条式子中的\(x_2\)用第二条式子的右边部分替换掉,然后直接解

​  

  然后我们可以想办法往这个方向靠

  我们来小小的转化一下这个问题,我们考虑把每一条式子\(x_i=k_i*x_{p_i}+b_i\),转化为由\(x_{p_i}\)向\(x_i\)连一条有向边,然后这样的话我们就可以得到一个。。基环外向树森林,大概是若干个长这个样子的东西(额当然这里没有把边的方向画出来):

  处理这样的东西,有一个比较套路的方法就是拆掉环上一条边然后变成树来维护处理

  我们选环上的其中一个点作为这块东西的\(rt\),然后将拆掉的那条边(某个点\(y\)指到\(rt\))对应的点\(y\)记为\(sp[rt]\),也就是\(rt\)的一个\(special\ father\)

  然后我们考虑用LCT来维护这个东西,但是具体维护什么呢

  

  这里有一个很神的想法,对于每一个splay上的节点,我们维护其子树内最左边的节点\(L\)(也就是深度最浅的那个)的\(sp\)表达最右边的那个节点\(R\)(深度最深的那个)的表达式的两个系数,也就是:

\[x_{R}=k*x_L+b
\]

  对于splay上的每个节点我们维护上面这个式子里面的\(k\)和\(b\)分别是多少(记作\(info[x].k\)和\(info[x].b\))

  这样一来,我们对于一个节点\(x_i\)做了\(access(x_i)\)以及\(splay(x_i)\)之后,\(info[x]\)中存的表达式其实就是:

\[x_i=k*sp[rt]+b
\]

  那么对于每次查询(记查询的那个点为\(x_a\)),我们需要做的就是用上面的操作得到\(x_i\)和\(sp[rt]\)之间的表达式,然后只要再得到\(sp[rt]\)关于自己的表达式我们就可以求出\(sp[rt]\)进而求得\(x_i\)了。后者的话因为根据定义\(sp[rt]\)应该是这棵树中的某个节点,所以我们直接用同样的\(access+splay\)操作就可以得到\(sp[rt]\)关于自己的表达式了

  然后对于修改的话,我们需要分类讨论一下(可以自己画个图理解一下就很清晰了)

1、\(rt=x\)

​  这里又可以再分两类

​  如果说\(p\)在这棵树中,那么修改\(sp[rt]\)即可;否则\(sp[rt]=0\)然后将\(rt\)接到\(p\)这个节点上面去,作为\(p\)的一个儿子

2、\(rt!=x\)

  不管别的首先我们都要将\(x\)和原来的\(fa[x]\)断开

​  如果说\(x\)在\(rt\)到\(sp[rt]\)的这个环上的话,断开之后会有一个影响,就是\(sp[rt]\)指向\(rt\)这条边不需要断开了,所以我们要将\(rt\)连到\(sp[rt]\)那里去作为其一个儿子

  然后不管是\(x\)是否在环上,我们都要判断如果说\(p\)在这棵树上,那么\(sp[x]=p\),否则\(sp[x]=0\)然后将\(x\)连到\(p\)那里去作为其一个儿子

  

​  这些都讨论完了之后,我们来想想这个关键的\(info[x]\)要怎么维护

  注意到这个在\(update\)的时候是必须按照一定顺序的,因为一个节点\(x\)的信息只能和原树中的\(fa[x]\)合并

  具体一点就是:

\[\begin{aligned}
x&=k_1*x_{fa}+b_1\\
x_{fa}&=k_2*x'+b_2\\
\\
\downarrow\\
\\
x&=k_1(k_2*x'+b_2)+b_1
\end{aligned}
\]

  我们用\(ch[x][0]\)和\(ch[x][1]\)表示splay中\(x\)节点的左右儿子

​  假设我们现在知道\(info[ch[x][0]]]\)和\(info[ch[x][1]]\),我们想要得到\(info[x]\),那么其实只要先将\(info[ch[x][0]]\)表示的式子和\(x\)本身的式子先合并存为\(info[x]\),再将\(info[x]\)与\(info[ch[x][1]]\)合并即可,具体的话就是因为左子树中深度最深的节点在原树上就是\(fa[x]\),同理右子树中深度最浅的节点在原树上的\(fa\)就是\(x\),所以直接这么合并就好了

  

  想明白了的话还是挺好写的ovo(废话qwq然而我想了一天。。。)

​  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=30010,MOD=10007,LCT=N;
struct Data{
int k,b;
Data(){}
Data(int k1,int b1){k=k1; b=b1;}
friend Data operator + (Data x,Data y)
{return Data(x.k*y.k%MOD,(x.b*y.k%MOD+y.b)%MOD);}
}val[N];
int h[N],vis[N],Fa[N],inv[N];
int n,m,tot,Cnt;
namespace Lct{/*{{{*/
int ch[LCT][10],fa[LCT],sp[LCT];
Data info[LCT];
int tot;
bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
int which(int x){return ch[fa[x]][1]==x;}
void update(int x){//order is important
info[x]=val[x];
if (ch[x][0]) info[x]=info[ch[x][0]]+info[x];
if (ch[x][1]) info[x]=info[x]+info[ch[x][1]];
}
void rotate(int x){
int dir=which(x),f=fa[x];
if (!isroot(f)) ch[fa[f]][which(f)]=x;
fa[x]=fa[f]; fa[f]=x;
if (ch[x][dir^1]) fa[ch[x][dir^1]]=f;
ch[f][dir]=ch[x][dir^1];
ch[x][dir^1]=f;
update(f); update(x);
}
void splay(int x){
for (int f=fa[x];!isroot(x);f=fa[x]){
if (!isroot(f))
rotate(which(f)==which(x)?f:x);
rotate(x);
}
}
void access(int x){
for (int last=0;x;last=x,x=fa[x]){
splay(x);
ch[x][1]=last;
update(x);
}
}
int get_rt(int x){
access(x); splay(x);
while (ch[x][0]) x=ch[x][0];
return x;
}
void query(int x){
Data tmp1,tmp2;
access(x); splay(x);
tmp1=info[x];//sp[rt]-->x int rt=get_rt(x);
access(sp[rt]); splay(sp[rt]);
tmp2=info[sp[rt]];//sp[rt]-->sp[rt] if (tmp2.k==1){
if (tmp2.b==0) printf("-2\n");
else printf("-1\n");
}
else{
int valrt=inv[(1-tmp2.k+MOD)%MOD]*tmp2.b%MOD;
printf("%d\n",(tmp1.k*valrt%MOD+tmp1.b)%MOD);
}
}
void Cut(int x){
access(x); splay(x);
ch[x][0]=fa[ch[x][0]]=0;
update(x);
}
bool InCir(int x,int y){//x in cir(y,sp[y])?
access(sp[y]);
splay(sp[y]);
splay(x);
return x==sp[y]||(!isroot(sp[y]));
}
void change(int x,int k,int p,int b){
access(x);
splay(x);
val[x]=Data(k,b);
update(x); int rt=get_rt(x);
if (rt==x){
if (get_rt(p)==rt) sp[x]=p;
else sp[rt]=0,fa[rt]=p;
}
else{
if (InCir(x,rt)){
Cut(x);
splay(rt);
fa[rt]=sp[rt];
sp[rt]=0;
}
else
Cut(x);
if (get_rt(p)==x)
sp[x]=p;
else
sp[x]=0,fa[x]=p;
}
}
}/*}}}*/ void prework(int x){
vis[x]=Cnt;
Lct::fa[x]=Fa[x];
if (vis[Fa[x]]==Cnt){
Lct::fa[x]=0;
Lct::sp[x]=Fa[x];
return;
}
prework(Fa[x]);
} void get_inv(int n){
inv[1]=1;
for (int i=2;i<=n;++i)
inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}; int main(){/*{{{*/
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
char op[5];
int x,p,k,b;
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d%d%d",&val[i].k,&Fa[i],&val[i].b);
get_inv(N-10);
Cnt=0;
for (int i=1;i<=n;++i)
if (!vis[i])
++Cnt,prework(i); scanf("%d",&m);
for (int i=1;i<=m;++i){
scanf("%s",op);
if (op[0]=='A'){
scanf("%d",&x);
Lct::query(x);
}
else{
scanf("%d%d%d%d%d",&x,&k,&p,&b);
Lct::change(x,k,p,b);
}
}
}/*}}}*/

【bzoj2759】一个动态树好题的更多相关文章

  1. BZOJ2759: 一个动态树好题

    BZOJ2759: 一个动态树好题 Description 有N个未知数x[1..n]和N个等式组成的同余方程组:x[i]=k[i]*x[p[i]]+b[i] mod 10007其中,k[i],b[i ...

  2. BZOJ2759 一个动态树好题 LCT

    题解: 的确是动态树好题 首先由于每个点只有一个出边 这个图构成了基环内向树 我们观察那个同余方程组 一旦形成环的话我们就能知道环上点以及能连向环上点的值是多少了 所以我们只需要用一种结构来维护两个不 ...

  3. BZOJ2759一个动态树好题 LCT

    题如其名啊 昨天晚上写了一发忘保存 只好今天又码一遍了 将题目中怕$p[i]$看做$i$的$father$ 可以发现每个联通块都是一个基环树 我们对每个基环删掉环上一条边 就可以得到一个森林了 可以用 ...

  4. [BZOJ 2759] 一个动态树好题

    [BZOJ 2759] 一个动态树好题 题目描述 首先这是个基环树. 然后根节点一定会连出去一条非树边.通过一个环就可以解除根的答案,然后其他节点的答案就可以由根解出来. 因为要修改\(p_i\),所 ...

  5. bzoj 2759一个动态树好题

    真的是动态树好题,如果把每个点的父亲设成p[x],那么建出来图应该是一个环套树森林,拆掉一条边,就变成了动态树,考虑维护什么,对于LCT上每个节点,维护两组k和b,一组是他到他父亲的,一组是他LCT子 ...

  6. 【刷题】BZOJ 2759 一个动态树好题

    Description 有N个未知数x[1..n]和N个等式组成的同余方程组: x[i]=k[i]*x[p[i]]+b[i] mod 10007 其中,k[i],b[i],x[i]∈[0,10007) ...

  7. BZOJ 2759 一个动态树好题(动态树)

    题意 https://www.lydsy.com/JudgeOnline/problem.php?id=2759 思路 每个节点仅有一条有向出边, 这便是一棵基环内向树,我们可以把它在 \(\text ...

  8. BZOJ 2759 一个动态树好题 (LCT)

    PoPoQQQ 再一次orz-没看得特别明白的可以回来看看蒟蒻的补充口胡 我这里提一下关于splaysplaysplay维护的子树信息- 在原树上考虑,对于每一个点iii都有这样一个信息xi=ki∗x ...

  9. bzoj2049-洞穴勘测(动态树lct模板题)

    Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好 ...

随机推荐

  1. 为CentOS系统配置防火墙设置

    在各种操作系统中,为了保护系统在网络中是相对安全的,我们通常都会给操作系统配置防火墙,通过配置防火墙来限定哪些流量可以进来,哪些流量可以出去,通过这样的一种方式,可以有效的管理系统的流量,从一定程度上 ...

  2. Python接口测试实战3(下)- unittest测试框架

    如有任何学习问题,可以添加作者微信:lockingfree 课程目录 Python接口测试实战1(上)- 接口测试理论 Python接口测试实战1(下)- 接口测试工具的使用 Python接口测试实战 ...

  3. unittest,requests——接口测试脚本及报告

    用unittest管理两个利用requests模块,做百度搜索的简单接口测试用例,之后自动输出报告 # encoding=utf-8import requests,unittest,HTMLTestR ...

  4. Spring学习(3):IOC基础(转载)

    一. IoC是什么 Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象 ...

  5. IBM基于Kubernetes的容器云全解析

    基于Kubernetes的容器云 容器云最主要的功能是以应用为中心,帮助用户把所有的应用以容器的形式在分布式里面跑起来,最后把应用以服务的形式呈现给用户.容器云里有两个关键点,一是容器编排,二是资源调 ...

  6. django的htpp请求之WSGIRequest

    WSGIRequest对象 Django在接收到http请求之后,会根据http请求携带的参数以及报文信息创建一个WSGIRequest对象,并且作为视图函数第一个参数传给视图函数.这个参数就是dja ...

  7. SVN服务器搭建及客户端配置

    为什么要使用SVN? 在程序的编写过程中,每个程序员都会负责开发一个或多个模块,且开发中会生成很多不同的版本, 这就需要程序员有效的管理代码,在需要的时候可以迅速,准确取出相应的版本. Subvers ...

  8. socket编程 123

    1. 预备知识 一直以来很少看到有多少人使用php的socket模块来做一些事情,大概大家都把它定位在脚本语言的范畴内吧,但是其实php的socket模块可以做很多事情,包括做ftplist,http ...

  9. 在PHP中,是以分好结束一条语句的吗

    在PHP中,是以分号结束一条语句的,这个和C语言类似. 但是,有一条例外,对于PHP结束tag之前的语句,是可以不写分号的: <?php if ($a == $b) { echo "R ...

  10. 简易四则运算生成程序——添加GUI支持

    项目成员:张金生     张政 工程地址: https://coding.net/u/jx8zjs/p/paperOne/git ssh://git@git.coding.net:jx8zjs/pap ...