沒有題面,沒有鏈接。node
2019 csp-s day1 t2c++
說真的我當時考場上真的連這個題的暴力都沒想到spa
暴力的思路很簡單,由於是鏈,能夠依次枚舉當前點i,而後枚舉區間,判斷這個區間是否合法,若是合法,ans++;code
複雜度是\(O(n^4)\),實際並跑不滿;遞歸
#include<bits/stdc++.h> #define ll long long using namespace std; inline ll read() { ll ans=0; char last=' ',ch=getchar(); while(ch>'9'||ch<'0') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } ll n; char a[500010]; char Getchar() { char ch; do { ch=getchar(); }while(ch!=')'&&ch!='('); return ch; } ll ans; ll fa[500010]; bool check(int l,int r) { int top=0; for(int i=l;i<=r;i++) { if(a[i]=='(') top++; else { if(top>0) top--; else return 0; } } return top==0?1:0; } int main() { n=read(); for(int i=1;i<=n;i++) a[i]=Getchar(); for(int i=1;i<n;i++) fa[i+1]=read(); ll k; for(int i=1;i<=n;i++) { k=0; for(int l=1;l<i;l++) { for(int r=l+1;r<=i;r++) { if(check(l,r)) k++; } } ans^=(k*i); } printf("%lld",ans); return 0; }
定義一個lst[i],記錄從1~i,以i結尾的合法的括號序列有多少個get
對於一個'('來講,顯然lst[i]=0;
因此考慮右括號:it
舉例子手玩一下:io
括號√ | ( | ) | ) | ( | ) | ) | ( | ( | ) |
---|---|---|---|---|---|---|---|---|---|
lst | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
sum | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 3 |
括號√ | ( | ) | ( | ( | ( | ) | ) | ) | ) |
---|---|---|---|---|---|---|---|---|---|
lst | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 2 | 0 |
sum | 0 | 1 | 1 | 1 | 1 | 2 | 3 | 5 | 5 |
能夠發現,當一個括號是右括號(記位置爲i)的時候,首先要找是否有匹配的左括號。table
若是沒有能夠匹配的左括號,lst=0;ast
若是有了匹配的左括號,lst的值至少爲1(顯然中間的部分都是合法的說 感性李姐,
而後再考慮匹配的左括號左邊是否能夠與當前的括號再拼成其餘的合法括號序列,這個時候咱們就要看匹配的左括號的左邊一個的lst(記這個位置爲t-1),顯然的,若是以t-1s結尾的括號能夠拼成合法的括號序列,那麼它與剛剛匹配的那段序列一樣能夠組成一個以最後的右括號結尾的合法序列。
所以,\(lst[i]=lst[t-1]+1;\)
用一個棧來維護左括號是再好不過了:遇到左括號壓入棧中,若是碰到一個匹配的右括號,退棧。
因而:
\(lst[i]=lst[t-1]+1,t=s[top]\\ans[i]=ans[i-1]+lst[i]\)
因而這就是鏈的部分分:
#include<bits/stdc++.h> #define ll long long using namespace std; inline ll read() { ll ans=0; char last=' ',ch=getchar(); while(ch>'9'||ch<'0') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } ll n; char a[500010]; char Getchar() { char ch; do { ch=getchar(); }while(ch!=')'&&ch!='('); return ch; } ll Ans; ll fa[500010]; ll lst[500010],s[500010]; ll ans[500010]; ll top; int main() { n=read(); for(int i=1;i<=n;i++) a[i]=Getchar(); for(int i=1;i<n;i++) fa[i+1]=read(); for(int i=1;i<=n;i++) { if(a[i]==')') { if(top!=0) { int t=s[top]; top--; lst[i]=lst[t-1]+1; } } else s[++top]=i; ans[i]=ans[i-1]+lst[i]; Ans^=ans[i]*i; } printf("%lld",Ans); return 0; }
考慮把鏈上的dp轉移到樹上:
和在鏈上的dp很相似,由於是在樹上,所以只須要將dp式子中的\(t-一、i-1\)替換成\(fa[t]、fa[i]\)便可;
大體以下:
\(lst[u]=lst[fa[t]]+1,t=s[top];\\ans[u]=ans[fa[u]]+lst[u]\)
而後考慮應該如何維護s:
一路向下遞歸,顯然遞歸獲得的是一條鏈,所以往下遞歸的時候,按照鏈的遞歸方法可勁加就好,主要須要注意的在遞歸到底之後的回溯上:
\(\mathbb{A}.\)若是當前節點是一個右括號,
\(\mathfrak{a}.\)在這條鏈上,這個右括號找到了一個匹配的左括號。
顯然,左括號會在與右括號匹配過程當中從棧中彈出,而當咱們要回溯時,被彈出的節點要從新壓入棧中,來與其餘子樹中的右括號匹配,
\(\mathfrak{b}.\)固然,若是這個點沒有找到能夠匹配的左括號,咱們天然不須要再進棧。
\(\mathbb{B}.\)若是當前節點是左括號,那麼須要將當前節點退棧。
關於建圖的話,由於給出的是明確的父親兒子關係,所以能夠只建單向邊。
#include<bits/stdc++.h> #define ll long long using namespace std; inline ll read() { ll ans=0; char last=' ',ch=getchar(); while(ch>'9'||ch<'0') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } char Getchar() { char ch; do { ch=getchar(); }while(ch!=')'&&ch!='('); return ch; } const int mxn=500010; ll n,Ans; char a[mxn]; ll lst[mxn],ans[mxn]; ll s[mxn],top; struct node { int to,nxt; }e[mxn<<1]; ll ecnt,head[mxn],fa[mxn]; void add(int from,int to) { ++ecnt; e[ecnt].to=to; e[ecnt].nxt=head[from]; head[from]=ecnt; } void dfs(int u) { int t=0; if(a[u]=='(') //左括號,入棧 s[++top]=u; else {//右括號 if(top!=0) {//能夠找到匹配的左括號 t=s[top]; lst[u]=lst[fa[t]]+1; top--;//退棧 } } ans[u]=ans[fa[u]]+lst[u]; Ans^=ans[u]*u; for(int i=head[u],v;i;i=e[i].nxt) { v=e[i].to; dfs(v); } if(t!=0) //A: a or b s[++top]=t; if(a[u]=='(') //B top--; } int main() { n=read(); for(int i=1;i<=n;i++) a[i]=Getchar(); for(int v=2,u;v<=n;v++) { u=read(); fa[v]=u; add(u,v); } dfs(1); printf("%lld",Ans); return 0; }
\(\color{Gold}{\mathfrak{To\ be\ a \ better \ person,with \ YkY}}\)