zoukankan      html  css  js  c++  java
  • 括号树

    括号树

    P5658 括号树

    题目简述:

    PS:具体描述请见上面的题目链接

    给定n表示树的大小,然后给出每个点是左括号还是右括号,再给出2~n-1每个点的父节点f的编号(1一定为根节点)

    定义si:将根结点到i号结点的简单路径上的括号,按节点经过顺序依次排列组成的字符串

    要求对所有的i求出,si中有多少个互不相同的子串是合法括号串

    合法字符串的定义:

    1. ()是合法括号串
    2. 如果A是合法括号串,则(A)是合法括号串
    3. 如果A,B是合法括号串,则AB是合法括号串

    设si共有ki个不同子串是合法括号串,你只需要求出所有i×ki的异或和


    数据范围:

    数据范围


    算法&知识点:

    链&树、栈、DFS


    解题历程

    因为是考试的题,我们就用解决考试题的思路来解决这道题

    • 读懂题意
    1. 求出所有i×ki的异或和,注意是先相乘再异或和
    2. 了解所有合法字符串的样子:()、(())、()()
    • 分析数据范围

    看到特殊性质一栏,发现有接近一半的数据满足“fi=i-1”,这是啊!!相对于直接处理复杂的树,先将链的部分分拿到岂不是更稳?

    • 处理链的部分分
    1. 直接处理链好像也没什么思路,那我们就先用暴力来模拟处理链的情况,如下(思路就是纯枚举,纯暴力,20pts):
    /* 接近O(N^4)的复杂度,只能跑过前四个点,20pts */
    
    #include <bits/stdc++.h>
    using namespace std;
    int n,x,ans,sum[510005];
    char op[510005];
    string ops;
    
    inline bool check(string s) { //check子函数,开栈来判断括号是否匹配 
    	stack<char> fh;
    	for(register int i=0;i<s.length();i++) {
    		if(s[i]=='(') { //左括号直接入栈 
    			fh.push(s[i]);
    		}
    		else if(s[i]==')') {
    			if(fh.empty()) return false; //如果栈为空且当前为有括号,肯定不合法 
    			if(!fh.empty()&&fh.top()=='(') fh.pop(); //如果栈顶为左括号,则一一对应,直接弹出 
    		}
    	}
    	if(fh.empty()) return true; //如果为空,说明合法;反之,不合法 
    	else return false;
    }
    
    int main() {
    	scanf("%d",&n);
    	scanf("%s",op+1);
    	for(register int i=2;i<=n;i++) { //因为只处理链的情况,所以父亲节点可以不管 
    		scanf("%d",&x);
    	}
    	for(register int i=1;i<=n;i++) { //表示从根节点到第i号节点 
    		for(register int l=1;l<i;l++) { //枚举字符串 
    			ops=op[l]; //注意初始化 
    			if(op[l]==')') continue; //一开始就是左括号,肯定不合法 
    			for(register int r=l+1;r<=i;r++) {
    				ops+=op[r];
    				if(ops.length()%2!=0) continue; //长度为奇数,肯定不合法 
    				if(check(ops)==true) sum[i]++; //sum[i]存储到i号节点有多少个合法字符串 
    			}
    		}
    	}
    	for(register int i=1;i<=n;i++) { //根据题目要求求出答案 
    		ans=ans xor (sum[i]*i);
    	}
    	printf("%d",ans);
    	return 0;
    }
    
    1. 上面的代码的问题就在于枚举每一条路上的所有字符串太浪费时间,所以我们需要找递推式,使得计算每个点以前的合法字符串更简便,先来手推:
    我们设每个节点及以前的单个合法字符串'()'为now
    再设全部合法字符串'(())'或'()()'为sum
    
    例1:
    字符串:()()
    节点:1 2 3 4
    每个节点的now:0 1 0 2
    每个节点的sum:0 1 1 3
    
    例2:
    字符串:()(())
    节点:1 2 3 4 5 6
    now:0 1 0 0 1 2
    sum:0 1 1 1 2 4
    
    

    我们发现:一个后括号如果能匹配一个前括号,假设这个前括号的前1位同样有一个已经匹配了的后括号,那么我们可以把当前的匹配和之前的匹配序列合并,当前now,其实就等于前面那个后括号的now+1

    计算出了now,那sum就很容易得出:sum[i]=sum[i-1]+now[i]

    /* 跑过链的全部分:1-7点和11-14点 , 55pts*/
    
    #include <bits/stdc++.h>
    using namespace std;
    int n,x;
    long long ans,sum[510005],now[510005]; //注意开long long 
    char op[510005];
    stack<int> fh; //注意一下现在栈的类型是int(因为存的下标) 
    
    int main() {
    	scanf("%d",&n);
    	scanf("%s",op+1);
    	for(register int i=2;i<=n;i++) { //依旧没有用 
    		scanf("%d",&x);
    	}
    	for(register int i=1;i<=n;i++) {
    		if(op[i]==')') {
    			if(!fh.empty()) { 
    				int t=fh.top(); //如果栈不为空则满足now的条件,找到最近的合法括号下标 
    				fh.pop();
    				now[i]=now[t-1]+1; //核心递推式1 
    			}
    		}
    		else if(op[i]=='(') fh.push(i); //存放的是下标 
    		sum[i]=sum[i-1]+now[i]; //核心递推式2 
    	}
    	for(register int i=1;i<=n;i++) {
    		ans=ans xor (sum[i]*i);
    	}
    	printf("%lld",ans);
    	return 0;
    }
    

    解决了链的所有分,我们尝试解决树的情况,因为树可以看做很多条链组成,所以我们在链的核心思路上进行进一步思考

    1. 链的父亲就是当前节点i的前一个,但是在树上父亲编号不一定就是i-1,所以我们要想办法转换求now和sum值的递推式

    解决:now是从上一个合法括号递推而来,在链中是now[t-1],在树中就是t的父亲节点now[fa[t]];sum同理,将sum[i-1]改成sum[fa[i]]即可

    1. 链直接遍历1~n即可,但是树的遍历是从父亲到儿子的递归,所以我们要知道怎么遍历

    解决:记录每个点的父亲节点,再建图

    1. 因为在树上,每个父亲可能有多个儿子,那么就会涉及到每一次dfs后栈的状态问题,所以我们还需要回溯栈的状态

    解决:如果当前括号为右括号,则回溯时需要重新压入;如果是左括号,则弹出

    /* 满分Code */
    
    #include <bits/stdc++.h>
    using namespace std;
    stack<int> fh; 
    char op[510005];
    int n,x,tot,head[510005];
    long long ans,fa[510005],sum[510005],now[510005];
    
    struct node {
    	int to,net;
    } a[510005];
    
    inline void add(int u,int v) {
    	a[++tot].to=v;
    	a[tot].net=head[u];
    	head[u]=tot;
    }
    
    inline void dfs(int x) {
    	int t=0;
    	if(op[x]==')') {
    		if(!fh.empty()) {
    			t=fh.top();
    			fh.pop();
    			now[x]=now[fa[t]]+1;
    		}
    	}
    	else if(op[x]=='(') fh.push(x);
    	sum[x]=sum[fa[x]]+now[x];
    	for(register int i=head[x];i;i=a[i].net) { //邻接表存图遍历 
    		int v=a[i].to;
    		dfs(v);
    	}
    	if(t!=0) fh.push(t); //注意不能直接判断栈是否为空来压入 
    	else if(op[x]=='(') fh.pop();
    }
    
    int main() {
    	scanf("%d",&n);
    	scanf("%s",op+1);
    	for(register int i=2;i<=n;i++) {
    		scanf("%d",&x);
    		fa[i]=x; //记录父亲节点 
    		add(x,i); //建边 
    	}
    	dfs(1);
    	for(register int i=1;i<=n;i++) {
    		ans=ans xor (sum[i]*(long long)i);
    	}
    	printf("%lld",ans);
    	return 0;
    }
    

    后序:

    啊...终于解决了这道题,来总结一下:

    1. 敲代码前一定要确保读懂题意,否则会错得不知所以然

    2. 不要上来就想满分思路(当然你强就想怎么就怎么,蒟蒻在线卑微QAQ),分析数据范围和数据特性,从简单的情况开始做

    3. 一定要多手推,不要怕麻烦,说不定推着推着思路就出来了

    本题的解决过程:暴力->链->树(正解)


    写给自己:

    去年考试简直是惨不忍睹...

    对我本身算是一种毁灭性的打击吧,学OI的兴趣和信心一下子就全部丧失完了,且赛后一直处于滑铁卢的阴霾中,真的挺难受的

    在多方开导下慢慢走出来,继续OI之路,但是内心还是一直在回避考试、害怕失败

    嗯...希望自己能坚定OI的心,不管学的好还不好、不管别人怎么说,沿着自己的路一步一步地前进,努力终不会白费的


  • 相关阅读:
    dotNet开发游戏微端
    另类Unity热更新大法:代码注入式补丁热更新
    KSFramework配置表:扩展表格解析类型
    KSFramework常见问题:Excel如何进行SVN协作、差异比较?
    KSFramework常见问题:Lua脚本热重载,内存状态数据丢失?
    KEngine:Unity3D资源的打包、加载、调试监控
    KEngine策划指南:配置表格的编辑与编译
    开发遇到的奇葩问题
    初始加载时edittext不自动获取焦点的方法
    android 监控软键盘确定 搜索 按钮并赋予点击事件
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13151284.html
Copyright © 2011-2022 走看看