题目分析:

好题。本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法。

首先考虑静态的情况。这个的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】【树链剖分】【线段树】的更多相关文章

  1. 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)

    [BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...

  2. 洛谷 P3781 - [SDOI2017]切树游戏(动态 DP+FWT)

    洛谷题面传送门 SDOI 2017 R2 D1 T3,nb tea %%% 讲个笑话,最近我在学动态 dp,wjz 在学 FWT,而我们刚好在同一天做到了这道题,而这道题刚好又是 FWT+动态 dp ...

  3. 【bzoj5210】最大连通子块和 树链剖分+线段树+可删除堆维护树形动态dp

    题目描述 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块和. 其中,一棵子树的最大连通子块和指的是:该子树 ...

  4. 【bzoj4712】洪水 树链剖分+线段树维护树形动态dp

    题目描述 给出一棵树,点有点权.多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值. 输入 输入文件第一行包含 ...

  5. BZOJ2819Nim——树链剖分+线段树+Nim游戏

    题目描述 著名游戏设计师vfleaking,最近迷上了Nim.普通的Nim游戏为:两个人进行游戏,N堆石子,每回合可以取其中某一堆的任意多个,可以取完,但不可以不取.谁不能取谁输.这个游戏是有必胜策略 ...

  6. 洛谷P3313 [SDOI2014]旅行 题解 树链剖分+线段树动态开点

    题目链接:https://www.luogu.org/problem/P3313 这道题目就是树链剖分+线段树动态开点. 然后做这道题目之前我们先来看一道不考虑树链剖分之后完全相同的线段树动态开点的题 ...

  7. BZOJ 3589 动态树 (树链剖分+线段树)

    前言 众所周知,90%90\%90%的题目与解法毫无关系. 题意 有一棵有根树,两种操作.一种是子树内每一个点的权值加上一个同一个数,另一种是查询多条路径的并的点权之和. 分析 很容易看出是树链剖分+ ...

  8. B20J_3231_[SDOI2014]旅行_树链剖分+线段树

    B20J_3231_[SDOI2014]旅行_树链剖分+线段树 题意: S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,城市信仰不同的宗教,为了方便,我们用不同的正整数代表各种宗教. S国 ...

  9. BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)

    题目链接 BZOJ 洛谷 点分治 单调队列: 二分答案,然后判断是否存在一条长度在\([L,R]\)的路径满足权值和非负.可以点分治. 对于(距当前根节点)深度为\(d\)的一条路径,可以用其它子树深 ...

  10. BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)

    前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...

随机推荐

  1. Item 18: 使用srd::unique_ptr来管理独占所有权的资源

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 当你需要一个智能指针的时候,std::unique_ptr通常是最 ...

  2. Acceleration for ML 论文导读

    Energy efficient parallel neuromorphic architectures with approximate arithmetic on FPGA Motivation ...

  3. hibernate延迟加载org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.javakc.hibernate.onetomany.entity.DeptEntity.emp, could not initialize proxy - no Session

    public static void main(String[] args) {  DeptEntity dept = getDept("402882e762ae888d0162ae888e ...

  4. Vue2.0 搭建Vue脚手架(vue-cli)

    介绍 Vue.js是一套构建用户界面的渐进式框架.Vue 只关注视图层,采用自底向上增量开发的设计.Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. 阅读之前需要了解的知 ...

  5. dynamo与cassandra区别

    虽说cassandra是dynamo的开源版本,但两者还是有很大区别的. coordinator的选取: 在dynamo论文中,一般是preference list中N个副本的第一个 为什么叫“一般” ...

  6. 解决selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home

    解决方案: 1.查看浏览器当前版本:chrome://version/. 2.到https://sites.google.com/a/chromium.org/chromedriver/downloa ...

  7. Python技术之书籍汇总

    近日,一直在学习Python,发现有关的书籍还是很多值得一读的,所以在此总结一下.以后慢慢去研读吧!!! Python入门 <Python编程快速上手——让繁琐工作自动化> 作者: [美] ...

  8. java学习之—排序

    package test3; public class Sort{ /** * 冒泡排序 * @param array */ public void bubbleSort(int[] array) { ...

  9. python之路--类与类之间的关系

    类和类之间的关系 在我们的世界中事物和事物之间总会有一些联系. 在面向对象中. 类和类之间也可以产生相关的关系 1. 依赖关系 执行某个动作的时候. 需要xxx来帮助你完成这个操作. 此时的关系是最轻 ...

  10. Delphi调用MSSQL存储过程返回的多个数据集的方法

    varaintf:_Recordset;RecordsAffected:OleVariant; begin ADOStoredProc1.Close;ADOStoredProc1.Open;aintf ...