2024暑假集训测试8

前言

image

爆零了?!?T4 莫名 CE 了,T2 因为某些人打乱搞做法使出题人改数据和时限,\(O(npk)\) 做法死掉了,主要还是数组开大了还忘了算,直接爆零了。

T1 White and Black

显然不存在无解,从根开始扫,遇到黑色就翻转,前后顺序不影响结果,该方案为正确且唯一方案。

继续观察发现若一个点与其父亲颜色不同,则产生一次贡献。

答案为 \(m+\sum\limits_{i=1}^{m}size_i-sum_i\)\(sum_i\) 指其子节点中颜色同样为黑色的个数。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,fa[N],deg[N],b[N],ans;
vector<int>e[N];
signed main()
{
    read(n),read(m);
    for(int i=2;i<=n;i++) read(fa[i]),deg[fa[i]]++;
    for(int i=1,x;i<=m;i++)
    {
        read(x); ans=x;
        for(int j=1;j<=x;j++) read(b[j]),deg[fa[b[j]]]-=2;
        for(int j=1;j<=x;j++) ans+=deg[b[j]];
        for(int j=1;j<=x;j++) deg[fa[b[j]]]+=2;
        write(ans),puts("");
    }
}

T2 White and White

  • 部分分 \(10pts\)\(O(n^2k)~DP\)

  • 部分分 \(50pts\)\(O(nkp)\),考虑对于每个前缀和同余相同的本质是相同的,对于每个模数只保存最优策略即可。本来能过的做法

  • 部分分 \(50pts\)\(O(nk\log(p))\),树状数组做法,本来是能过的,但赛时出题人发现某人该做法常数巨大十分不爽,改时限就过不去了;又因为某人直接输出 \(sum\bmod p\) 还贼不好卡,又把捆绑加上了。

  • 正解:

    先写出 \(O(n^2k)\) 的转移方程:

    \[f_{i,k}=\min\limits_{j=k-1}^{i-1}\{f_{j,k-1}+(sum_i-sum_j)\bmod p\} \]

    发现对于每个 \(f_{i,*}\)\(\bmod p\) 的值恒等于 \(sum_i \bmod p\),那么对于两个转移点 \(x,y\),有:

    \[f_{x,k-1}+(sum_i-sum_x)\bmod p\equiv f_{y,k-1}+(sum_i-sum_y)\bmod p\pmod p \]

    \(f_{x,k-1}<f_{y,k-1}\),因为 \((sum_i-sum_j)\bmod p<p\),所以一定有 \(f_{x,k-1}+(sum_i-sum_x)\bmod p\le f_{y,k-1}+(sum_i-sum_y)\bmod p\)

    所以直接记录最优策略即可,复杂度 \(O(nk)\)

    点击查看代码
    #include<bits/stdc++.h>
    #define int long long 
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=5e5+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,m,p,f[N][2],sum[N],lastx[N],lasty[N];
    queue<pair<int,int>>q;
    signed main()
    {
        read(n),read(m),read(p);
        for(int i=1,a;i<=n;i++)
            read(a),sum[i]=sum[i-1]+a;
        memset(f,0x3f,sizeof(f));
        f[0][0]=0;
        for(int j=1;j<=m;j++)
        {
            int minn=f[j-1][(j-1)&1],ans=j-1;
            for(int i=j;i<=n;i++)
            {
                f[i][j&1]=minn+(sum[i]-sum[ans])%p;
                if(f[i][(j-1)&1]<minn) 
                {
                    minn=f[i][(j-1)&1];
                    ans=i;
                }
            }
        }
        write(f[n][m&1]);
    }
    

T3 Black and Black

直接填入 \(1\sim n\),若 \(sum=0\),直接输出即可。

\(sum<0\),设 \(v=0-sum\) 因为现在是满足单调递增的,要维持这个转台只能从前缀或后缀修改,那么对于一个 \(a_i\) 的前缀和与后缀和,若其存在负值前缀和,定存在前缀和为 \(-1\)\(i\),另 \(1\sim i\) 全部 \(-v\)即可;同理若存在正值后缀和,定存在后缀和为 \(1\)\(i\),另 \(i\sim n\) 全部 \(+v\) 即可。

