[AtCoder]ARC063 &CF501 Clone Exercising 题解

在这次比赛中,由于我比较菜,考试完后查了题解才把题做出来,故在此做一个汇总~

T1:木と整数 / Integers on a Tree(AtCoder - 2148)

题意

给你一棵树,有n个点,现在有一些点被填上了点权,其余的点都是空的。现在你需要向其中没有被填入点权的点填入点权(每一个点都要填),使得相邻的两个点的点权之差的绝对值为1.
若存在这样的方案,输出”Yes”,并输出方案;反之输出”No”

输入

第一行为一个整数N,表示有N个点(1≦N≦10^5)
接下来N-1行,每行两个整数Ai,Bi,表示Ai和Bi之间有一条边
接下来的一行为一个整数K,表示有K个点已经填上了权值
接下来的K行,每行两个整数Vi,Pi,表示第Vi个点被填上了Pi这个权值(0≦Pi≦105)

输出

若存在这样的方案,输出”Yes”,并输出方案;反之输出”No”

思路

我们可以先考虑非法(不存在这样的方案)的情况:
1:从有权值的地方出发,虽然点的具体权值不清楚,但是可以确定它们的奇偶性。若遇到有值的点,则判断与当前我们根据之前权值所推断出来的奇偶性是否相同。若不同,则必然无解。
2:从任意一个点出发(为了写起来方便,可以直接从之前的那个根出发),从儿子节点把当前的点的权值范围处理出来,再看是否为一个空的区间,若是,则无解。注意,此时不需要把当前点的信息下放,因为在子树更新完了之后可以与当前值比较,相当于利用了当前节点的信息。
这样子处理完之后,我们可以保证当前的情况一定是有解的,就可以随意构造了:
从根出发(最好是有值的点),并根据到达的点的权值的范围赋一个值,然后根据这个值继续递归赋值即可。

代码

//一共三个DFS
//第一个:处理奇偶性  第二个:处理点权的范围  第三个:递归赋值
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100000
const int INF=1e9;
using namespace std;
vector<int> G[MAXN+5];
int d[MAXN+5],val[MAXN+5];
int L[MAXN+5],R[MAXN+5];
bool vis[MAXN+5];//记录当前这个点是否已经赋过权值了
void Init()
{
    memset(vis,0,sizeof(vis));
    for(int i=0;i<=MAXN+3;i++)
        L[i]=-INF,R[i]=INF;
}
bool DFS(int u,int fa,bool typ)
{
    if(vis[u]==true&&typ!=val[u]%2)//如果当前有值才进行判断
        return false;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==fa)
            continue;
        if(DFS(v,u,!typ)==false)
            return false;
    }
    return true;
}
bool DFS2(int u,int fa)
{
//如果当前这个点已经赋过权值了,那么它的点权范围自然是[val[u],val[u]]
    if(vis[u]==true)
        L[u]=R[u]=val[u];
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==fa)
            continue;
        if(DFS2(v,u)==false)
            return false;
        L[u]=max(L[v]-1,L[u]);
        R[u]=min(R[v]+1,R[u]);
    }
    if(L[u]>R[u])
        return false;
    return true;
}
//注意此时不需要判断是否还存在解,因为之前已经充分利用了信息
void DFS3(int u,int fa,int vn)
{
    val[u]=vn;//直接赋值
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==fa)
            continue;
        if(L[v]<=vn-1&&vn-1<=R[v])//判断是否在范围之内
            DFS3(v,u,vn-1);
        else 
            DFS3(v,u,vn+1);
    }
}
int main()
{
    Init();
    int n,u,v;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
        d[u]++,d[v]++;
    }
    int maxd=0,rt,k;
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        scanf("%d %d",&u,&v);
        val[u]=v;
        vis[u]=true;
        if(d[u]>maxd)
            maxd=d[u],rt=u;
    }
    if(DFS(rt,-1,val[rt]%2)==false)//先判断奇偶性
        printf("No\n");
    else if(DFS2(rt,-1)==false)//再算范围
        printf("No\n");
    else
    {
        DFS3(rt,-1,val[rt]);//最后赋值
        printf("Yes\n");
        for(int i=1;i<=n;i++)
            printf("%d\n",val[i]);
    }
    return 0;
}

PS:这道题算是这场比赛最简单的一道题了,然而所有人都去肝最后一道题,完美错过最简单的题…

T2:すぬけ君の塗り絵 2 / Snuke’s Coloring 2(AtCoder - 2149)

题意

给你一张w*h的图(一个矩阵,左下为坐标原点),初始时全为白色,再给你N个点,以及它们的坐标(xi,yi)。(xi不两两相等,yi也不两两相等)
现在对于每一个点,你需要从四个操作中选择一个来执行:
1.将x小于xi的部分全部涂成黑色;2.将x大于xi的部分全部涂成黑色;
3.将y小于yi的部分全部涂成黑色;3.将y大于yi的部分全部涂成黑色;
现在需要你来进行选择,使得最后形成的图形中白色部分的周长最大。

输入

