潜龙未见静水流,沉默深藏待时秋。一朝破空声势振,惊世骇俗展雄猷。
随笔 - 82, 文章 - 0, 评论 - 3, 阅读 - 2161

CSP 2022 提高组 题解

目录

A.假期计划
B.策略游戏
C.星战
D.数据传输

A.假期计划

题目描述

给定一张 n 个点, m 条边的无向图,点权 vi

你需要构造一条路径 1abcd1 ,满足 1,a,b,c,d 互不相同,并且相邻两点在原图上可以经过不超过 k 个中转点(不包含两个端点)到达。

va+vb+vc+vd 的最大值。

数据范围

  • 5n2500,1m104,0k100,1vi1018,保证至少存在一条合法路径。

时间限制 2s ,空间限制 512MB

分析

注意到图的规模很小,用 01-bfs 可以在 O(nm) 的时间内求出任意两点是否满足在原图上的距离 k+1

对每个点 x ,记录 1ix 的前三大的二元组 (vi,i)

枚举中间的边 bc ,将这 9(a,d) 检查互异性后更新答案。

时间复杂度 O(n2+nm)

#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2505;
int k,m,n,u,v,res;
int d[maxn],w[maxn];
pii f[maxn][3];
bool b[maxn][maxn];
vector<int> g[maxn];
signed main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=2;i<=n;i++) scanf("%lld",&w[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%lld%lld",&u,&v);
        g[u].push_back(v),g[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
    {
        queue<int> q;
        for(int j=1;j<=n;j++) d[j]=-1;
        d[i]=0,q.push(i);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(auto v:g[u]) if(d[v]==-1) d[v]=d[u]+1,q.push(v);
        }
        for(int j=1;j<=n;j++) b[i][j]=d[j]>=0&&d[j]<=k+1;
    }
    for(int i=1;i<=n;i++)
        for(int j=2;j<=n;j++)
        {
            if(j==i||!b[1][j]||!b[j][i]) continue;
            pii cur=mp(w[i]+w[j],j);
            if(cur>f[i][0]) f[i][2]=f[i][1],f[i][1]=f[i][0],f[i][0]=cur;
            else if(cur>f[i][1]) f[i][2]=f[i][1],f[i][1]=cur;
            else if(cur>f[i][2]) f[i][2]=cur;
        }
    for(int u=1;u<=n;u++)
        for(int v=u+1;v<=n;v++)
        {
            if(!b[u][v]) continue;
            for(int i=0;i<=2;i++)
                for(int j=0;j<=2;j++)
                {
                    int x=f[u][i].se,y=f[v][j].se;
                    if(x&&y&&x!=v&&y!=u&&x!=y) res=max(res,f[u][i].fi+f[v][j].fi);
                }
        }
    printf("%lld\n",res);
    return 0;
}

B.策略游戏

题目描述

给定一个长为 n 的数组 a ,和一个长为 m 的数组 b

q 次询问,给定 l1,r1,l2,r2 ,求 maxl1xr1minl2yr2axby

数据范围

  • 1n,m,q105,109ai,bi109
  • 1l1r1n,1l2r2m

时间限制 1s ,空间限制 512MB

分析

对于 ax0 ,显然 by 只会选择最小值。

对于 ax0 ,显然 by 只会选择最大值。

ax0ax0 的下标 x 分开维护,根据 by 的正负性我们可以知道ax 应该尽量小还是尽量大。

ST 表维护区间最大最小值,时间复杂度 O(nlogn+mlogm+q)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,inf=1e9+5;
int m,n,q,l1,r1,l2,r2;
int a[maxn],b[maxn],lg[maxn];
bool c[maxn];
struct node
{
    int f[17][maxn],g[17][maxn];
    int query(int l,int r,int op)
    {
        int k=lg[r-l+1];
        return !op?min(f[k][l],f[k][r-(1<<k)+1]):max(g[k][l],g[k][r-(1<<k)+1]);
    }
    void init(int n,int *a,bool *b)
    {
        for(int i=1;i<=n;i++) f[0][i]=b[i]?a[i]:inf,g[0][i]=b[i]?a[i]:-inf;
        for(int j=1;j<=16;j++)
            for(int i=1;i+(1<<j)-1<=n;i++)
            {
                f[j][i]=min(f[j-1][i],f[j-1][i+(1<<(j-1))]);
                g[j][i]=max(g[j-1][i],g[j-1][i+(1<<(j-1))]);
            }
    }
}t0,t1,t2;
long long mul(int x,int y)
{
    if(abs(y)==inf) return -1e18;
    return 1ll*x*y;
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    for(int i=2;i<=max(n,m);i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=m;i++) c[i]=1;
    t0.init(m,b,c);
    for(int i=1;i<=n;i++) c[i]=a[i]>=0;
    t1.init(n,a,c);
    for(int i=1;i<=n;i++) c[i]=a[i]<=0;
    t2.init(n,a,c);
    while(q--)
    {
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int x=t0.query(l2,r2,0),y=t0.query(l2,r2,1);
        printf("%lld\n",max(mul(x,t1.query(l1,r1,x>=0)),mul(y,t2.query(l1,r1,y>=0))));
    }
    return 0;
}

C.星战

题目描述

给定一张 n 个点, m 条边的有向图,初始所有边都被激活。

接下来 q 次操作:

  • 1 u v :失活一条 uv 的边,保证这条边操作前处于激活状态。
  • 2 u :失活以 u 为终点的所有边。
  • 3 u v :激活一条 uv 的边,保证这条边操作前处于失活状态。
  • 4 u :激活以 u 为终点的所有边。

在每次操作后,询问是否满足每个点恰有一条出边被激活。

数据范围

  • 1n,m,q5105

时间限制 2s ,空间限制 512MB

分析

观察本题的操作,发现更容易维护的是入度而不是出度。

将所有以 i 为起点的被激活的边全部赋值 wi ,其中 wi 为关于 i 的随机数。

记第 i 个点的入度为 xi ,注意到每条边恰好贡献一个出度和一个入度,因此我们可以维护以 i 为终点的边的权值和,也就相当于维护了 wixi 的值。

由于 wixi=wi 的非负整数解 (x1,,xn) 很大概率只有 (1,,1) 这一组,因此我们可以认为出现这种情况等价于每个点出度均为一。

时间复杂度 O(n+m+q)

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int maxn=5e5+5;
int m,n,q,u,v,op;
ull all,cur;
ull f[maxn],g[maxn],w[maxn];
mt19937_64 rnd(time(0));
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) w[i]=rnd(),all+=w[i];
    while(m--) scanf("%d%d",&u,&v),g[v]+=w[u];
    for(int i=1;i<=n;i++) f[i]=g[i],cur+=f[i];
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d",&op);
        if(op==1) scanf("%d%d",&u,&v),cur-=w[u],f[v]-=w[u];
        if(op==2) scanf("%d",&u),cur-=f[u],f[u]=0;
        if(op==3) scanf("%d%d",&u,&v),cur+=w[u],f[v]+=w[u];
        if(op==4) scanf("%d",&u),cur+=g[u]-f[u],f[u]=g[u];
        printf(cur==all?"YES\n":"NO\n");
    }
    return 0;
}

