【解题报告】洛谷P5658 括号树

【解题报告】洛谷P5658 括号树

题目链接

https://www.luogu.com.cn/problem/P5658

思路

看到这道题目,我们有必要了解一下括号序列

然后不会打正解?

好,打部分分

  • \(f_i=i-1\) 的部分

    • \(n \le 200\)

      这一部分的分数我们可以直接打暴力,然后三个循环 \(O(n^3)\) 再加上一个 \(O(n)\) 的判断被嵌套在这三个循环里面,总共是 \(O(n^4)\) ,可以过掉这一部分的数据

      期望得分 20pts

      #include <iostream>
      #include <cstdio>
      #include <algorithm>
      #include <cstring>
      #include <string>
      using namespace std;
      const int maxn=500005;
      int n,fa[maxn],ans;
      bool flag=true;
      char s[maxn];
      struct edge{
      	int e,next;
      }ed[maxn<<1];
      int en,first[maxn];
      void add_edge(int s,int e)
      {
      	en++;
      	ed[en].next=first[s];
      	first[s]=en;
      	ed[en].e=e;
      }
      bool check(int l,int r)
      {
      	int cnt=0;
      	for(int i=l;i<=r;i++)
      	{
      		if(s[i]=='(') cnt++;
      		else if(s[i]==')') cnt--;
      		if(cnt<0) return false;
      	}
      	if(cnt==0) return true;
      	return false;
      }
      int main()
      {
      	cin>>n;
      	scanf("%s",s+1);
      	for(int i=1;i<=n-1;i++)
      	{
      		int x;
      		cin>>x;
      		fa[i+1]=x;
      		if(fa[i+1]!=i) flag=false;
      		add_edge(i+1,x);
      		add_edge(x,i+1);
      	}
      	if(flag)
      	{
      		for(int i=1;i<=n;i++)
      		{
      			int cnt=0;
      			for(int l=1;l<=i;l++)
      			{
      				for(int r=l+1;r<=i;r++)
      				{
      					if(check(l,r))
      						cnt++;
      				}
      			}
      			ans=(ans^(cnt*i));
      		}
      		cout<<ans<<'\n';
      		return 0;
      	}
      	return 0;
      }
      
    • \(n \le 10^5\) 的部分

      我们可以发现,实际上我们的瓶颈在于我们有一个 \(n^3\) 的美剧左端点,枚举右端点和一次判断,我们要完成 \(10^5\) 的数据,至少要达到 \(O(nlogn)\) 的时间复杂度,所以我们要继续优化

      根据大佬的提示,我们可以知道下面一段话

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

      然后我们就可以按照这个写出代码,这个结论要用手模一下

      期望得分 55pts

      #include <iostream>
      #include <cstdio>
      #include <algorithm>
      #include <cstring>
      #include <string>
      #define int long long
      using namespace std;
      const int maxn=500005;
      int n,fa[maxn],ans;
      int st[maxn],don[maxn],sum[maxn],size;
      bool flag=true;
      char s[maxn];
      struct edge{
      	int e,next;
      }ed[maxn<<1];
      int en,first[maxn];
      void add_edge(int s,int e)
      {
      	en++;
      	ed[en].next=first[s];
      	first[s]=en;
      	ed[en].e=e;
      }
      bool check(int l,int r)
      {
      	int cnt=0;
      	for(int i=l;i<=r;i++)
      	{
      		if(s[i]=='(') cnt++;
      		else if(s[i]==')') cnt--;
      		if(cnt<0) return false;
      	}
      	if(cnt==0) return true;
      	return false;
      }
      signed main()
      {
      	cin>>n;
      	scanf("%s",s+1);
      	for(int i=1;i<=n-1;i++)
      	{
      		int x;
      		cin>>x;
      		fa[i+1]=x;
      		if(fa[i+1]!=i) flag=false;
      		add_edge(i+1,x);
      		add_edge(x,i+1);
      	}
      	if(flag)
      	{
      		for(int i=1;i<=n;i++)
      		{
      			if(s[i]==')')
      			{
      				if(size==0) continue;
      				int t=st[size];
      				don[i]=don[t-1]+1;
      				size--;
      			}
      			else if(s[i]=='(')
      			st[++size]=i;
      		}
      		for(int i=1;i<=n;i++)
      			sum[i]=sum[i-1]+don[i];
      		for(int i=1;i<=n;i++)
      		ans=(ans^(i*sum[i]));
      		cout<<ans<<'\n';
      		return 0;
      	}
      	return 0;
      }
      
  • 无特殊性质

    这种情况,我们要做的是把序列上的问题转化到树上,每个结点到根的简单路径有且仅有一条,所以如果单单是对于这一条链的话,策略跟上面一样,但是我们的前一个结点就变了,上一个左括号的,也就是上面的代码中的 \(t\) 我们要改成 \(fa[t]\) 这样,然后计算每个结点对应的合法序列的个数也要改变

    那我们压进栈里面的数字怎么办啊

    我们设置了一个中间值,记录上一个左括号的位置,如果这个中间值为 \(0\) 的话,说明这是一个左括号,那么在栈里面加入它,如果不是 \(0\) 的话,判断一下栈的大小是否为 \(0\) ,如果不是 \(0\) 的话,我们就可以发现一个事情,在之前的递归中,我们一定压入了一个数,但是这个数会影响后面的答案,所以我们将这个值弹出,相当于回溯一下子

    期望得分100pts

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <string>
    #define int long long
    using namespace std;
    const int maxn=500005;
    int n,fa[maxn],ans;
    int st[maxn],don[maxn],sum[maxn],size;
    bool flag=true;
    char s[maxn];
    struct edge{
    	int e,next;
    }ed[maxn<<1];
    int en,first[maxn];
    void add_edge(int s,int e)
    {
    	en++;
    	ed[en].next=first[s];
    	first[s]=en;
    	ed[en].e=e;
    }
    
    
    void dfs(int x)
    {
    	int tmp=0;
    	if(s[x]==')')
    	{
    		if(size!=0)
    		{
    			tmp=st[size];
    			don[x]=don[fa[tmp]]+1;
    			size--;
    		}
    	}
    	else if(s[x]=='(')
    	st[++size]=x;
    	sum[x]=sum[fa[x]]+don[x];
    	for(int i=first[x];i;i=ed[i].next)
    	{
    		int e=ed[i].e;
    		dfs(e);
    	}
    	if(tmp!=0)
    	st[++size]=tmp;
    	else if(size)
    	size--;
    }
    
    bool check(int l,int r)
    {
    	int cnt=0;
    	for(int i=l;i<=r;i++)
    	{
    		if(s[i]=='(') cnt++;
    		else if(s[i]==')') cnt--;
    		if(cnt<0) return false;
    	}
    	if(cnt==0) return true;
    	return false;
    }
    signed main()
    {
    	cin>>n;
    	scanf("%s",s+1);
    	for(int i=2;i<=n;i++)
    	{
    		int x;
    		cin>>x;
    		add_edge(x,i);
    		fa[i]=x;
    	}
    	dfs(1);
    	for(int i=1;i<=n;i++)
    	ans=(ans^(i*sum[i]));
    	cout<<ans<<'\n';
    	return 0;
    }
    
posted @ 2021-10-11 18:57  wweiyi  阅读(55)  评论(0编辑  收藏  举报
js脚本