【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)

【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)

\(T1\) luogu P11323 【MX-S7-T1】「SMOI-R2」Happy Card \(20pts\)

  • 发现可以把「炸弹」也看做「三带一」。

  • 先使用「三带一」带走原用于出「单牌」的牌,若「三带一」还有剩余则尝试带走原用于出「对子」的牌,否则直接出「单牌」和「对子」。

    点击查看代码
    ll a[300010],r[4];
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        ll t,n,ans,cnt,i,j;
        scanf("%lld",&t);
        for(j=1;j<=t;j++)
        {
            ans=cnt=0;
            memset(r,0,sizeof(r));
            scanf("%lld",&n);
            for(i=1;i<=n;i++)
            {
                scanf("%lld",&a[i]);
                r[a[i]%3]++;
                cnt+=a[i]/3;
            }
            if(cnt>=r[1])
            {
                cnt-=r[1];
                ans+=r[1];
                if(cnt>=2*r[2])
                {
                    cnt-=2*r[2];
                    ans+=2*r[2];
                    ans+=cnt/4*3+((cnt%4==3)?3:(2*(cnt%4!=0)));
                }
                else
                {
                    r[2]-=cnt/2;
                    ans+=cnt;
                    ans+=r[2];
                }
            }
            else
            {
                r[1]-=cnt;
                ans+=cnt+r[1]+r[2];
            }
            printf("%lld\n",ans);
        }
        return 0;
    }
    

