LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】
题目分析:
好题。本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法。
首先考虑静态的情况。这个的DP方程非常容易写出来。
接着可以注意到对于异或结果的计数可以看成一个FWT的过程,进一步地可以注意到FWT在中途没有还原的必要。从FWT的过程中我们可以发现FWT具有可加性和交换律结合律。
这样原问题可以在静态的情况下通过树形DP做到$O(nm)$。
考虑动态的问题。根据《神奇的子图》命题报告及其拓展中描述的算法五,我们应该不难想到基于树链剖分的这样的做法。
首先对树进行轻重路径剖分,构建一棵重链树。接着将树中轻子树的影响放进对应的重链父节点(不是top节点)上。接着在线段树上合并问题的答案。
对于一个线段树中的结点,经过分析我们应该可以知道需要记录的信息有这么几个:
L:从这个线段树区间所对应的子重链的顶端开始延伸出的包含轻边子树的所有情况的FWT值。
R:从这个线段树区间所对应的子重链的底端开始延伸出的包含轻边子树的所有情况的FWT值。
C:这个线段树区间所对应的子重链上的所有点都被选择,接着往子树方向延伸的所有情况的FWT值。
tot:这个线段树区间所对应的子重链以及它的所有轻边子树看作一个整体的树的答案的FWT值。
有了这些信息之后线段树的合并答案就不难写出来了。我们用下标$0$表示左子树,下标$1$表示右子树。
$L = L_0+C_0*L_1$
$R = R_1+C_1*R_0$
$C = C_0*C_1$
$tot = tot_0+tot_1+L_1*R_0$
我们还提到了线段树中的叶子结点是包含它的所有轻边子树信息的。根据《神奇的子图》命题报告及其拓展中的描述,我们能够想到通过普通的DP来转移答案。
下面我们用H来表示它的一个轻边儿子,Now表示叶子对应的原树上的点,Base为128个单位FWT之一。注意到对于叶子结点,他的L和R和C是相同的。所以忽略R和C。这样叶子的转移可以这样写:
$Leaf_L = Base[v[Now]]*\prod_{i \in son}(H_L+Base[0])$可以理解为01背包的选和不选。
$Leaf_{tot} = \sum_{i \in son}tot_i + Leaf_L$.
有了它们,我们现在可以进行修改了。
对于一次修改,我们要做的是,沿着重链树往上跳,遇到一条重链则更改重链在线段树中的信息。这样做只会经过$O(logn)$条重链,每条重链只会修改一个点,对应的在线段树上只会修改一个点,线段树每次修改时间复杂度为$O(logn)$,所以这是$O(log^2n)$的。
除以0的问题可以另外建一棵线段树解决,但大家好像都说可以记录0的个数,我不会,希望会的可以留个言。
时间复杂度$O(nmlog^2n)$,树链剖分常数较小,可以通过所有数据。
upd:LOJ时间倒数第三。。。
代码:
#include<bits/stdc++.h>
using namespace std; const int maxn = ;
const int maxm = ;
const int mod = ; int n,m,num,num2,bfsnum;
int v[maxn];
vector <int> g[maxn]; vector <int> Chain[maxn]; int dep[maxn],fa[maxn],top[maxn],number[maxn],sz[maxn],son[maxn];
int tail[maxn],where[maxn],im[maxn],bfsin[maxn],bfsout[maxn]; struct func{
int cont[maxm];
func operator * (func b){
for(int i=;i<=maxm;i++) b.cont[i] = (b.cont[i]*cont[i])%mod;
return b;
}
func operator + (func b){
for(int i=;i<=maxm;i++) b.cont[i] = (b.cont[i]+cont[i])%mod;
return b;
}
func operator - (func b){
for(int i=;i<=maxm;i++) b.cont[i] = (cont[i]-b.cont[i]+mod)%mod;
return b;
}
}base[maxm],T2[maxn<<],Newnumber; struct node{
func L,R,C,tot;
//左端延伸,右端延伸,总和
}T[maxn<<]; void push_up(int now){
T[now].L = T[now<<].C*T[now<<|].L + T[now<<].L;
T[now].R = T[now<<|].C*T[now<<].R + T[now<<|].R;
T[now].C = T[now<<].C*T[now<<|].C;
T[now].tot = T[now<<].tot+T[now<<|].tot+T[now<<].R*T[now<<|].L;
} void dfs1(int now,int f,int dp){
dep[now] = dp; fa[now] = f;
for(int i=;i<g[now].size();i++){
if(g[now][i] == f) continue;
dfs1(g[now][i],now,dp+);
sz[now] += sz[g[now][i]];
if(sz[son[now]] < sz[g[now][i]]) son[now] = g[now][i];
}
sz[now]++;
} void dfs2(int now,int tp){
top[now] = tp; number[now] = ++num; where[num] = now;
if(now == tp && now!=){Chain[top[fa[now]]].push_back(now);}
if(sz[now] != sz[son[now]] + ){
bfsin[now] = bfsnum+;
if(fa[now] != ) bfsout[now] = bfsnum+g[now].size()-;
else bfsout[now] = bfsnum+g[now].size()-;
}else bfsin[now] = ,bfsout[now] = -;
for(int i=;i<g[now].size();i++){
if(g[now][i] == fa[now] || g[now][i] == son[now]) continue;
im[g[now][i]] = ++bfsnum;
}
if(son[now]) {dfs2(son[now],tp);tail[now]=tail[son[now]];}
else tail[now] = now;
for(int i=;i<g[now].size();i++){
if(g[now][i] == fa[now] || g[now][i] == son[now]) continue;
dfs2(g[now][i],g[now][i]);
}
} node merge(node alpha,node beta){
node gamma; gamma.L = alpha.L + beta.L*alpha.C;
gamma.R = beta.R + alpha.R*beta.C;
gamma.C = alpha.C*beta.C;
gamma.tot = alpha.tot+beta.tot+alpha.R*beta.L;
return gamma;
} node Query(int now,int tl,int tr,int l,int r){
if(tl >= l && tr <= r) return T[now];
int mid = (tl+tr)/;
if(mid >= r) return Query(now<<,tl,mid,l,r);
if(mid < l) return Query(now<<|,mid+,tr,l,r);
node ans1 = Query(now<<,tl,mid,l,r);
node ans2 = Query(now<<|,mid+,tr,l,r);
return merge(ans1,ans2);
} void build_tree(int now,int tl,int tr,int l,int r){
if(tl > r || tr < l) return;
if(tl == tr){
tl = where[tl]; T[now].L = base[v[tl]];
for(int i=;i<g[tl].size();i++){
if(g[tl][i] == fa[tl] || g[tl][i] == son[tl]) continue;
int SS = g[tl][i];
node forw = Query(,,n,number[SS],number[tail[SS]]);
T[now].tot = T[now].tot + forw.tot;
T[now].L = T[now].L*(base[]+forw.L);
}
T[now].R = T[now].C = T[now].L;
T[now].tot = T[now].tot + T[now].L;
}else{
int mid = (tl+tr)/;
build_tree(now<<,tl,mid,l,r);
build_tree(now<<|,mid+,tr,l,r);
push_up(now);
}
} void dfs3(int now){
for(int i=;i<Chain[now].size();i++){ dfs3(Chain[now][i]); } //sub
int p = tail[now];
build_tree(,,n,number[now],number[tail[now]]);
} void read(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++) scanf("%d",&v[i]);
for(int i=;i<n;i++){
int x,y; scanf("%d%d",&x,&y);
g[x].push_back(y); g[y].push_back(x);
}
} void FWT(func &now,int data){
now.cont[data] = ;
for(int i=;i<=maxm;i<<=){
int yum = maxm/i;
for(int j=;j<maxm;j+=yum*){
for(int k=;k<yum;k++){
int x = now.cont[j+k],y = now.cont[j+k+yum];
now.cont[j+k] = x+y; now.cont[j+k+yum] = (x-y+mod)%mod;
}
}
}
} void IFWT(func &now){
for(int i=;i<maxm;i<<=){
for(int j=;j<=maxm;j+=i*){
for(int k=;k<i;k++){
int x = now.cont[j+k],y = now.cont[j+k+i];
now.cont[j+k] = ((x+y)*)%mod;
now.cont[j+k+i] = ((x-y+mod)*)%mod;
}
}
}
} func VQuery(int now,int tl,int tr,int l,int r){
if(tl >= l && tr <= r) return T2[now];
if(tl > r || tr < l) return base[];
int mid = (tl+tr)/;
return VQuery(now<<,tl,mid,l,r)*VQuery(now<<|,mid+,tr,l,r);
} void VModify(int now,int tl,int tr,int place){
if(tl == tr){
T2[now] = Newnumber;
}else{
int mid = (tl+tr)/;
if(place <= mid) VModify(now<<,tl,mid,place);
else VModify(now<<|,mid+,tr,place);
T2[now] = T2[now<<]*T2[now<<|];
}
} void Modify(int now,int tl,int tr,int place){
if(tl == tr){
tl = where[tl];
func res = VQuery(,,bfsnum,bfsin[tl],bfsout[tl]);
T[now].tot = T[now].tot - T[now].L;
T[now].L = T[now].R = T[now].C = res*base[v[tl]];
T[now].tot = T[now].tot + T[now].L;
}else{
int mid = (tl+tr)/;
if(place <= mid) Modify(now<<,tl,mid,place);
else Modify(now<<|,mid+,tr,place);
push_up(now);
}
} void Erase(int now,int tl,int tr,int place,int dr){
if(tl == tr){
tl = where[tl];
if(dr == )T[now].tot = T[now].tot - Newnumber;
else T[now].tot = T[now].tot + Newnumber;
}else{
int mid = (tl+tr)/;
if(place <= mid) Erase(now<<,tl,mid,place,dr);
else Erase(now<<|,mid+,tr,place,dr);
push_up(now);
}
} void work(){
for(int i=;i<maxm;i++) FWT(base[i],i);
dfs1(,,);
dfs2(,);
dfs3();
for(int i=;i<=n;i++){
if(im[i]){
Newnumber=Query(,,n,number[i],number[tail[i]]).L + base[];
VModify(,,bfsnum,im[i]);
}
}
int q; scanf("%d",&q);
for(int i=;i<=q;i++){
char str[]; scanf("%s",str);
if(str[] == 'C'){
int x,y; scanf("%d%d",&x,&y);
int now = x;v[x] = y;
stack<int> sta;
while(fa[top[now]]!=){sta.push(top[now]); now=fa[top[now]];}
while(!sta.empty()){ // clear tot
int hd = sta.top();sta.pop();
Newnumber = Query(,,n,number[hd],number[tail[hd]]).tot;
Erase(,,n,number[fa[hd]],);
} now = x;
while(now != ){
Modify(,,n,number[now]);
now = top[now];
Newnumber = Query(,,n,number[now],number[tail[now]]).tot;
if(fa[top[now]]) Erase(,,n,number[fa[now]],);
Newnumber=base[]+Query(,,n,number[now],number[tail[now]]).L;
if(im[now]) VModify(,,bfsnum,im[now]);
now = fa[now];
}
}else{
int k; scanf("%d",&k);
func ans = Query(,,n,number[],number[tail[]]).tot;
IFWT(ans);
printf("%d\n",ans.cont[k]);
}
}
} int main(){
read();
work();
return ;
}
LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】的更多相关文章
- 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)
[BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...
- 洛谷 P3781 - [SDOI2017]切树游戏(动态 DP+FWT)
洛谷题面传送门 SDOI 2017 R2 D1 T3,nb tea %%% 讲个笑话,最近我在学动态 dp,wjz 在学 FWT,而我们刚好在同一天做到了这道题,而这道题刚好又是 FWT+动态 dp ...
- 【bzoj5210】最大连通子块和 树链剖分+线段树+可删除堆维护树形动态dp
题目描述 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块和. 其中,一棵子树的最大连通子块和指的是:该子树 ...
- 【bzoj4712】洪水 树链剖分+线段树维护树形动态dp
题目描述 给出一棵树,点有点权.多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值. 输入 输入文件第一行包含 ...
- BZOJ2819Nim——树链剖分+线段树+Nim游戏
题目描述 著名游戏设计师vfleaking,最近迷上了Nim.普通的Nim游戏为:两个人进行游戏,N堆石子,每回合可以取其中某一堆的任意多个,可以取完,但不可以不取.谁不能取谁输.这个游戏是有必胜策略 ...
- 洛谷P3313 [SDOI2014]旅行 题解 树链剖分+线段树动态开点
题目链接:https://www.luogu.org/problem/P3313 这道题目就是树链剖分+线段树动态开点. 然后做这道题目之前我们先来看一道不考虑树链剖分之后完全相同的线段树动态开点的题 ...
- BZOJ 3589 动态树 (树链剖分+线段树)
前言 众所周知,90%90\%90%的题目与解法毫无关系. 题意 有一棵有根树,两种操作.一种是子树内每一个点的权值加上一个同一个数,另一种是查询多条路径的并的点权之和. 分析 很容易看出是树链剖分+ ...
- B20J_3231_[SDOI2014]旅行_树链剖分+线段树
B20J_3231_[SDOI2014]旅行_树链剖分+线段树 题意: S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,城市信仰不同的宗教,为了方便,我们用不同的正整数代表各种宗教. S国 ...
- BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)
题目链接 BZOJ 洛谷 点分治 单调队列: 二分答案,然后判断是否存在一条长度在\([L,R]\)的路径满足权值和非负.可以点分治. 对于(距当前根节点)深度为\(d\)的一条路径,可以用其它子树深 ...
- BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)
前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...
随机推荐
- Flask序列化
我们在做后台接口的时候,对于返回值,用的最多的就是json数据格式 flask中,返回json数据格式,我们可以用到flask的jsonify函数. 对于基础序列是可以直接序列化的,但是更多的情况下, ...
- Python全栈开发之路 【第七篇】:面向对象编程设计与开发(1)
本节内容 一.编程范式 编程指的是写程序.敲代码,就是指程序员用特定的语法.数据结构和算法编写的代码,目的是来告诉计算机如何执行任务的. 在编程的世界里最常见的两大流派是:面向过程与面向对象.“功夫的 ...
- Beta阶段团队成员贡献分分配规则
Beta阶段团队成员贡献分分配规则 Alpha阶段贡献分分配有些负责,在这里进行一些修改: 对任务完成得分部分进行了简化 对发现bug的惩罚措施进行了修改 移除了优化得分,不鼓励修改他人代码 移除了帮 ...
- Divisors of Two Integers CodeForces - 1108B (数学+思维)
Recently you have received two positive integer numbers xx and yy. You forgot them, but you remember ...
- 牛客练习赛 A题 筱玛的快乐
链接:https://ac.nowcoder.com/acm/contest/342/A来源:牛客网 筱玛的快乐 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 524288K,其他语 ...
- ibeacon和蓝牙有什么区别_它们的区别在哪里
iBeacon概述 iBeacon是苹果公司2013年9月发布的移动设备用OS(iOS7)上配备的新功能.其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用BLE技术向周围发送自己特有的ID, ...
- 关于iframe页面里的重定向问题
最近公司做的一个功能,使用了iframe,父页面内嵌子页面,里面的坑还挺多的,上次其实就遇到过,只不过今天在此描述一下. 请允许我画个草图: 外层大圈是父级页面,里层是子级页面,我们是在父级引用子级页 ...
- Servlet处理GET和POST请求
doGet() . doPost().service()方法 doGet()表示,当客户端是使用get方式请求该servlet时,那么就会触发执行doGet()方法中的代码. doPost()表示,当 ...
- MySQL的binlog及关闭方法
如何关闭MySQL日志,删除mysql-bin.0000*日志文件 - VPS侦探https://www.vpser.net/manage/delete-mysql-mysql-bin-0000-lo ...
- 监控系统对比 Ganglia vs Open-falcon vs Prometheus vs Zabbix vs Nagios vs PandoraFMS
Zabbix vs Nagios vs PandoraFMS: an in depth comparison - Pandora FMS - The Monitoring Bloghttps://bl ...