洛谷 P5658 [CSP-S2019] 括号树
链接:
分析:
显然我们应该在dfs树的同时维护每个点的答案。
注意到第 \(u\) 个点的答案可以分成两部分,不包含 \(u\) 点时的答案,和加入 \(u\) 点后新增的答案,前者可以从父节点继承下来,所以我们对于每个点考虑的是加入该点后新增的答案。
在dfs树时会回溯,所以我们还需要考虑撤销这个点带来的影响。
所以我们实际要做的就是思考出一种策略,使其能够对新加入的点维护出新增的答案,还能将这个点的影响撤销。
算法:
对于一个点,我们分类讨论左括号和右括号的两种操作。
对于加入点的策略。
首先让当前点继承父节点的答案,然后考虑新增答案。
如果加入的是右括号,它可能会与前面的第一个左括号匹配,所以考虑对左括号维护一个栈。考虑这两个括号中间的部分,它一定是一个合法子串或空串(假如中间有不匹配的右括号,那么它一定会和当前左括号匹配,假如中间有不匹配的左括号,那么它会成为第一个左括号,变成当前右括号的匹配对象)。所以当前右括号匹配后一定会新增一个答案,考虑其他的答案,一定是多组匹配的括号相连,形如\((\cdots)(\cdots)(\cdots)\)。所以我们用while
循环查询当前now
节点匹配的左括号的前一个是否是右括号且有匹配(匹配括号内部一样也是合法子串。同时我们需要对右括号维护一个匹配左括号):如果是,那么答案++,并将now
更新为这个右括号,循环查询;如果不是,那么退出。这样就可以正确地维护出当前加入点的新增答案了。
如果加入的是左括号,那么不会有新增的答案(不可能与另一个右括号匹配),只需维护栈。
对于退出点时策略。
如果退出的是右括号,那么其他点的影响只有“可能占据了一个匹配的左括号“,重新在栈里加入这个左括号即可。
如果退出的是左括号,那么在栈里弹出即可。
优化:
基于以上算法,我们可以写出这样的程序:
void dfs(int u){
dp[u]=dp[fa[u]];
if(c[u]=='(') sta[++top]=u;
else{
if(top){
dp[u]++;
match[u]=sta[top--];
int now=u;
while(match[fa[match[now]]]){
now=fa[match[now]];
dp[u]++;
}
}
}
ans^=(u*dp[u]);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
if(c[u]=='(')top--;
else if(match[u])sta[++top]=match[u];
}
但是只获得了8AC,2TLE的好成绩。
反思整个算法,最可能影响时间复杂度的就是那个while
循环了。我们看循环的过程,它实际是在不停向上跳,返回右括号的数量,对于一个右括号向上跳的结果是不会被后面的节点影响的,所以我们可以对每个右括号维护一个up值,对于一个右括号,只用在上一个右括号的up值加1就是当前的up值。新增的答案就是当前的up值了。
这样就把一个while
循环优化成了 \(O(1)\) 查询。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int N=5e5+5;
int n;
char c[N];
struct edge{
int v,next;
}e[N];
int head[N],en;
void insert(int u,int v){
e[++en].v=v;
e[en].next=head[u];
head[u]=en;
}
int fa[N];
int sta[N],top;//left(
int match[N],up[N];//right)
int ans;
int dp[N];
void dfs(int u){
dp[u]=dp[fa[u]];
if(c[u]=='(') sta[++top]=u;
else if(top){
match[u]=sta[top--];
up[u]=up[fa[match[now]]]+1;
dp[u]+=up[u];
}
ans^=(u*dp[u]);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
if(c[u]=='(')top--;
else if(match[u])sta[++top]=match[u];
}
signed main(){
n=in;
for(int i=1;i<=n;i++)
cin>>c[i];
for(int i=2;i<=n;i++){
fa[i]=in;
insert(fa[i],i);
}
dfs(1);
cout<<ans;
return 0;
}
题外话:
两年前的普及组蒟蒻看到这题就放弃了场外提高,两年后的我看到这题在30分钟内成功AC!
洛谷 P5658 [CSP-S2019] 括号树的更多相关文章
- 洛谷 P3384 【模板】树链剖分-树链剖分(点权)(路径节点更新、路径求和、子树节点更新、子树求和)模板-备注结合一下以前写的题目,懒得写很详细的注释
P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...
- 洛谷p3384【模板】树链剖分题解
洛谷p3384 [模板]树链剖分错误记录 首先感谢\(lfd\)在课上调了出来\(Orz\) \(1\).以后少写全局变量 \(2\).线段树递归的时候最好把左右区间一起传 \(3\).写\(dfs\ ...
- 括号树 noip(csp??) 2019 洛谷 P5658
洛谷AC通道 本题,题目长,但是实际想起来十分简单. 首先,对于树上的每一个后括号,我们很容易知道,他的贡献值等于上一个后括号的贡献值 + 1.(当然,前提是要有人跟他匹配,毕竟题目中要求了,是不同的 ...
- 洛谷 P3384 【模板】树链剖分
树链剖分 将一棵树的每个节点到它所有子节点中子树和(所包含的点的个数)最大的那个子节点的这条边标记为"重边". 将其他的边标记为"轻边". 若果一个非根节点的子 ...
- 洛谷P3459 [POI2007]MEG-Megalopolis(树链剖分,Splay)
洛谷题目传送门 正解是树状数组维护dfn序上的前缀和,这样的思路真是又玄学又令我惊叹( 我太弱啦,根本想不到)Orz各路Dalao 今天考了这道题,数据范围还比洛谷的小,只有\(10^5\)(害我复制 ...
- [洛谷P1198/BZOJ1012][JSOI2008] 最大数 - 树状数组/线段树?
其实已经学了树状数组和线段树,然而懒得做题,所以至今没写多少博客 Description 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数 ...
- 点分治模板(洛谷P4178 Tree)(树分治,树的重心,容斥原理)
推荐YCB的总结 推荐你谷ysn等巨佬的详细题解 大致流程-- dfs求出当前树的重心 对当前树内经过重心的路径统计答案(一条路径由两条由重心到其它点的子路径合并而成) 容斥减去不合法情况(两条子路径 ...
- 洛谷P2617 Dynamic Rankings (主席树)
洛谷P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a ...
- 【BZOJ】1012: [JSOI2008]最大数maxnumber /【洛谷】1198(线段树)
Description 现在请求你维护一个数列,要求提供以下两种操作:1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度.2. 插 ...
- 洛谷 P3359 改造异或树
题目描述 给定一棵n 个点的树,每条边上都有一个权值.现在按顺序删掉所有的n-1条边,每删掉一条边询问当前有多少条路径满足路径上所有边权值异或和为0. 输入输出格式 输入格式: 第一行一个整数n. 接 ...
随机推荐
- mysql升级-rpm安装
mysql版本5.7.29升级到5.7.30 由于我们安装mysql的方式是通过mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar中的rpm包安装:rpm -Uvh my ...
- 基于pgpool搭建postgressql集群部署
postgresql集群搭建 基于pgpool中间件实现postgresql一主多从集群部署,这里用两台服务器作一主一从示例 虚拟机名 IP 主从划分 THApps 192.168.1.31 主节点 ...
- shell 脚本获取数组字符串长度
#!/bin/sh source /etc/init.d/functions funOne() { array=(I am dfh kjlhfjksdf sdfj jdkfhaskl mjjoldfu ...
- 【CTF】msf和impacket联合拿域控内网渗透-拿域控
前言 掌控安全里面的靶场内网渗透,练练手! 内网渗透拿域控 环境:http://afsgr16-b1ferw.aqlab.cn/?id=1 1.进去一看,典型的sql注入 2.测试了一下,可以爆库,也 ...
- P3190-[HNOI2007]神奇游乐园【插头dp】
正题 题目链接:https://www.luogu.com.cn/problem/P3190 题目大意 \(n*m\)的网格上有权值,求一条权值和最大的不交回路. \(1\leq n\leq 100, ...
- python3之cx_Freeze使用(PyQt5)
1. cx_Freeze简介 Python脚本在装有Python的系统中可以直接双击运行,但绝大多数普通用户并没有配置此类环境,而编译为可执行二进制文件后,用户无需预先安装Python及依赖库即可 ...
- 二、mybatis之数据输出
上一篇我们做了一个入门案例,是我们做mybatis的基本步骤,不熟悉的可以回顾一下https://www.cnblogs.com/jasmine-e/p/15330355.html,在这篇文章中只是简 ...
- Vue组件间的数据传输
1.父组件向子组件传输数据:自定义属性 1 //父组件 2 <Son :msg="message" :user="userinfo"></So ...
- disruptor笔记之八:知识点补充(终篇)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Linux下关于用户账户的几个文件解析
Linux下关于用户账户的几个文件解析 Linux是一个多用户系统,但是对于一个多用户共存的系统中,当然不能够出现用户相互越权等一系列的安全问题,所以如何正确的管理账户成为了Linux系统中至关重要的 ...