peiwenjun's blog 没有知识的荒原

CSP 2022 提高组 题解

A.假期计划

题目描述

给定一张 \(n\) 个点, \(m\) 条边的无向图,点权 \(v_i\)

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

\(v_a+v_b+v_c+v_d\) 的最大值。

数据范围

  • \(5\le n\le 2500,1\le m\le 10^4,0\le k\le 100,1\le v_i\le 10^{18}\),保证至少存在一条合法路径。

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\)

分析

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

对每个点 \(x\) ,记录 \(1\to i\to x\) 的前三大的二元组 \((v_i,i)\)

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

时间复杂度 \(\mathcal O(n^2+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\) 次询问,给定 \(l_1,r_1,l_2,r_2\) ,求 \(\max\limits_{l_1\le x\le r_1}\min\limits_{l_2\le y\le r_2}a_x\cdot b_y\)

数据范围

  • \(1\le n,m,q\le 10^5,-10^9\le a_i,b_i\le 10^9\)
  • \(1\le l_1\le r_1\le n,1\le l_2\le r_2\le m\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{512MB}\)

分析

对于 \(a_x\ge 0\) ,显然 \(b_y\) 只会选择最小值。

对于 \(a_x\le 0\) ,显然 \(b_y\) 只会选择最大值。

\(a_x\ge 0\)\(a_x\le 0\) 的下标 \(x\) 分开维护,根据 \(b_y\) 的正负性我们可以知道\(a_x\) 应该尽量小还是尽量大。

\(\texttt{ST}\) 表维护区间最大最小值,时间复杂度 \(\mathcal O(n\log n+m\log m+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 :失活一条 \(u\to v\) 的边,保证这条边操作前处于激活状态。
  • 2 u :失活以 \(u\) 为终点的所有边。
  • 3 u v :激活一条 \(u\to v\) 的边,保证这条边操作前处于失活状态。
  • 4 u :激活以 \(u\) 为终点的所有边。

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

数据范围

  • \(1\le n,m,q\le 5\cdot 10^5\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\)

分析

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

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

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

由于 \(\sum w_ix_i=\sum w_i\) 的非负整数解 \((x_1,\cdots,x_n)\) 很大概率只有 \((1,\cdots,1)\) 这一组,因此我们可以认为出现这种情况等价于每个点出度均为一。

时间复杂度 \(\mathcal 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\) ,点有点权 \(v_i\)

\(q\) 次询问,每次给定 \(s,t\)

你需要构造一个序列 \(c\) (记长度为 \(m\) ),满足 \(c_1=s,c_m=t,\texttt{dis}(c_i,c_{i+1})\le k\) ,求 \(\sum_{i=1}^mv_{c_i}\) 的最小可能值。

数据范围

  • \(1\le n,q\le 2\cdot 10^5,1\le k\le 3,1\le v_i\le 10^9\)
  • \(1\le s,t\le n,s\neq t\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{1GB}\)

分析

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

对于 \(k=2\) ,容易发现我们只会经过 \(s\to t\) 树上路径中的点。

\(f_{i,0/1}\) 表示考虑到树上第 \(i\) 个点,是否在 \(c\) 中出现, \(\sum v_{c_i}\) 的最小可能值。

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

\[f_{i,0}=\min(f_{j,0},f_{j,1})+v_i\\ f_{i,1}=f_{j,0} \]

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

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

\(f_{i,0/1/2}\) 表示考虑到树上第 \(i\) 个点,上一个在 \(c\) 中出现的点到 \(i\) 的距离为 \(0/1/2\)\(\sum v_{c_i}\) 的最小可能值。

如果 \(x_i\) 刚好也在 \(s\to t\) 路径上也没关系,因为这种情况绕路一定不优。

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

\[f_{i,0}=\min(f_{j,0},f_{j,1},f_{j,2})+v_i\\ f_{i,1}=\min(f_{j,0},f_{j,1}+x_i)\\ f_{i,2}=f_{j,1} \]

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

时间复杂度 \(\mathcal O(k^3(n+q)\log n)\)

#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 2023-04-13 18:45  peiwenjun  阅读(27)  评论(0)    收藏  举报

导航