其他分享
首页 > 其他分享> > Codeforces Round #556 (Div. 2)

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. 求树的直径其实就是求树上的所有路径的最大值,
  2. 将括号序列匹配的删去,最后剩下的序列的长度即为树上某条链的长度

但知道这一点后,我们会发现这很难维护的,因此我们尝试进行接下来的推导。

推导式子

首先,我们刚开始想到想消除匹配的括号,那肯定是想到能不能将(设为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

这个就没什么特别值得说的,就直接求区间和就好

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)的最值

维护时,分为三种情况。

  1. x,y均在左区间。

    该情况最值为tr[u<<1].mx1

  2. x在左区间,y在右区间

    该情况最值为tr[u<<1|1].lmx + tr[u<<1].sum - 2*tr[u<<1].lmi

  3. x,y均在右区间

    该情况最值为tr[u<<1|1].mx1-tr[u<<1].sum

mx2

维护的是,这个区间中,一些右端点为r的子区间的sum(y,r) - sum(x,y)的最值

维护时,依旧分为三种情况。

  1. x,y均在右区间

    该情况最值为tr[u<<1|1].mx2

  2. y在右区间,x在左区间

    该情况最值为tr[u<<1|1].sum - 2*tr[u<<1|1].lmi - tr[u<<1].rmi

  3. 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\)

这次,我们分的情况较多,四种。

  1. x,y,z全部在左区间

    tr[u<<1].mx3

  2. x,y在左区间,z在右区间

    tr[u<<1|1].lmx + tr[u<<1].mx2

  3. x在左区间,y,z在右区间

    -tr[u<<1].rmi + tr[u<<1].mx1

  4. 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