Codeforces Round #556 (Div. 2) 题解(A~E)

已完结,包括div2所有题目:A,B,C,D,E 题解

同时发布在本人的洛谷博客: https://www.luogu.org/blog/996-icu/

题目链接:https://codeforces.com/contest/1150


蒟蒻准备开始打cf了,打算以后赛后补完题有时间的话就写下题解

说来惭愧,这场比赛前三题大水题,后两题比赛里最后都没几十个人a,本蒟全凭手速+运气二十分钟a了前三题然后开始挂机毫无作为,d看了会儿感觉没思路就去做e了,然鹅e却是更大的坑QAQ。写了个线段树但是还是只能搞出到根最长距离,虽然标答也是线段树但思想却完全没想到,窝太菜了。

然后最后竟然就只靠二十分钟出前三题排到了三百多名,rating从1500涨了一百二十多分一发上蓝,下次估计就得表演在线掉段了…


A.Stock Arbitraging

贪心取前者最小后者最大就行了,然鹅我比赛时候还顺手敲的sort…


B. Tiling Challenge

从第一行第一个元素开始往后遍历,遇到空位则把十字形砖放入(下左,下,下右,下下)都判空并填上,若判空时已是填上的状态则输出no,遍历完后输出yes


C. Prefix Sum Primes

依旧是贪心,除了2以外素数全是奇数,故(有2的话)先放一个2,1的数目是奇数的话就全放完再放2,是偶数的话就留一个1再全放2最后再放上1即可(主要是为了保证开始放2的时候前缀和是奇数状态)

#include
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 301000
typedef long long ll;
int n,ind1,ind2,x;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for (int i=1; i<=n; i++)
    {
        cin>>x;
        if (x==1) ind1++;
        else ind2++;
    }
    if (ind1%2==1)
    {
        if (ind2--)
            cout<<2<<" ";
        for (int i=1; i<=ind1; i++)
            cout<<1<<" ";
        for (int i=1; i<=ind2; i++)
            cout<<2<<" ";
    }
    else
    {
        if (ind2--)
            cout<<2<<" ";
        for (int i=1; i<ind1; i++)
            cout<<1<<" ";
        for (int i=1; i<=ind2; i++)
            cout<<2<<" ";
        if (ind1) cout<<1;
    }
    return 0;
}

D.Three Religions

一道卡了一万个人的dp(这脑回路得多清奇才想得出是dp啊= =)。预处理一个二维数组nechar[i][j]存放宇宙(总)字符串的第i位往后下一个j号字母的位置(若没有则设置为n+1),三维动态规划数组dp[a][b][c]代表一号宗教字符串长为a,二号宗教字符串长为b,三号宗教字符串长为c时需要用到宇宙字符串的位数,易得出转移方程(以第一维为例,其它维同理)

dp[i][j][k]=min(dp[i] [j] [k], nechar[dp[i-1] [j] [k]+1][A[i]]); //A为第一宗教字符串

当操作是减短时可以直接判断,增加时则需要修改一遍dp数组,以增加第一个宗教字符串为例,第二维和第三维都需要从头遍历,第二维则只需要改变最后一维(因为修改只在字符串尾部进行),用转移方程跑一遍即可,因此每次修改的复杂度是2502,总复杂度是2502*q,属于可以接受的范围。

判断是否可行时只需判断dp[len[1]][len[2]][len[3]]是否大于n即可(len为宗教字符串长度)