反之同理。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,a[N],sum,ans[N],pre[N],suf[N];
signed main()
{
    read(n);
    for(int i=1;i<=n;i++)
        read(a[i]),
        sum+=a[i]*i,
        pre[i]=pre[i-1]+a[i],
        ans[i]=i;
    for(int i=n;i>=1;i--)
        suf[i]=suf[i+1]+a[i];
    if(sum==0)
    {
        puts("Yes");
        for(int i=1;i<=n;i++) write(i),putchar(' ');
    }
    if(sum<0)
    {
        int v=0-sum;
        bool flag=0;
        for(int i=1;i<=n;i++)
            if(pre[i]==-1)
            {
                for(int j=1;j<=i;j++) ans[j]-=v;
                flag=1;
                break;
            }
        if(!flag)
            for(int i=n;i>=1;i--)
                if(suf[i]==1)
                {
                    for(int j=n;j>=i;j--) ans[j]+=v;
                    flag=1;
                    break;
                }
        if(!flag) puts("No");
        else 
        {
            puts("Yes");
            for(int i=1;i<=n;i++) write(ans[i]),putchar(' ');
        }
    }
    if(sum>0)
    {
        int v=sum-0;
        bool flag=0;
        for(int i=1;i<=n;i++)
            if(pre[i]==1)
            {
                for(int j=1;j<=i;j++) ans[j]-=v;
                flag=1;
                break;
            }
        if(!flag)
            for(int i=n;i>=1;i--)
                if(suf[i]==-1)
                {
                    for(int j=n;j>=i;j--) ans[j]+=v;
                    flag=1;
                    break;
                }
        if(!flag) puts("No");
        else 
        {
            puts("Yes");
            for(int i=1;i<=n;i++) write(ans[i]),putchar(' ');
        }
    }
}

