首页 > 其他分享 >Living-Dream 系列笔记 第83期

Living-Dream 系列笔记 第83期

时间:2024-10-20 14:59:51浏览次数:1  
标签:Living cur int siz son dep 重子 83 Dream

DSU on tree

又称 tree 上启发式合并。

适用于统计子树内信息。

原理:贪心。

特征:通常需要一个全局的桶。

实现方法:对于每个节点,先统计「轻子树」并清空桶,再统计「重子树」并保留桶。其中,「重子树」表示每个节点最大的子树,其余则称「轻子树」。

通常需要离线询问。

正确性说明:类似于重链剖分,每个节点的「重子节点」(即「重子树」的根)将会从根到叶子节点形成一条「重链」。正因为是一条链,我们就能每次爬上去时仅需统计当前「重子节点」而无需再次统计整个「重子树」了,但是轻子树就需要再次统计了。

一些性质:

  • 一个非叶子节点总是有一个「重儿子」。

  • 一个节点可能是其父节点的「重儿子」,却不一定在「重链」上。

  • 一个节点至多向上走 \(\log n\) 步就会合并到重链上(考虑树的构建,可以看成是轻子树不断合并到重子树上去,这至少会使树的大小翻倍,而树的大小为 \(n\),我们最多是从叶子走到根,于是每次翻倍,最多走 \(\log n\) 步,并查集按秩合并也是一样的道理)。

时间复杂度:由性质 \(3\) 可得时间复杂度为 \(O(n \log n)\)(最多 \(n\) 个轻节点,但这显然十分跑不满)。

CF246E

直接上代码爽。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m;
int heavy;
string name[N];
int siz[N],dep[N],ans[N],son[N];
vector<int> G[N];
struct QUERY{
	int id,k;
};
vector<QUERY> qry[N];
set<string> st[N]; 

void dfs(int cur){ //寻找「重链」
	siz[cur]=1;
	for(int i:G[cur]){
		dep[i]=dep[cur]+1;
		dfs(i);
		siz[cur]+=siz[i];
		if(siz[i]>siz[son[cur]])
			son[cur]=i;
	}
}
void getans(int cur){ //更新挂在 cur 节点上的询问
	for(auto i:qry[cur]){
		int pos=dep[cur]+i.k;
		if(pos>n+1)
			ans[i.id]=0;
		else
			ans[i.id]=st[pos].size();
	}
}
void upd(int cur,int val){ //更新 cur 子树内的信息
	if(val==1)
		st[dep[cur]].insert(name[cur]);
	else
		st[dep[cur]].clear();
	for(int i:G[cur]){
		if(i==heavy) //避开「重子树」,先更新「轻子树」
			continue;
		upd(i,val);
	} 
}
void dfs2(int cur,int big){ //按顺序处理子树,其中 big 表示是否为「重子树」
	for(int i:G[cur]){
		if(i==son[cur]) //避开「重子树」,先处理「轻子树」
			continue;
		dfs2(i,0);
	}
	if(son[cur])
		dfs2(son[cur],1),heavy=son[cur]; //最后处理 「重子树」
	upd(cur,1);
	heavy=0; //清空是为了方便将轻子树的重子树一并清空
	getans(cur);
	if(!big)
		upd(cur,-1); //将轻子树信息清空
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		int r;
		cin>>name[i]>>r;
		G[r].push_back(i);
	}
	cin>>m;
	for(int i=1,v,k;i<=m;i++){
		cin>>v>>k;
		qry[v].push_back({i,k});
	}
	dfs(0);
	dfs2(0,0);
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<'\n';
	return 0;
}

CF208E

与上题几乎一致,就多加了一个倍增找到 \(p\) 级祖先。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m;
int heavy;
string name[N];
int siz[N],dep[N],ans[N],son[N],cnt[N],dp[N][31];
vector<int> G[N];
struct QUERY{
	int k,id;
};
vector<QUERY> qry[N];