D.数据传输

题目描述

给定一棵 n 个节点的树和常数 k ,点有点权 vi

q 次询问,每次给定 s,t

你需要构造一个序列 c (记长度为 m ),满足 c1=s,cm=t,dis(ci,ci+1)k ,求 i=1mvci 的最小可能值。

数据范围

  • 1n,q2105,1k3,1vi109
  • 1s,tn,st

时间限制 3s ,空间限制 1GB

分析

对于 k=1 ,输出树上带权路径和即可。

对于 k=2 ,容易发现我们只会经过 st 树上路径中的点。

fi,0/1 表示考虑到树上第 i 个点,是否在 c 中出现, vci 的最小可能值。

记路径中上一个点为 j ,转移方程如下:

fi,0=min(fj,0,fj,1)+vifi,1=fj,0

对于 k=3 ,我们不一定只经过树上路径中的点,还可以通过路径上某个点的邻点来绕路!

显然如果要绕路一定只会选择第i个点的邻点中的最小权值,不妨记为 xi

fi,0/1/2 表示考虑到树上第 i 个点,上一个在 c 中出现的点到 i 的距离为 0/1/2vci 的最小可能值。

如果 xi 刚好也在 st 路径上也没关系,因为这种情况绕路一定不优。

记路径中上一个点为 j ,转移方程如下:

fi,0=min(fj,0,fj,1,fj,2)+vifi,1=min(fj,0,fj,1+xi)fi,2=fj,1

容易发现上述转移都可以用 (min,+) 矩阵刻画,维护倍增向上和倍增向下的矩阵连乘积即可。

时间复杂度 O(k3(n+q)logn)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5,inf=1e18;
int m,n,q,u,v;
int d[maxn],w[maxn],fa[maxn][18];
vector<int> g[maxn];
struct mat
{
    int v[3][3];
}x,y,a[maxn],up[maxn][18],dn[maxn][18];
inline mat operator*(const mat &a,const mat &b)
{
    static mat c;
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++)
        {
            c.v[i][j]=inf;
            for(int k=0;k<m;k++) c.v[i][j]=min(c.v[i][j],a.v[i][k]+b.v[k][j]);
        }
    return c;
}
void dfs(int u,int f)
{
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1,fa[v][0]=u,dn[v][0]=up[v][0]=a[v];
        for(int i=1;i<=17;i++)
        {
            fa[v][i]=fa[fa[v][i-1]][i-1];
            dn[v][i]=dn[fa[v][i-1]][i-1]*dn[v][i-1];
            up[v][i]=up[v][i-1]*up[fa[v][i-1]][i-1];
        }
        dfs(v,u);
    }
}
int lca(int u,int v)
{
    if(d[u]<d[v]) swap(u,v);
    for(int i=17;i>=0;i--)
        if(d[fa[u][i]]>=d[v])
            u=fa[u][i];
    if(u==v) return u;
    for(int i=17;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
signed main()
{
    scanf("%lld%lld%lld",&n,&q,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%lld%lld",&u,&v);
        g[u].push_back(v),g[v].push_back(u);
    }
    if(m==1) for(int i=1;i<=n;i++) a[i].v[0][0]=w[i];
    if(m==2) for(int i=1;i<=n;i++) a[i].v[0][0]=a[i].v[1][0]=w[i],a[i].v[1][1]=inf;
    if(m==3) for(int i=1;i<=n;i++)
    {
        int mn=inf;
        for(auto v:g[i]) mn=min(mn,w[v]);
        a[i]={w[i],0,inf,w[i],mn,0,w[i],inf,inf};
    }
    d[1]=1,dfs(1,0);
    mat tmp={0,inf,inf,inf,0,inf,inf,inf,0};
    while(q--)
    {
        scanf("%lld%lld",&u,&v),x=y=tmp;
        int p=lca(u,v);
        for(int i=17;i>=0;i--)
        {
            if(d[fa[u][i]]>=d[p]) x=x*up[u][i],u=fa[u][i];
            if(d[fa[v][i]]>=d[p]) y=dn[v][i]*y,v=fa[v][i];
        }
        printf("%lld\n",(x*a[p]*y).v[m-1][0]);
    }
    return 0;
}

posted on   peiwenjun  阅读(2)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示