T4 Black and White

  • 部分分 \(60pts\)\(O(nm)\) 暴力。

  • 正解:

    这道题方法特别多,我写的是括号序列的做法。

    括号序列指遍历一棵树的过程中进入一个节点前加一个 (,将该节点子树遍历完后加一个 ),从而与原 \(dfs\) 序得到一个括号序列。

    那么此时对于两点间的距离,截取两点之间的区间,将能够匹配上的左右括号删去,剩下的括号的个数就是两点间距离,很好理解,局限性是只能处理边权为 \(1\) 的情况。

    考虑对于动态修改的黑色点集维护树的直径,开一颗线段树,每个节点即维护对应区间 \([l,r]\) 内的直径,考虑如何维护。

    定义 \(suml,sumr\) 表示左右括号个数,每次合并左区间左括号与右区间右括号匹配,故有:

    if(t[ls].suml>t[rs].sumr)
    {
        f.suml=t[ls].suml-t[rs].sumr+t[rs].suml;
        f.sumr=t[ls].sumr;
    }
    else 
    {
        f.suml=t[rs].suml;
        f.sumr=t[ls].sumr+t[rs].sumr-t[ls].suml;
    }
    

    类似于区间子段和的操作,找到直径最大的一个子区间,故此需要维护前后缀,假设找到了那个子区间,有 \(f.dis=ls.sumr+|ls.suml-rs.sumr|+rs.suml\),故有 \(f.dis=\max((ls.suml+ls.sumr)+(rs.suml-rs.sumr),(ls.sumr-ls.suml)+(rs.suml+rs.sumr))\),不妨另 \(pre1,pre2\) 分别表示前缀 \(\max(suml+sumr),\max(suml-sumr)\)\(suf1,suf2\) 分别表示后缀的 \(\max(suml+sumr),\max(sumr-suml)\),从而有转移:

    f.dis=max({t[ls].dis,t[rs].dis,t[ls].suf1+t[rs].pre2,t[ls].suf2+t[rs].pre1});
    f.pre1=max({t[ls].pre1,t[rs].pre1+t[ls].sumr-t[ls].suml,t[rs].pre2+t[ls].suml+t[ls].sumr});
    f.pre2=max({t[ls].pre2,t[rs].pre2+t[ls].suml-t[ls].sumr});
    f.suf1=max({t[rs].suf1,t[ls].suf1+t[rs].suml-t[rs].sumr,t[ls].suf2+t[rs].suml+t[rs].sumr});
    f.suf2=max({t[rs].suf2,t[ls].suf2+t[rs].sumr-t[rs].suml});
    

    对于白点是没有意义的,另起 \(dis,pre,suf\) 均为 \(-inf\) 即可。

    修改直接单点修改即可。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    #define f t[p]
    #define ls (p<<1)
    #define rs (p<<1|1)
    using namespace std;
    const int N=5e5+10,inf=1e9;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    template<typename Tp> inline void wt(Tp x)
    {if(x>9)wt(x/10);putchar((x%10)+'0');}
    template<typename Tp> inline void write(Tp x)
    {if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,m,tot,cnt,pos[N],dfn[N],c[N];
    vector<int>e[N];
    struct aa {int l,r,dis,suml,sumr,pre1,pre2,suf1,suf2;}t[N<<2];
    void dfs(int x,int fa)
    {
        pos[++tot]=-1; pos[++tot]=x; dfn[x]=tot;
        for(int y:e[x]) if(y!=fa) dfs(y,x);
        pos[++tot]=-2;
    }
    void push(int p,int x)
    {
        f.suml=f.sumr=0;
        f.dis=f.pre1=f.pre2=f.suf1=f.suf2=-inf;
        if(pos[x]==-1) f.suml=1;
        else if(pos[x]==-2) f.sumr=1;
        else if(c[pos[x]]==0) f.dis=f.pre1=f.pre2=f.suf1=f.suf2=0;
    }
    void pushup(int p)
    {
        if(t[ls].suml>t[rs].sumr)
        {
            f.suml=t[ls].suml-t[rs].sumr+t[rs].suml;
            f.sumr=t[ls].sumr;
        }
        else 
        {
            f.suml=t[rs].suml;
            f.sumr=t[ls].sumr+t[rs].sumr-t[ls].suml;
        }
        f.dis=max({t[ls].dis,t[rs].dis,t[ls].suf1+t[rs].pre2,t[ls].suf2+t[rs].pre1});
        f.pre1=max({t[ls].pre1,t[rs].pre1+t[ls].sumr-t[ls].suml,t[rs].pre2+t[ls].suml+t[ls].sumr});
        f.pre2=max({t[ls].pre2,t[rs].pre2+t[ls].suml-t[ls].sumr});
        f.suf1=max({t[rs].suf1,t[ls].suf1+t[rs].suml-t[rs].sumr,t[ls].suf2+t[rs].suml+t[rs].sumr});
        f.suf2=max({t[rs].suf2,t[ls].suf2+t[rs].sumr-t[rs].suml});
    }
    void build(int p,int l,int r)
    {
        f.l=l,f.r=r;
        if(l==r) {push(p,l); return ;}
        int mid=(l+r)>>1;
        build(ls,l,mid),build(rs,mid+1,r);
        pushup(p);
    }
    void change(int p,int x)
    {
        if(f.l==f.r) {push(p,x); return ;}
        int mid=(f.l+f.r)>>1;
        if(x<=mid) change(ls,x);
        else change(rs,x);
        pushup(p);
    }
    signed main()
    {
        read(n); 
        for(int i=1,a,b;i<=n-1;i++)
        {
            read(a),read(b);
            e[a].push_back(b);
            e[b].push_back(a);
        }
        dfs(1,0);
        build(1,1,tot);
        read(m); cnt=n;
        for(int i=1,x;i<=m;i++)
        {
            char op;
            cin>>op; 
            if(op=='C')
            {
                read(x);
                c[x]^=1;
                cnt+=(c[x]==0)?1:-1;
                change(1,dfn[x]);
            }
            else 
            {
                if(cnt==0) puts("-1");
                else if(cnt==1) puts("0");
                else write(t[1].dis),puts("");
            }
        }
    }
    

总结

不管什么情况记得算空间。

由于赛时被 Huge 喊回去整理内务耗费时间,但不是主要原因,因为身体不好走两趟直接虚了,状态很差。

昨天 #define int long long\(D\) 了,以后不这么写了。

posted @ 2024-07-22 09:40  卡布叻_周深  阅读(36)  评论(0编辑  收藏  举报