int fnd(int x,int y){
	for(int i=21;i>=0;i--)
		if(y>=(1<<i))
			x=dp[x][i],y-=(1<<i);
	return x;
}
void dfs(int cur){
	siz[cur]=1;
	for(int i:G[cur]){
		dfs(i);
		siz[cur]+=siz[i];
		if(siz[i]>siz[son[cur]])
			son[cur]=i;
	}
}
void getans(int cur){
	for(auto i:qry[cur]){
		int pos=dep[cur]+i.k;
		if(!cur)
			ans[i.id]=0;
		else
			ans[i.id]=cnt[pos]-1;
	}
}
void upd(int cur,int val){
	cnt[dep[cur]]+=val;
	for(int i:G[cur]){
		if(i==heavy)
			continue;
		upd(i,val);
	}
}
void dfs2(int cur,int big){
	for(int i:G[cur]){
		if(i==son[cur])
			continue;
		dfs2(i,0);
	}
	if(son[cur])
		dfs2(son[cur],1),heavy=son[cur];
	upd(cur,1);
	heavy=0;
	getans(cur);
	if(!big)
		upd(cur,-1);
}
void init_lca(int cur,int fa){
	dep[cur]=dep[fa]+1;
	dp[cur][0]=fa;
	for(int i=1;(1<<i)<=dep[cur];i++)
		dp[cur][i]=dp[dp[cur][i-1]][i-1];
	for(int i:G[cur])
		init_lca(i,cur);
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		int r; cin>>r;
		G[r].push_back(i);
	}
	cin>>m;
	init_lca(0,0);
	for(int i=1,v,k;i<=m;i++){
		cin>>v>>k;
		int pos=fnd(v,k);
		qry[pos].push_back({k,i});
	}
	dfs(0);
	dfs2(0,0);
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<' ';
	return 0;
}

CF570D

与上题几乎一致,就多加了一个统计答案时只要出现次数为奇数的字母个数 \(\le 1\) 即为 Yes

code
//
//  CF570D.cpp
//
//
//  Created by _XOFqwq on 2024/10/20.
//

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+5;
int n,m;
int heavy;
char c[N];
int siz[N],dep[N],son[N];
bool ans[N];
vector<int> G[N];
int cnt[N][31];
struct QUERY{
    int id,k;
};
vector<QUERY> qry[N];

void dfs(int cur){
    siz[cur]=1;
    for(int i:G[cur]){
        dep[i]=dep[cur]+1;
        dfs(i);
        siz[cur]+=siz[i];
        if(siz[i]>siz[son[cur]])
            son[cur]=i;
    }
}
void getans(int cur){
    for(auto i:qry[cur]){
        int pos=i.k;
        if(pos>n)
            ans[i.id]=0;
        else{
            int f=0;
            for (char j='a'; j<='z'; j++) {
                if (cnt[pos][j]&1) {
                    f++;
                }
            }
            ans[i.id]=(f<=1);
        }
    }
}
void upd(int cur,int val){
    cnt[dep[cur]][c[cur]]+=val;
    for(int i:G[cur]){
        if(i==heavy)
            continue;
        upd(i,val);
    }
}
void dfs2(int cur,int big){
    for(int i:G[cur]){
        if(i==son[cur])
            continue;
        dfs2(i,0);
    }
    if(son[cur])
        dfs2(son[cur],1),heavy=son[cur];
    upd(cur,1);
    heavy=0;
    getans(cur);
    if(!big)
        upd(cur,-1);
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=2;i<=n;i++){
        int r; cin>>r;
        G[r].push_back(i);
    }
    for (int i=1; i<=n; i++) {
        cin>>c[i];
    }
    for(int i=1,v,k;i<=m;i++){
        cin>>v>>k;
        qry[v].push_back({i,k});
    }
    dep[1]=1,dfs(1);
    dfs2(1,0);
    for(int i=1;i<=m;i++)
        cout<<(ans[i]?"Yes\n":"No\n");
    return 0;
}

所以说 DSU on tree 的题还是很板的()

标签:Living,cur,int,siz,son,dep,重子,83,Dream
From: https://www.cnblogs.com/XOF-0-0/p/18487319