第一行三个整数W,H,N(1≦W,H≦10^8,1≦N≦3×10^5)
接下来N行,每行有两个整数xi,yi,表示点的坐标。

输出

输出进行完所有操作后,最大的白色部分周长。

思路

参考博客:https://blog.csdn.net/blackjack_/article/details/80446302
首先,我们可以把这道题转化成一个问题:求一个矩形,使得它不包含任何一个给出的点,求它的最大周长。因为每一次的操作只会把平面一分为二,所以易证。
然后,我们可以从中推出一个结论:最终形成的矩形必然经过x=w/2或y=h/2。(因为保证了没有两个点会在同一行或同一列,最开始就是因为没看见这个条件,想半天都想不通),因此周长至少为2*max(w,h)+2。
然后我们就可以来考虑算法了。首先,看到这道的第一眼,应该和扫描线有关。先考虑必然经过y=h/2的情况:我们可以枚举一个左边界,然后从上边界开始向右枚举一个右边界,再在过程中维护一个在y=h/2上方的点中的纵坐标的最小值,以及一个在y=h/2下方的点中的纵坐标的最大值,就可以方便的算出当前的左右边界对应的周长了。但是这样的复杂度是O(n^2)的,肯定过不去的。
接着我们来考虑优化:
这次我们先不考虑左边界,向右枚举右边界。在过程中维护一个单调栈(上下各维护一个)。对于上方的点来说,因为考虑到右边界逐渐往右移的过程中,只有当纵坐标比较小时,才会造成影响,所以我们需要维护一个单调递减的栈。下方的点反过来即可。
然后考虑左边界的选取问题。可以将答案(即周长)维护在线段树中,最后直接从全局访问最优解即可。具体细节请看代码。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 300000
using namespace std;
int w,h,n;
struct point
{
    int x,y;
    point(){};
    point(int _x,int _y):x(_x),y(_y){};
}p[MAXN+5];
bool operator < (const point &a,const point &b)
{
    return a.x<b.x;
}
struct node
{
    int l,r;
    int w,tag;
}tree[4*MAXN];
void PushUp(int i)
{
    if(tree[i].l==tree[i].r)
        return;
    tree[i].w=max(tree[i*2].w,tree[i*2+1].w);
}
void PushDown(int i)
{
    if(tree[i].l==tree[i].r)
        return;
    if(tree[i].tag)
    {
        tree[i*2].w+=tree[i].tag,tree[i*2].tag+=tree[i].tag;
        tree[i*2+1].w+=tree[i].tag,tree[i*2+1].tag+=tree[i].tag;
        tree[i].tag=0;
    }
}
void Build(int i,int l,int r)
{
    tree[i].l=l;
    tree[i].r=r;
    tree[i].w=0;
    tree[i].tag=0;
    if(l==r)
        return;
    int mid=(l+r)/2;
    Build(i*2,l,mid);
    Build(i*2+1,mid+1,r);
}
void Insert(int i,int l,int r,int val)
{
    if(r<tree[i].l||tree[i].r<l)
        return;
    if(l<=tree[i].l&&tree[i].r<=r)
    {
        tree[i].w+=val;
        tree[i].tag+=val;
        return;
    }
    PushDown(i);
    Insert(i*2,l,r,val);
    Insert(i*2+1,l,r,val);
    PushUp(i);
}
int ans=0;
int top[2],st[2][MAXN+5];
void solve()
{
    int i;
    sort(p+2,p+1+n);
    top[0]=top[1]=0;
    st[0][0]=st[1][0]=1;
    Build(1,1,n);
    Insert(1,1,n,h<<1);//先在为一个点都插入两个长为h的长
    for(i=2;i<=n;i++)
    {
        Insert(1,1,i-1,(p[i].x-p[i-1].x)<<1);
        //将之前的每一个点都补上到当前这个右边界的距离
        ans=max(ans,tree[1].w);
        if(p[i].y>=(h>>1))
        {
            Insert(1,st[0][top[0]],i-1,(p[i].y-h)<<1);
            //矩形的上边界到当前的纵坐标的距离减去(因为是周长,所以要减两个)
            while(top[0]&&p[st[0][top[0]]].y>=p[i].y)
            {
                Insert(1,st[0][top[0]-1],st[0][top[0]]-1,(p[i].y-p[st[0][top[0]]].y)<<1);
                //每次要删去一个点时都将之前算的多出来的距离删去
                top[0]--;
            }
            st[0][++top[0]]=i;
        }
        else//与上面相反
        {
            Insert(1,st[1][top[1]],i-1,-p[i].y<<1);
            while(top[1]&&p[st[1][top[1]]].y<=p[i].y)
            {
                Insert(1,st[1][top[1]-1],st[1][top[1]]-1,(p[st[1][top[1]]].y-p[i].y)<<1);
                top[1]--;
            }
            st[1][++top[1]]=i;
        }
    }
    memset(tree,0,sizeof(tree));//结构体居然能够直接清零?!
}
int main()
{
    scanf("%d %d %d",&w,&h,&n);
    n++;//将1号节点变为(0,0)
    int i;
    for(i=2;i<=n;i++)
        scanf("%d %d",&p[i].x,&p[i].y);
    p[++n]=point(w,h);//将n+1号节点变为(w,h)
    solve();
    swap(w,h);//旋转坐标系,相当于变为了必然经过x=w/2的情况
    for(int i=1;i<=n;i++)
        swap(p[i].x,p[i].y);
    solve();
    printf("%d\n",ans);
    return 0;
}

