没有题面,没有连接。
2019 csp-s day1 t2
1.期望20pts左右的完全暴力:
$mathbb
说真的我当时考场上真的连这个题的暴力都没想到
暴力的思路很简单,因为是链,可以依次枚举当前点i,然后枚举区间,判断这个区间是否合法,如果合法,ans++;
复杂度是$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;
}
2.链上的dp(55pts:
$mathbb
定义一个lst[i],记录从1~i,以i结尾的合法的括号序列有多少个
对于一个'('来说,显然lst[i]=0; 所以考虑右括号:
举例子手玩一下:
括号√ | ( | ) | ) | ( | ) | ) | ( | ( | ) |
---|---|---|---|---|---|---|---|---|---|
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)的时候,首先要找是否有匹配的左括号。
如果没有可以匹配的左括号,lst=0;
如果有了匹配的左括号,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;
}
3.关于正解
$mathbb
考虑把链上的dp转移到树上: 和在链上的dp很类似,因为是在树上,因此只需要将dp式子中的$t-1、i-1$替换成$fa[t]、fa[i]$即可;
大致如下:
(lst[u]=lst[fa[t]]+1,t=s[top];\ans[u]=ans[fa[u]]+lst[u])
然后考虑应该如何维护s:
一路向下递归,显然递归得到的是一条链,因此往下递归的时候,按照链的递归方法可劲加就好,主要需要注意的在递归到底以后的回溯上:
$mathbb.$如果当前节点是一个右括号,
$mathfrak.$在这条链上,这个右括号找到了一个匹配的左括号。
显然,左括号会在与右括号匹配过程中从栈中弹出,而当我们要回溯时,被弹出的节点要重新压入栈中,来与其他子树中的右括号匹配,
$mathfrak.$当然,如果这个点没有找到可以匹配的左括号,我们自然不需要再进栈。
$mathbb.$如果当前节点是左括号,那么需要将当前节点退栈。
关于建图的话,因为给出的是明确的父亲儿子关系,因此可以只建单向边。
#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;
}
$mathfrak
(color{Gold}{mathfrak{To be a better person,with YkY}})