相关文章

  • DreamMesh4D: Video-to-4D Generation with Sparse-Controlled Gaussian-Mesh HybridR
    目录一、概述二、前置知识1、分数蒸馏采样 2、LBS 3、DQS4、EucDist和GeoDist算法三、相关工作1、三维生成2、4D表示3、4D生成四、DreamMesh4D1、静态阶段 2、动态阶段-可变形图建立 3、动态阶段--自适应可变蒙皮算法 一、概述    该论文提出了......
  • Springboot的洗衣店预约APP的设计与实现-附源码260839
    摘 要随着现在网络的快速发展,网络的应用在各行各业当中它很快融入到了许多学校的眼球之中,他们利用网络来做这个洗衣店预约的网站,随之就产生了“洗衣店预约系统”,这样就让用户洗衣店预约系统更加方便简单。对于本洗衣店预约系统的设计来说,它主要是采用后台采用java语言、s......
  • 基于springboot建筑造价师资格考试应试网站-附源码260839
    摘 要如何合理确定和有效控制工程投资,是工程项目建设的一大难题,如何使建筑工程造价管理与社会生产水平相适应,是建筑工程造价管理中需要解决的问题,只有加强建筑工程造价管理工作力度,提高建筑工程造价人员素质,才能使建筑工程造价管理走上国际化的道路。本文根据我国工程造价......
  • SSM黑河市劳务人员管理系统83i90 点赞收藏
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统内容:劳务人员,企业,缴费通知,招工信息,接单信息,订单信息,企业评分,收入统计开题报告内容一、选题背景与意义随着经济的快速发展和城市化进程的加速,黑河市......
  • 【并查集+dfs】codeforces 1833 E. Round Dance
    题意输入一个正整数\(T(1\leqT\leq10^4)\),表示接下来输入\(T\)组测试用例,对于每一个测试用例:第一行,输入一个正整数\(n(2\leqn\leq2*10^5)\)第二行,输入\(n\)个正整数\(a_i(1\leqa_i\leqn)\),表示节点\(i\)到节点\(a_i\)存在一条有向边,保证无自环这\(n......
  • 代码随想录算法训练营 | 115.不同的子序列,583. 两个字符串的删除操作,72. 编辑距离
    115.不同的子序列题目链接:115.不同的子序列文档讲解︰代码随想录(programmercarl.com)视频讲解︰不同的子序列日期:2024-10-18想法:dp[i][j]表示以s[i-1],t[j-1]结尾的s,t自学列中满足s的子序列为t的个数,如果s[i-1],t[j-1]相等,那么个数应该跟双方上一个结尾状态dp[i-1][j-......
  • P8969 幻梦 | Dream with Dynamic
    Sol好题!注意到popcount操作会使数以\(\log\)的速度变小,所以没有加操作的话我们就可以直接暴力维护。但是注意到有加操作,考虑懒标记,先popcount后加。当一个区间popcount之后,值域范围极小,所以考虑暴力对每一种数预处理popcount。这里其实可以用线段树但是我懒了,用了分......
  • 洛谷 P1983 [NOIP2013 普及组] 车站分级(拓扑排序)
    题目传送门解题思路对于每一趟列车,我们可以知道其中没有经过的车站的级别肯定会比经过的车站的级别低。于是,我们可以根据这种关系来建一个图。将等级小的车站往等级大的车站建边。于是,我们可以发现这是一个DAG(有向无环图),所以我们可以拓扑排序。我们从等级最小的车站......
  • 83. 删除排序链表中的重复元素 线性法&快慢双指针
    83.删除排序链表中的重复元素给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。示例1:输入:head=[1,1,2]输出:[1,2]示例2:输入:head=[1,1,2,3,3]输出:[1,2,3]提示:链表中节点数目在范围 [0,300] 内-100<=......
  • DataDream:调一调更好,基于LoRA微调SD的训练集合成新方案 | ECCV'24
    尽管文本到图像的扩散模型已被证明在图像合成方面达到了最先进的结果,但它们尚未证明在下游应用中的有效性。先前的研究提出了在有限的真实数据访问下为图像分类器训练生成数据的方法。然而,这些方法在生成内部分布图像或描绘细粒度特征方面存在困难,从而阻碍了在合成数据集上训练的......