Codeforces Round #556 (Div. 2)
作者:互联网
Codeforces Round #556 (Div. 2)
D. Three Religions
分析
一开始的想法是,我们贪心的,每次操作后都暴力匹配一下每个串能不能匹配上。
匹配的贪心是,我们考虑对于一个串\(s_i\),我们在S中匹配时,一定尽可能选择靠近的字符匹配。
但是很明显,这样的贪心是错的。
我们举个简单的例子,比如
一个串为sa
,另一个为as
,我们的S为asas
,如果我们先匹配第二个串,则给第一个剩下的就是一个as
了,那就凑不出答案了,但是其实是有办法凑出来的。
则,我们换一个思路,我们考虑dp
我们定义dp[i][j][k]
为只考虑\(s_1\)前缀到i
,\(s_2\)前缀到j
,\(s_3\)前缀到k
,在S中的最早匹配的下标。
则我们的转移也很好想,当给一个\(s_i\)尾部加上一个字符时,我们直接暴力枚举转移就好啦。
我们直接看代码吧,这也很好理解。
时间复杂度为\(O(q*250^2)\)
AC_code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n,q;
int dp[260][260][260],ne[N][30],len[5];
char s[N],d[5][260];
int main()
{
scanf("%d%d%s",&n,&q,s+1);
for(int i=0;i<26;i++) ne[n][i] = ne[n+1][i] = n + 1;
for(int i=n-1;i>=0;i--){
for(int j=0;j<26;j++)
ne[i][j] = ne[i+1][j];
ne[i][s[i+1]-'a'] = i + 1;
}
while(q--)
{
char op[2];int x;
scanf("%s%d",op,&x);
if(*op=='-') len[x]--;
else
{
len[x]++;
scanf("%s",op);
d[x][len[x]] = *op;
for(int p1=x==1?len[x]:0;p1<=len[1];p1++)
for(int p2=x==2?len[x]:0;p2<=len[2];p2++)
for(int p3=x==3?len[x]:0;p3<=len[3];p3++)
{
dp[p1][p2][p3] = n + 1;
if(p1) dp[p1][p2][p3] = min(dp[p1][p2][p3],ne[dp[p1-1][p2][p3]][d[1][p1]-'a']);
if(p2) dp[p1][p2][p3] = min(dp[p1][p2][p3],ne[dp[p1][p2-1][p3]][d[2][p2]-'a']);
if(p3) dp[p1][p2][p3] = min(dp[p1][p2][p3],ne[dp[p1][p2][p3-1]][d[3][p3]-'a']);
}
}
puts(dp[len[1]][len[2]][len[3]]<=n?"YES":"NO");
}
return 0;
}
E. Tree Generator™
分析
线段树好题,我们来一点点推理。
这里我们用括号序列来表示出一棵树,如果你对欧拉序这个概念有了解的话,括号序列就是对应的欧拉序。
本题你知道了欧拉序这个概念可能更好理解,但不懂也没事。
首先,我们需要知道。
任取一段括号序列,左右括号匹配抵消后,剩下的括号数量对应了树中的一条链的长度。
这点,很好理解,因为,左右括号匹配后,我们可以理解为,我们从一个点下去后又回来了,那我们可以理解为该点的影响消除掉(如果你对欧拉序有了解就会更明白这个意思)
此时,我们要求的树的直径就是树上所有路径的最大值,我们记树上任意一条路径的长度为\(f(l,r)\),则答案为\(\underset{1\leq l \leq r \leq 2(n-1)}{max}f(l,r)\)
推导到这一步,我们需要知道两个关键点
- 求树的直径其实就是求树上的所有路径的最大值,
- 将括号序列匹配的删去,最后剩下的序列的长度即为树上某条链的长度
但知道这一点后,我们会发现这很难维护的,因此我们尝试进行接下来的推导。
推导式子
首先,我们刚开始想到想消除匹配的括号,那肯定是想到能不能将(
设为1,)
设为-1,。
但是,很容易就能发现肯定不对啊,因为会把一些不匹配的也删去。
接下来的推导就比较奇妙了。
首先,我们在将括号转换结束后,我们假设[l,r]
消完之后剩余x
个右括号,y
个左括号。我们进行求前缀和,可以得到数组\(s_i\),那么显然\(mins_i=-x\)。
我们考虑,我们想求的路径的长度实际是x+y,但如果我们直接这样求完得到的值为y-x。
所以,我们为了得到答案,我们考虑将区间[l,r]
在k
处断开,则区间前半段左右相消后,一定为x
个右括号,区间的后半段左右相消后,一定为y个左括号。
我们记sum(l,r)
为[l,r]
中所有数字和。那么有sum(l,k) = -x
,sum(k+1,r) = y
,还记得我们要求的值是什么嘛,sum(r,k+1) - sum(l,k) = y + x
。同时我们可以将这个式子转化,sum(l,r) - 2*sum(l,k)
,我们可以发现,该式子的最值在sum(l,k) = -x
处取到,因此,我们想要的答案即为整个序列的所有区间的该式子的最大值。
即,我们想要维护区间上sum(k+1,r) - sum(l,k)
的最值,即,我们要维护的是,对于整个区间中,某一段满足\(1\leq x \leq y \leq z \leq 2(n-1)\),sum(y,z) - sum(x,y)
的最大值。
维护变量
我尽量以推理的角度,将维护的几个值合理推导一下,可能理解起来会更顺利一些。但为了理解的顺利,我会先将所有需要维护的值,在最前面列出来。
只需要记住,我们维护前面七个,是为了维护最后一个,逻辑推导就会顺利一些
- sum表示sum(l,r)
- lmx表示maxsum(l,k)
- rmx表示maxsum(k,r)
- lmi表示minsum(l,k)
- rmi表示minsum(k,r)
- mx1表示maxsum(x,y) - sum(l,x),\(l\leq x\leq y \leq r\)
- mx2表示maxsum(y,r) - sum(x,y),\(l\leq x\leq y \leq r\)
- mx3表示maxsum(y,z) - sum(x,y),\(l\leq x\leq y \leq z \leq r\)
我们一个个来说
sum
这个就没什么特别值得说的,就直接求区间和就好
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum
lmx与rmx
维护从l开始的最大和,可以为0
维护以r结尾的最大和,可以为0
tr[u].lmx = max(tr[u<<1].lmx,tr[u<<1].sum + tr[u<<1|1].lmx);
tr[u].rmx = max(tr[u<<1|1].rmx,tr[u<<1|1].sum + tr[u<<1].rmx);
lmi与rmi
维护从l开始的最小和,可以为0
维护以r结尾的最小和,可以为0
tr[u].lmi = min(tr[u<<1].lmi,tr[u<<1].sum + tr[u<<1|1].lmi);
tr[u].rmi = min(tr[u<<1|1].rmi,tr[u<<1|1].sum + tr[u<<1].rmi);
mx1
维护的是,这个区间中,一些左端点为l的子区间的sum(y,x) - sum(l,x)
的最值
维护时,分为三种情况。
-
x,y均在左区间。
该情况最值为
tr[u<<1].mx1
-
x在左区间,y在右区间
该情况最值为
tr[u<<1|1].lmx + tr[u<<1].sum - 2*tr[u<<1].lmi
-
x,y均在右区间
该情况最值为
tr[u<<1|1].mx1-tr[u<<1].sum
mx2
维护的是,这个区间中,一些右端点为r的子区间的sum(y,r) - sum(x,y)
的最值
维护时,依旧分为三种情况。
-
x,y均在右区间
该情况最值为
tr[u<<1|1].mx2
-
y在右区间,x在左区间
该情况最值为
tr[u<<1|1].sum - 2*tr[u<<1|1].lmi - tr[u<<1].rmi
-
x,y都在左区间
该情况最值为
tr[u<<1|1].sum + tr[u<<1].mx2
mx3
维护的是,这个区间内,任选三个点x,y,z
,sum(y,z) - sum(x,y)的最值,\(l \leq x \leq y \leq z \leq r\)
这次,我们分的情况较多,四种。
-
x,y,z全部在左区间
tr[u<<1].mx3
-
x,y在左区间,z在右区间
tr[u<<1|1].lmx + tr[u<<1].mx2
-
x在左区间,y,z在右区间
-tr[u<<1].rmi + tr[u<<1].mx1
-
x,y,z在右区间
tr[u<<1|1].mx3
修改操作就不多说了,直接改就是了。
AC_code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
/**
* mx1 = sum(x,y) - sum(l,x)
* mx2 = sum(y,r) - sum(x,y)
* mx3 = sum(y,z) - sum(x,y)
*/
struct Node
{
int l,r,sum;
int lmx,rmx;
int lmi,rmi ;
int mx1,mx2,mx3;
}tr[N<<2];
char s[N];
int n,q;
void pushup(int u)
{
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
tr[u].lmx = max(tr[u<<1].lmx,tr[u<<1].sum+tr[u<<1|1].lmx);
tr[u].rmx = max(tr[u<<1|1].rmx,tr[u<<1|1].sum+tr[u<<1].rmx);
tr[u].lmi = min(tr[u<<1].lmi,tr[u<<1].sum+tr[u<<1|1].lmi);
tr[u].rmi = min(tr[u<<1|1].rmi,tr[u<<1|1].sum+tr[u<<1].rmi);
tr[u].mx1=max(tr[u<<1].mx1,max(-tr[u<<1].sum+tr[u<<1|1].mx1,tr[u<<1|1].lmx + tr[u<<1].sum - 2*tr[u<<1].lmi));
tr[u].mx2=max(tr[u<<1|1].mx2,max(tr[u<<1|1].sum+tr[u<<1].mx2,-tr[u<<1].rmi+tr[u<<1|1].sum-2*tr[u<<1|1].lmi));
tr[u].mx3=max(max(tr[u<<1].mx3,tr[u<<1|1].mx3),max(tr[u<<1].mx2+tr[u<<1|1].lmx,-tr[u<<1].rmi+tr[u<<1|1].mx1));
}
void build(int u,int l,int r)
{
tr[u] = {l,r};
if(l==r)
{
if(s[l]=='(') tr[u].sum = 1,tr[u].lmx = tr[u].rmx = 1,tr[u].lmi = tr[u].rmi = 0;
else tr[u].sum = -1,tr[u].lmx = tr[u].rmx = 0,tr[u].lmi = tr[u].rmi = -1;
tr[u].mx1 = tr[u].mx2 = tr[u].mx3 = 1;
return ;
}
int mid = l + r >> 1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
void modify(int u,int x,int k)
{
if(tr[u].l==tr[u].r)
{
tr[u].lmx = tr[u].rmx = max(k,0);
tr[u].lmi = tr[u].rmi = min(k,0);
tr[u].sum = k;
tr[u].mx1 = 1,tr[u].mx2 = 1,tr[u].mx3 = 1;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x<=mid) modify(u<<1,x,k);
else modify(u<<1|1,x,k);
pushup(u);
}
int main()
{
scanf("%d%d",&n,&q);
scanf("%s",s+1);
build(1,1,2*(n-1));
printf("%d\n",tr[1].mx3);
while(q--)
{
int x,y;scanf("%d%d",&x,&y);
if(s[x]!=s[y])
{
swap(s[x],s[y]);
modify(1,x,(s[x]=='(')?1:-1);
modify(1,y,(s[y]=='(')?1:-1);
}
printf("%d\n",tr[1].mx3);
}
return 0;
}
标签:sum,556,tr,Codeforces,leq,区间,Div,我们,最值 来源: https://www.cnblogs.com/aitejiu/p/16539397.html