T3:Bracket Substring(CodeForces - 1015F)

题意

给你一个括号序列(任意的),要求你构造一个合法的括号序列,使得这个括号序列在大的括号序列之中(为子串),问有多少种方案。

输入

一个整数n,表示你需要构造的是一个2*n的合法序列。(1≤n≤100)
接下来一行为一个串,为任意的括号序列。(1≤|s|≤200)

输出

输出方案数。

思路

参考博客:https://blog.csdn.net/qq_34454069/article/details/81364000
首先应该是一道DP题。按照常规的思路来列DP式的话,会重复计数,因此我们要改变方法。这里我们采用KMP算法,求出给出的括号序列的fail序列,然后我们定义qr[i][add]为在匹配了i个原序列中的括号后,放入一个新的括号后(add=0为”(“,add=1为”)”),向前跳转到的匹配位置(就是”fail”指针)。这个可以根据求出来的fail数组处理掉。
然后我们定义dp1[i][j]为已经插入了i个字符,并且前缀和为j(定义”(“为1,”)”为-1),的随机放置的方案数。因为下标只能是正数,所以一定是合法的(转移也一定是合法的)。
d p 1 [ i ] [ j ] = d p 1 [ i 1 ] [ j 1 ] + d p 1 [ i 1 ] [ j + 1 ]
再定义dp2[i][j][k]为已经插入了i个字符,并且前缀和为j,再原序列中已经匹配了k个字符的方案数。
当k==|S|-1时就可以统计答案了。也就是ans+=dp2[i][j][k]*dp1[2*n-i-1][j+(str[len-1]==’(‘?1:-1])(因为是强行把最后一个字符插进来的,所以要专门计算最后一个字符的值(-1 or 1))。
d p 2 [ i + 1 ] [ j + 1 ] [ q r [ k ] [ 0 ] ] + = d p 2 [ i ] [ j ] [ k ]
d p 2 [ i + 1 ] [ j 1 ] [ q r [ k ] [ 1 ] ] + = d p 2 [ i ] [ j ] [ k ]
然后就可以做了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 200
#define MO 1000000007
using namespace std;
char str[MAXN+5];
int len,a[MAXN+5],fail[MAXN+5];
int qr[MAXN+5][2];
int dp1[MAXN+5][MAXN+5],dp2[MAXN+5][MAXN+5][MAXN+5];
void prepare()
{
    memset(fail,0,sizeof(fail));
    fail[0]=-1;
    int i=1,j=0;
    while(i<len)
    {
        if(j==-1||str[i]==str[j])//求fail指针
            fail[++i]=++j;
        else
            j=fail[j];
    }
    for(i=0;i<len;i++)//根据fail指针求qr数组
    {
        for(int add=0;add<=1;add++)
        {
            int x=i;
            while(true)//实际上就是一个KMP的过程
            {
                if(add==a[x+1])
                {
                    qr[i][add]=x+1;
                    break;
                }
                if(x==0)
                    break;
                x=fail[x];
            }
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",str);
    len=strlen(str);
    for(int i=0;i<len;i++)
    {
        if(str[i]=='(')         a[i+1]=0;
        else if(str[i]==')')    a[i+1]=1;
    }
    prepare();
    dp1[0][0]=1;//dp1和dp2的初始状态
    dp2[0][0][0]=1;
    for(int i=1;i<=2*n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(j>0)//保证转移合法
                dp1[i][j]=(1LL*dp1[i][j]+1LL*dp1[i-1][j-1])%MO;
            dp1[i][j]=(1LL*dp1[i][j]+1LL*dp1[i-1][j+1])%MO;
        }
    }
    int last;
    if(a[len]==0)
        last=1;
    else
        last=-1;
    int ans=0;
    for(int i=0;i<=2*n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            for(int k=0;k<len;k++)
            {
                if(j+last>=0&&k==len-1&&2*n-i>=1)//到达最后一个位置时统计答案
                    ans=(1LL*ans+1LL*dp2[i][j][k]*dp1[2*n-i-1][j+last]%MO)%MO;
                dp2[i+1][j+1][qr[k][0]]=(1LL*dp2[i+1][j+1][qr[k][0]]+1LL*dp2[i][j][k])%MO;
                if(j>=1)
                    dp2[i+1][j-1][qr[k][1]]=(1LL*dp2[i+1][j-1][qr[k][1]]+1LL*dp2[i][j][k])%MO;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

如有误,请参看参考博客

猜你喜欢

转载自blog.csdn.net/G20202502/article/details/81369130