\(T2\) luogu P11324 【MX-S7-T2】「SMOI-R2」Speaker \(40pts\)

  • 部分分

    • \(40pts\)
      • \(h_{i}\) 表示从 \(i\) 出发不经过 \(x \to y\) 路径上的其他点所能到达的点的最大贡献 \(c-2dis\) 。则等价于询问 \(c_{x}+c_{y}-dis_{x,y}+\max\limits_{z \in (x \to y)} \{ h_{z} \}\)
    点击查看代码
    // #define Isaac
    namespace IO{
        #ifdef Isaac
        FILE*Fin(fopen("in.in","r")),*Fout(fopen("out.out","w"));
        #else
        FILE*Fin(stdin),*Fout(stdout);
        #endif
        class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin);
        class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout);
    }
    #define cin IO::qcin
    #define cout IO::qcout
    struct node
    {
        int nxt,to,w;
    }e[400010];
    int head[400010],fa[400010],siz[400010],dep[400010],son[400010],top[400010],cnt=0;
    ll dis[400010],c[400010],maxx;
    bool vis[400010];
    queue<int>q;
    void add(int u,int v,int w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs1(int x,int father)
    {
        siz[x]=1;
        fa[x]=father;
        dep[x]=dep[father]+1;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=father)
            {
                dis[e[i].to]=dis[x]+e[i].w;
                dfs1(e[i].to,x);
                siz[x]+=siz[e[i].to];
                son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
            }
        }
    }
    void dfs2(int x,int id)
    {
        top[x]=id;
        if(son[x]!=0)
        {
            dfs2(son[x],id);
            for(int i=head[x];i!=0;i=e[i].nxt)
            {
                if(e[i].to!=fa[x]&&e[i].to!=son[x])
                {
                    dfs2(e[i].to,e[i].to);
                }
            }
        }
    }
    int lca(int u,int v)
    {
        while(top[u]!=top[v])
        {
            if(dep[top[u]]>dep[top[v]])
            {
                u=fa[top[u]];
            }
            else
            {
                v=fa[top[v]];
            }
        }
        return dep[u]<dep[v]?u:v;
    }
    void dfs(int x,int fa,ll dis)
    {
        maxx=max(maxx,-2*dis+c[x]);
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa&&vis[e[i].to]==0)
            {
                dfs(e[i].to,x,dis+e[i].w);
            }
        }
    }
    ll query(int u,int v,int n)
    {
        int rt=lca(u,v);
        ll ans=c[u]+c[v]-(dis[u]+dis[v]-2*dis[rt]);
        for(int i=1;i<=n;i++)
        {
            vis[i]=0;
        }
        maxx=0;
        vis[rt]=1;
        q.push(rt);
        for(;u!=rt;u=fa[u])
        {
            vis[u]=1;
            q.push(u);
        }
        for(;v!=rt;v=fa[v])
        {
            vis[v]=1;
            q.push(v);
        }
        while(q.empty()==0)
        {
            dfs(q.front(),0,0);
            q.pop();
        }
        return ans+maxx;
    }
    int main()
    {
        int n,m,u,v,w,i;
        cin>>n>>m;
        for(i=1;i<=n;i++)
        {
            cin>>c[i];
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        dfs1(1,0);
        dfs2(1,1);
        for(i=1;i<=m;i++)
        {
            cin>>u>>v;
            cout<<query(u,v,n)<<endl;
        }
        return 0;
    }
    
  • 正解

    • 发现“不经过 \(x \to y\) 路径上的其他点”这个限制没什么用,去掉后并不会对答案产生影响,推导过程同 luogu P2491 [SDOI2011] 消防 | luogu P1099 [NOIP2007 提高组] 树网的核
    • 现在需要快捷地求出 \(h_{i}\) 表示从 \(i\) 出发所能到达的点的最大贡献 \(c-2dis\) ,赛时以为会和 luogu P6419 [COCI2014-2015#1] Kamp 一样换根分讨最长链、次长链的多种更新情况遂没写,但实际上因为当固定一些东西后,只有距离这一个变量(其实是距离的差值)所以还算比较好写。
    • \(f_{x}\) 表示从 \(x\) 出发到达以 \(x\) 为根的子树内的节点的最大贡献,状态转移方程为 \(f_{x}=\max(c_{x},\max\limits_{y \in Son(x)}\{ f_{y}-2w_{x \to y} \})\) ,边界为 \(g_{1}=f_{1}\)
    • 然后进行换根,设 \(g_{x}\) 表示从 \(x\) 出发所能到达的点的最大贡献。状态转移方程为 \(g_{y}=\max(f_{y},g_{x}-2w_{x \to y}),y \in Son(x)\)
    • 树剖加 \(ST\) 表或倍增暴力跳即可。
    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[400010];
    ll head[200010],c[200010],dis[200010],dep[200010],siz[200010],fa[200010],top[200010],son[200010],pos[200010],dfn[200010],f[200010],g[200010],cnt=0,tot=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs1(ll x,ll father)
    {
        f[x]=c[x];
        siz[x]=1;
        fa[x]=father;
        dep[x]=dep[father]+1;	
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=father)
            {
                dis[e[i].to]=dis[x]+e[i].w;
                dfs1(e[i].to,x);
                f[x]=max(f[x],f[e[i].to]-2*e[i].w);
                siz[x]+=siz[e[i].to];
                son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
            }
        }
    }
    void reroot(ll x)
    {
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa[x])
            {
                g[e[i].to]=max(f[e[i].to],g[x]-2*e[i].w);
                reroot(e[i].to);
            }
        }
    }
    void dfs2(ll x,ll id)
    {
        top[x]=id;
        tot++;
        dfn[x]=tot;
        pos[tot]=x;
        if(son[x]!=0)
        {
            dfs2(son[x],id);
            for(ll i=head[x];i!=0;i=e[i].nxt)
            {
                if(e[i].to!=fa[x]&&e[i].to!=son[x])
                {
                    dfs2(e[i].to,e[i].to);
                }
            }
        }
    }
    struct ST
    {
        ll fmaxx[200010][25];
        void init(ll n)
        {
            memset(fmaxx,-0x3f,sizeof(fmaxx));
            for(ll i=1;i<=n;i++)
            {
                fmaxx[i][0]=g[pos[i]];
            }
            for(ll j=1;j<=log2(n);j++)
            {
                for(ll i=1;i+(1<<j)-1<=n;i++)
                {
                    fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]);
                }
            }
        }
        ll query(ll l,ll r)
        {
            ll t=log2(r-l+1);
            return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]);
        }
    }T;
    ll query(ll u,ll v)
    {
        ll ans=c[u]+c[v]-dis[u]-dis[v],maxx=-0x3f3f3f3f;
        while(top[u]!=top[v])
        {
            if(dep[top[u]]>dep[top[v]])
            {
                maxx=max(maxx,T.query(dfn[top[u]],dfn[u]));
                u=fa[top[u]];
            }
            else
            {
                maxx=max(maxx,T.query(dfn[top[v]],dfn[v]));
                v=fa[top[v]];
            }
        }
        if(dep[u]<dep[v])
        {
            maxx=max(maxx,T.query(dfn[u],dfn[v]));
            return ans+2*dis[u]+maxx;
        }
        else
        {
            maxx=max(maxx,T.query(dfn[v],dfn[u]));
            return ans+2*dis[v]+maxx;
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        ll n,m,u,v,w,i;
        cin>>n>>m;
        for(i=1;i<=n;i++)
        {
            cin>>c[i];
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        dfs1(1,0);
        g[1]=f[1];
        reroot(1);
        dfs2(1,1);
        T.init(n);
        for(i=1;i<=m;i++)
        {
            cin>>u>>v;
            cout<<query(u,v)<<endl;
        }
        return 0;
    }
    

\(T3\) luogu P11325 【MX-S7-T3】「SMOI-R2」Monotonic Queue \(0pts\)

  • \(R_{i}\) 表示 \(i\) 右侧第一个比 \(a_{i}\) 大的数(若没有则为 \(n+1\) ),那么 \(c_{i}\) 能对答案产生贡献当且仅当存在一个 \(j\) 使得 \(l_{j} \le i \land R_{i} \le r_{j+1}\)

  • 进一步观察性质,得到若 \(l_{j}=l_{j+1}\)\([l_{j+1},r_{j+1}]\) 这一询问区间对答案无用,不妨直接钦定 \(l_{i}=i,r_{i} \ge i\) 。此时限制条件只剩下了 \(R_{i} \le r_{i+1}\)

  • 不妨设 \(r_{i}'=r_{i+1} \in [i+1,n]\) ,此时就只需要考虑 \(r_{1 \sim n-1}'\) 的取值了。

  • \(f_{i,j}\) 表示处理到 \(i\)\(r_{i}'=j\) 的最大值,状态转移方程为 \(f_{i,j}=\max\limits_{k=i}^{j}\{ f_{i-1,k} \}+[j \ge R_{i}] \times c_{i}\)

  • 因为前面钦定 \(l_{i}=i\) 的存在,我们可以不考虑所有数,最终有 \(\max\limits_{i=1}^{n-1}\{ f_{i,i+1} \}\) 即为所求。

  • 因随着 \(i\) 的增长合法的 \(j\) 会不断减少,考虑先对 \(\{ f \}\) 做前缀 \(\max\) 优化,及时清除不合法状态,然后线段树进行进一步优化。

  • \(c_{i} \ge 0\) 时直接区间加即可;否则区间加后还需要重新维护单调性,具体地,在 \([R_{i},n]\) 中找到第一个 \(\ge f_{i,R_{i}-1}\) 的位置 \(p\) (找不到就则为 \(n+1\)),然后用 \(f_{i,R_{i}-1}\) 覆盖 \([R_{i},p-1]\)

    点击查看代码
    const ll inf=0x7f7f7f7f7f7f7f7f;
    ll c[500010],a[500010],R[500010],n;
    stack<ll>s;
    struct SMT
    {
        struct SegmentTree
        {
            ll maxx,lazy,col;
        }tree[2000010];
        ll lson(ll x)
        {
            return x*2;
        }
        ll rson(ll x)
        {
            return x*2+1;
        }
        void pushup(ll rt)
        {
            tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx);
        }
        void build(ll rt,ll l,ll r)
        {
            tree[rt].maxx=tree[rt].lazy=0;
            tree[rt].col=inf;
            if(l==r)
            {
                return;
            }
            ll mid=(l+r)/2;
            build(lson(rt),l,mid);
            build(rson(rt),mid+1,r);
        }
        void pushdown(ll rt)
        {
            if(tree[rt].col!=inf)
            {
                tree[lson(rt)].maxx=tree[rson(rt)].maxx=tree[rt].col;
                tree[lson(rt)].col=tree[rson(rt)].col=tree[rt].col;
                tree[lson(rt)].lazy=tree[rson(rt)].lazy=0;
                tree[rt].col=inf;
            }
            if(tree[rt].lazy!=0)
            {
                tree[lson(rt)].maxx+=tree[rt].lazy;
                tree[rson(rt)].maxx+=tree[rt].lazy;	
                tree[lson(rt)].lazy+=tree[rt].lazy;
                tree[rson(rt)].lazy+=tree[rt].lazy;	
                tree[rt].lazy=0;
            }
        }
        void update1(ll rt,ll l,ll r,ll x,ll y,ll val)
        {
            if(x<=l&&r<=y)
            {
                tree[rt].maxx+=val;
                tree[rt].lazy+=val;
                return;
            }
            pushdown(rt);
            int mid=(l+r)/2;
            if(x<=mid)
            {
                update1(lson(rt),l,mid,x,y,val);
            }
            if(y>mid)
            {
                update1(rson(rt),mid+1,r,x,y,val);
            }
            pushup(rt);
        }
        void update2(ll rt,ll l,ll r,ll x,ll y,ll val)
        {
            if(x<=l&&r<=y)
            {
                tree[rt].maxx=tree[rt].col=val;
                tree[rt].lazy=0;
                return;
            }
            pushdown(rt);
            int mid=(l+r)/2;
            if(x<=mid)
            {
                update2(lson(rt),l,mid,x,y,val);
            }
            if(y>mid)
            {
                update2(rson(rt),mid+1,r,x,y,val);
            }
            pushup(rt);
        }
        ll query1(ll rt,ll l,ll r,ll pos)
        {
            if(l==r)
            {
                return tree[rt].maxx;
            }
            pushdown(rt);
            ll mid=(l+r)/2;
            if(pos<=mid)
            {
                return query1(lson(rt),l,mid,pos);
            }
            else
            {
                return query1(rson(rt),mid+1,r,pos);
            }
        }
        ll query2(ll rt,ll l,ll r,ll x,ll y,ll k)
        {
            if(tree[rt].maxx<k)
            {
                return n+1;
            }
            if(l==r)
            {
                return l;
            }
            pushdown(rt);
            ll mid=(l+r)/2,tmp;
            if(y<=mid)
            {
                return query2(lson(rt),l,mid,x,y,k);
            }
            if(x>mid)
            {
                return query2(rson(rt),mid+1,r,x,y,k);
            }
            tmp=query2(lson(rt),l,mid,x,y,k);
            return (tmp==n+1)?query2(rson(rt),mid+1,r,x,y,k):tmp;
        }
    }T;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        ll ans=0,tmp,pos,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>c[i];
        }
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(i=n;i>=1;i--)
        {
            while(s.empty()==0&&a[s.top()]<a[i])
            {
                s.pop();
            }
            R[i]=(s.empty()==0)?s.top():n+1;
            s.push(i);
        }
        T.build(1,1,n);
        for(i=1;i<=n-1;i++)
        {
            T.update2(1,1,n,i,i,-inf);
            if(R[i]<=n)
            {
                T.update1(1,1,n,R[i],n,c[i]);
                if(c[i]<0)
                {
                    tmp=T.query1(1,1,n,R[i]-1);
                    pos=T.query2(1,1,n,R[i],n,tmp);
                    if(pos-1>=R[i])
                    {
                        T.update2(1,1,n,R[i],pos-1,tmp);
                    }
                }
                ans=max(ans,T.query1(1,1,n,i+1));
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(T4\) luogu P11326 【MX-S7-T4】「SMOI-R2」XA-Game \(0pts\)

总结

  • 整场都在瞪 \(T1\)
  • \(T2\) 的过程中死了两次机,以为 \(O(nq)\) 可以直接冲 \(60pts\) ,然后发现自己写的常数太大了。
  • \(T3\) 赛时没看见保证 \(\{ l \},\{ r \}\) 单调的限制,遂以为给出的代码是假的。基本没有进行进一步转化题意。
posted @ 2024-11-24 20:32  hzoi_Shadow  阅读(65)  评论(0编辑  收藏  举报
扩大
缩小