#include
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 101000
typedef long long ll;
int n,q,x;
int dp[256][256][256];
char A[256],B[256],C[256],yz[101000]={'a'},let_1,let_2;//yz第零位赋初值避免RE
int len[5],nechar[101000][30];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>q;
    cin>>(yz+1);
    for (int i=0; i<=26; i++)
        nechar[n+1][i]=n+1,nechar[n+2][i]=n+1;
    for (int i=n; i>=1; i--)
    {
        for (int j=1; j<=26; j++)
            nechar[i][j]=nechar[i+1][j];
        nechar[i][yz[i]-'a'+1]=i;
    }
    while (q--)
    {
        cin>>let_1>>x;
        if (let_1=='+') len[x]++;
        else len[x]--;
        if (let_1=='+')
        {
            cin>>let_2;
            int sta=0,stb=0,stc=0;
            if (x==1) sta=len[1],A[len[1]]=let_2;
            if (x==2) stb=len[2],B[len[2]]=let_2;
            if (x==3) stc=len[3],C[len[3]]=let_2;
            for (int i=sta; i<=len[1]; i++)
                for (int j=stb; j<=len[2]; j++)
                    for (int k=stc; k<=len[3]; k++)
                    {
                        int mindp=n+1;
                        if (i>0) mindp=min(mindp,nechar[dp[i-1][j][k]+1][A[i]-'a'+1]);
                        if (j>0) mindp=min(mindp,nechar[dp[i][j-1][k]+1][B[j]-'a'+1]);
                        if (k>0) mindp=min(mindp,nechar[dp[i][j][k-1]+1][C[k]-'a'+1]);
                        dp[i][j][k]=mindp;
                    }
        }
        if (dp[len[1]][len[2]][len[3]] <= n) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

E. Tree Generator™

一道结点里存的信息比较多的线段树。

首先观察题目,(代表向下遍历,)代表向上遍历,思考一下便可得出某个点的深度是字符串中当前位置左侧两种括号 ‘(’-’)’ 的差值。而树的直径怎么求呢?设d表示深度,则u,v间的距离则为

d[u] + d[v] - 2*d[ lca(u,v) ] (lca含义为最小公共祖先)

找到树里最长的两点距离即为树的直径。因此需要寻找一个区间内左侧最深的点,中间最浅的点,右侧最深的点,再按照上面的公式合起来就可得到这个区间内最长的两点距离。

(注:以下写的 lca(u,v) 都仅表示区间内最浅点,不需要严格的取两个点求最小公共祖先,至于为啥,跟每次更新线段树区间后各元素互相之间的关系有关,这个感觉不是很好表述,可以自己多想想)

括号可以用一个前缀和数组描述( '(‘则该位置为前一位+1,’)'则该位置为前一位-1),每次交换括号位置时只需要修改交换区间内数字的值,左括号在左侧向右交换则区间内所有前缀和-2,反之则+2。

修改区间值的话就很容易想到需要使用线段数组(其实不使用线段树组的话我感觉反而完全不知道能怎么写了…)

考虑如何得到u,v点间的距离,在线段树合并两个子区间时,最长距离需要由两个子区间内一个u,lca(u,v),v得到,不妨设u为左侧的点,而lca(u,v)则需要保证在u,v之间,若是把三个量分开存的话则肯定无法保证三个点的顺序。因此可以想到(我看别人的代码看了半天才想到QAQ),存一个d[u] - 2*d[ lca(u,v)]的元素,一个d[v] - d[ lca(u,v) ]的元素(以下二者称为差值元素),这样更新总区间内最长两点距离的时候就可以由

1、来自左区间的d[u] - 2*d[ lca(u,v) ]加上右区间的d[v]

2、来自右区间的d[v] - 2*d[ lca(u,v) ]加上左区间的d[u]

3、左区间的最长区间内的两点距离

4、右区间的最长区间内的两点距离

四个值选取最大者取之。要注意的是因为要维护最长两点距离的同时还要同时维护两个差值元素,因此更新大区间的时候需要同时用左右区间的d[u],d[ lca(u,v) ]去维护那两个差值元素 。

改变符号的时候只需要对改变区间内线段树的元素做修改即可,具体如何修改可以参考代码,代码里也写了些注释方便理解

#include
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 201000
typedef long long ll;
int n,q;
struct str
{
    int a,b,ab,ba,aba,laz; //a为区间内最深点,b为区间内最浅点,ab,ba为差值元素
}tr[maxn<<2];              //ab代表深点在左侧的差值元素,ba代表深点在右侧的差值元素
char A[maxn];              //aba代表区间内最长两点距离,laz为线段树的更改标记
void update(int id, int val)
{
    tr[id].laz+=val;
    tr[id].a+=val;
    tr[id].b+=val;
    tr[id].ab-=val;//差值元素要用减号
    tr[id].ba-=val;
}
void shift (int id)
{
    update(id<<1,tr[id].laz);
    update(id<<1^1,tr[id].laz);
    tr[id].laz=0;
}
void merg(int id)
{
    tr[id].a=max(tr[id<<1].a, tr[id<<1^1].a);
    tr[id].b=min(tr[id<<1].b, tr[id<<1^1].b);
    tr[id].ab=max({tr[id<<1].ab, tr[id<<1^1].ab, tr[id<<1].a - 2*tr[id<<1^1].b});//这里max比较多个元素需要c++14以上版本
    tr[id].ba=max({tr[id<<1].ba, tr[id<<1^1].ba, tr[id<<1^1].a - 2*tr[id<<1].b});//,编译错误可能是编译器版本问题
    tr[id].aba=max({tr[id<<1].aba, tr[id<<1^1].aba, tr[id<<1].ab+tr[id<<1^1].a, tr[id<<1].a+tr[id<<1^1].ba});
}
void add(int lt,int rt, int val, int id=1, int l=0, int r=n+1)
{
    if (rt<=l || lt>=r)
        return ;
    if (lt<=l && rt>=r)
        return void(update(id,val));
    shift(id);
    int mid=(l+r)>>1;
    add(lt, rt, val, id<<1, l, mid);
    add(lt, rt, val, id<<1^1, mid, r);
    merg(id);
}

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin>>n>>q>>(A+1);
    n=2*n-2;
    for (int i=1,s=0; i<n; i++)
    {
        s+= (A[i]=='(' ? 1: -1);
        add(i, i+1, s);//代码是参照大神的写的,这里我也还有点疑问,为什么点要存成两个点生成的区间
    }                  //可能是因为差值元素的存在?反正这里更改成单点后答案就会错...
    cout<<tr[1].aba<<endl;
    while (q--)
    {
        int l,r,v=2;
        cin>>l>>r;
        if (l>r) swap(l,r);
        if (A[l]=='(') v=-2;
        swap(A[l],A[r]);
        add(l,r,v);
        cout<<tr[1].aba<<endl;
    }
    return 0;
}

你可能感兴趣的:(Codeforces Round #556 (Div. 2) 题解(A~E))