CSP-S 2022

假期计划

简要题意:一个关键点可以通过最多 \(k\) 条边到达下一个关键点,要求你选出 \(4\)不同关键点使得从 \(1\) 出发并且能回到 \(1\),并最大化选出点的权值和。

首先可以 \(O(n^2\log n)\) 处理出每个点在 \(k\) 条边内能到的点,记为 \(a_{i,j}=\{0,1\}\)

我们记这 \(4\) 个点为 \(a,b,c,d\)。那么我们可以预处理出来从 \(1\)\(a\)\(b\) 可不可行,以及从 \(c\)\(d\)\(1\) 可不可行。这部分是 \(O(n^2)\) 的。

那么我们直接枚举 \(b,c\) 的话,只需判断 \(a,c\)\(b,d\) 是否相同,就可以统计答案了。如果直接枚举 \(a,d\) 单次是 \(O(n^2)\) 的,总复杂度 \(O(n^4)\)

考虑优化暴力,我们发现对于 \(b\) 来说,枚举 \(a\) 时只需要避免枚举到 \(c\),所以这里可以前后缀 \(\max\) 来优化。但如果此时 \(a,d\) 对于 \(b,c\) 都是最优值并且 \(a=d\) 的时候我们需要取舍,其中之一取次大值,所以前后缀再记一个次大值就好了。总时间复杂度 \(O(n^2\log n +n^2)\)

代码:

#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
void write(int x)
{
    if(x<0){x=-x;pc('-');}
    if(x>9)write(x/10);
    pc(x%10+48);
}
const int inf=4187201950435737472;
int n,m,k,ans;
int val[2505];
vector<int>e[2505];
bool a[2505][2505];
int dis[2505];bool vis[2505];
priority_queue< pair<int,int> >q;
void dk(int s)
{
    memset(dis,0x3f,sizeof dis);
    memset(vis,false,sizeof vis);
    q.push({0,s});dis[s]=0;
    while(!q.empty())
    {
        int u=q.top().second;q.pop();
        if(vis[u])continue; vis[u]=true;
        for(int v:e[u])
            if(dis[v]>dis[u]+1)
                {dis[v]=dis[u]+1;q.push({-dis[v],v});}
    }
    for(int t=1;t<=n;++t)
        if(dis[t]>0&&dis[t]<=k+1)a[s][t]=true;
}
int pmx[2505][2505],pmn[2505][2505],smx[2505][2505],smn[2505][2505];
signed main()
{
    freopen("holiday.in","r",stdin);
    freopen("holiday.out","w",stdout);
    n=read(),m=read(),k=read();val[0]=-inf;
    for(int i=2;i<=n;++i)val[i]=read();
    for(int i=1;i<=m;++i)
    {
        int u=read(),v=read();
        e[u].push_back(v);
        e[v].push_back(u);
    }for(int i=1;i<=n;++i)dk(i);
    
    for(int i=2;i<=n;++i)
    {
        int mx=0,mn=0;
        for(int j=1;j<=n;++j)
        {
            if(val[mx]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mx;
            else if(val[mx]>val[pmn[i][j]])pmn[i][j]=mx;
            if(val[mn]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mn;
            else if(val[mn]>val[pmn[i][j]])pmn[i][j]=mn;
            if(!a[1][j]||!a[j][i]||i==j)continue;
            if(val[j]>val[mx])mn=mx,mx=j;
            else if(val[j]>val[mn])mn=j;
        }mx=0,mn=0;
        for(int j=n;j>=1;--j)
        {
            if(val[mx]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mx;
            else if(val[mx]>val[pmn[i][j]])pmn[i][j]=mx;
            if(val[mn]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mn;
            else if(val[mn]>val[pmn[i][j]])pmn[i][j]=mn;
            if(!a[1][j]||!a[j][i]||i==j)continue;
            if(val[j]>val[mx])mn=mx,mx=j;
            else if(val[j]>val[mn])mn=j;
        }
    }
    for(int i=2;i<=n;++i)
    {
        int mx=0,mn=0;
        for(int j=1;j<=n;++j)
        {
            if(val[mx]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mx;
            else if(val[mx]>val[smn[i][j]])smn[i][j]=mx;
            if(val[mn]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mn;
            else if(val[mn]>val[smn[i][j]])smn[i][j]=mn;
            if(!a[j][1]||!a[i][j]||i==j)continue;
            if(val[j]>val[mx])mn=mx,mx=j;
            else if(val[j]>val[mn])mn=j;
        }mx=0,mn=0;
        for(int j=n;j>=1;--j)
        {
            if(val[mx]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mx;
            else if(val[mx]>val[smn[i][j]])smn[i][j]=mx;
            if(val[mn]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mn;
            else if(val[mn]>val[smn[i][j]])smn[i][j]=mn;
            if(!a[j][1]||!a[i][j]||i==j)continue;
            if(val[j]>val[mx])mn=mx,mx=j;
            else if(val[j]>val[mn])mn=j;
        }
    }
    for(int i=2;i<=n;++i)
        for(int j=2;j<=n;++j)
        {
            if(i==j||!a[i][j])continue;
            if(pmx[i][j]==smx[j][i])ans=max(ans,val[i]+val[j]+max(val[pmx[i][j]]+val[smn[j][i]],val[pmn[i][j]]+val[smx[j][i]]));
            else ans=max(ans,val[i]+val[j]+val[pmx[i][j]]+val[smx[j][i]]);
        }
    write(ans),pc('\n');
    return 0;
}

策略游戏

全场最简单的题。

维护 \(a_{1\dots n}\) 的区间非负最大值,最小值;非正最大值,最小值。维护 \(b_{1\dots m}\) 的区间最大值,最小值。然后分类讨论即可。注意细节。

可以用 st 表来实现,单次询问 \(O(1)\),也可以直接上 \(2\) 棵线段树,单次 \(O(\log n)\)

我写的线段树,时间复杂度 \(O(n+m+q\log n)\)

代码:

#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int long long
#define ls (pos<<1)
#define rs (pos<<1|1)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
void write(int x)
{
    if(x<0){x=-x;pc('-');}
    if(x>9)write(x/10);
    pc(x%10+48);
}
const int inf=2e9;
int n,m,q,ans;
int a[100005],b[100005];
struct node
{
    int lmx,lmn,rmn,rmx;
    node(int Lmx=inf,int Lmn=-inf,int Rmn=inf,int Rmx=-inf){lmx=Lmx;lmn=Lmn;rmn=Rmn;rmx=Rmx;}
};

struct a_tree
{
    int lmx[400005],lmn[400005],rmn[400005],rmx[400005];
    void build(int pos,int l,int r)
    {
        if(l==r)
        {
            if(a[l]<0){lmx[pos]=lmn[pos]=a[l];rmx[pos]=-inf,rmn[pos]=inf;}
            else if(a[l]>0){lmx[pos]=inf,lmn[pos]=-inf;rmx[pos]=rmn[pos]=a[l];}
            else {lmx[pos]=lmn[pos]=rmx[pos]=rmn[pos]=0;}
            return;
        }int mid=(l+r)>>1;
        build(ls,l,mid);build(rs,mid+1,r);
        lmx[pos]=min(lmx[ls],lmx[rs]);lmn[pos]=max(lmn[ls],lmn[rs]);
        rmn[pos]=min(rmn[ls],rmn[rs]);rmx[pos]=max(rmx[ls],rmx[rs]);
    }
    node merge(node x,node y)
    {
        node res;
        res.lmx=min(x.lmx,y.lmx);res.lmn=max(x.lmn,y.lmn);
        res.rmn=min(x.rmn,y.rmn);res.rmx=max(x.rmx,y.rmx);
        return res;
    }
    node query(int pos,int l,int r,int L,int R)
    {
        if(L<=l&&r<=R)return (node){lmx[pos],lmn[pos],rmn[pos],rmx[pos]};
        int mid=(l+r)>>1;node res;
        if(L<=mid)res=merge(res,query(ls,l,mid,L,R));
        if(R>mid)res=merge(res,query(rs,mid+1,r,L,R));
        return res;
    }
}tra;


struct b_tree
{
    int mn[400005],mx[400005];
    void build(int pos,int l,int r)
    {
        if(l==r){mn[pos]=mx[pos]=b[l];return;}
        int mid=(l+r)>>1;
        build(ls,l,mid);build(rs,mid+1,r);
        mn[pos]=min(mn[ls],mn[rs]);mx[pos]=max(mx[ls],mx[rs]);
    }
    pii merge(pii x,pii y)
    {
        pii res={inf,-inf};
        res.fi=min(x.fi,y.fi);res.se=max(x.se,y.se);
        return res;
    }
    pii query(int pos,int l,int r,int L,int R)
    {
        if(L<=l&&r<=R)return {mn[pos],mx[pos]};
        int mid=(l+r)>>1;pii res={inf,-inf};
        if(L<=mid)res=merge(res,query(ls,l,mid,L,R));
        if(R>mid)res=merge(res,query(rs,mid+1,r,L,R));
        return res;
    }
}trb;
signed main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    n=read(),m=read(),q=read();
    for(int i=1;i<=n;++i)a[i]=read();
    for(int i=1;i<=m;++i)b[i]=read();
    tra.build(1,1,n);trb.build(1,1,m);
    for(int i=1;i<=q;++i)
    {
        
        int l1=read(),r1=read(),l2=read(),r2=read();ans=-2e18;
        pii tmp1=trb.query(1,1,m,l2,r2);int mn=tmp1.fi,mx=tmp1.se;
        node tmp2=tra.query(1,1,n,l1,r1);
        int lmx=tmp2.lmx,lmn=tmp2.lmn,rmn=tmp2.rmn,rmx=tmp2.rmx;
        if(l1==r1){write(min(a[l1]*mn,a[l1]*mx)),pc('\n');continue;}
        if(l2==r2)
        {
            if(b[l2]<=0){if(lmx!=inf)ans=max(ans,b[l2]*lmx);if(rmn!=inf)ans=max(ans,b[l2]*rmn);} 
            else {if(rmx!=-inf)ans=max(ans,b[l2]*rmx);if(lmn!=-inf)ans=max(ans,b[l2]*lmn);}
            write(ans),pc('\n');continue;
        }
        if(mn>=0)ans=rmx!=-inf?mn*rmx:mx*lmn;
        else if(mx<=0)ans=lmx!=inf?mx*lmx:mn*rmn;
        else{if(rmn!=inf)ans=max(ans,mn*rmn);if(lmn!=-inf)ans=max(ans,mx*lmn);}
        write(ans),pc('\n');
    }return 0;
}

星战

首先可以发现能进行反攻就是满足所有点的出度为 \(1\)。这里就可以拿到暴力的 \(60\) 分了。

证明:由每个点都可以无限次虫洞穿梭可得,每个点的出度不为 \(0\)。由每个据点可以实现连续穿梭可得,每个点的出度为 \(1\)。所以第二个条件包含第一个条件,即我们只需要满足所有点出度为 \(1\) 即可,证毕。

\(1,3\) 操作容易维护,但 \(2,4\) 操作不好维护。我们可以考虑人类智慧做法——哈希。

给每个点随机一个 \(\text{unsigned long long}\) 内的权值 \(a_i\),若点 \(v\) 有若干条边指向自己,那么 \(val_v=\sum a_u\)

可以发现要满足上面的条件,就是 \(\sum_{i=1}^n val_i=\sum_{i=1}^n a_i\)

那么简单维护下即可,具体实现可以看代码。复杂度 \(O(n)\)

代码:

#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int unsigned long long
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
void write(int x)
{
    if(x<0){x=-x;pc('-');}
    if(x>9)write(x/10);
    pc(x%10+48);
}
mt19937_64 rng(time(NULL));
int n,m,q,ans,sum;
int a[500005],s[500005],c[500005];
signed main()
{
    freopen("galaxy.in","r",stdin);
    freopen("galaxy.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;++i)a[i]=rng(),sum+=a[i];
    for(int i=1;i<=m;++i)
    {
        int u=read(),v=read();
        s[v]+=a[u];c[v]+=a[u];ans+=a[u];
    }q=read();
    for(int i=1;i<=q;++i)
    {
        int op=read(),u=read(),v;if(op&1)v=read();
        if(op==1){c[v]-=a[u];ans-=a[u];}
        else if(op==3){c[v]+=a[u];ans+=a[u];}
        else if(op==2){ans-=c[u];c[u]=0;}
        else if(op==4){ans+=s[u]-c[u];c[u]=s[u];}
        puts(ans==sum?"YES":"NO");
    }
    return 0;
}

数据传输

\(k=1\) 时,就是路径权值和。

\(k=2\) 时,就是路径上选一些点并且相邻点距离不超过 \(2\),最小化权值和。可以简单证明为什么不会选择路径外的点:假如 \(u\) 跳到 \(fa_u\) 的另外一个儿子,那么此时再跳就只能跳到 \(fa_{fa_u}\) 上,这显然不如直接从 \(u\) 跳到 \(fa_{fa_u}\) 更优,证毕。

\(k=3\) 时,就可以跳到路径下一个节点的另一个儿子上去了,我们考虑 dp。

我们设 \(a_i\)\(i\) 点的权值,\(b_{i,0/1}\) 是距离 \(i\)\(0/1\) 的点的最小权值,\(f_{i,0/1/2}\) 表示从初始点走到位于距离 \(i\)\(0/1/2\) 的点上的最小代价。

这里每次询问 \(O(n)\) 的 dp 就可以拿到 \(44\) 分了。再把 \(k=1,k=2\) 的特殊性质分拿到就是 \(76\) 分了。

我们假设现在位于 \(i\) 点,要转移到 \(i+1\) 点。

\(k=1\)

\[f_{i+1,0}=f_{i,0} \]

\(k=2\)

\[\begin{cases} f_{i+1,0}=\min(f_{i,0}+b_{i+1,0}, f_{i,1}+b_{i+1, 0}) \\ f_{i+1,1}=f_{i,0} \\ f_{i+1,2}=\text{inf} \end{cases} \]

\(k=3\)

\[\begin{cases} f_{i+1,0}=\min(f_{i,0}+b_{i+1,0},f_{i,1}+b_{i+1,0},f_{i,2}+b_{i+1,0}) \\ f_{i+1,1}=\min(f_{i,0},f_{i,1}+b_{i+1,1}) \\ f_{i+1,2}=f_{i,1} \end{cases} \]

发现第二维很小,可以用倍增+矩阵来维护。

我们对矩阵乘法重定义:

\[c_{i,j}=\min_{k=0}^2\{a_{i,k}+b_{k,j}\} \]

那么可以写出转移:
\(k=1\)

\[\left[\begin{array}{ccc}f_{i+1,0} && f_{i+1,1} && f_{i+1,2}\end{array}\right] = \left[\begin{array}{ccc}f_{i,0} && f_{i,1} && f_{i,2}\end{array}\right] \times \left[ \begin{array}{ccc} b_{i+1,0} && \text{inf} && \text{inf} \\ \text{inf} && \text{inf} && \text{inf}\\ \text{inf} && \text{inf} && \text{inf}\\ \end{array} \right] \]

\(k=2\)

\[\left[\begin{array}{ccc}f_{i+1,0} && f_{i+1,1} && f_{i+1,2}\end{array}\right] = \left[\begin{array}{ccc}f_{i,0} && f_{i,1} && f_{i,2}\end{array}\right] \times \left[ \begin{array}{ccc} b_{i+1,0} && 0 && \text{inf} \\ b_{i+1,0} && \text{inf} && \text{inf}\\ \text{inf} && \text{inf} && \text{inf}\\ \end{array} \right] \]

\(k=3\)

\[\left[\begin{array}{ccc}f_{i+1,0} && f_{i+1,1} && f_{i+1,2}\end{array}\right] = \left[\begin{array}{ccc}f_{i,0} && f_{i,1} && f_{i,2}\end{array}\right] \times \left[ \begin{array}{ccc} b_{i+1,0} && 0 && \text{inf} \\ b_{i+1,0} && b_{i+1,1} && 0\\ b_{i+1,0} && \text{inf} && \text{inf}\\ \end{array} \right] \]

分别预处理出向上和向下的倍增矩阵 \(up,dn\),单次询问复杂度为 \(O(3^3\log n)\)。具体实现看代码。

代码:

#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int long long
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
void write(int x)
{
    if(x<0){x=-x;pc('-');}
    if(x>9)write(x/10);
    pc(x%10+48);
}
const int inf=1e18;
int n,m,q,lg[200005];
vector<int>e[200005];
int a[200005],b[200005][2];
struct matrix
{
    int a[3][3];
    matrix(int x=0){for(int i=0;i<3;++i)fill(a[i],a[i]+3,inf); if(x){a[0][0]=a[1][1]=a[2][2]=0;}}
    int* operator [](int x){return a[x];}
    friend matrix operator *(matrix x,matrix y)
    {
        matrix res;
        for(int i=0;i<3;++i)
            for(int j=0;j<3;++j)
                for(int k=0;k<3;++k)
                    res[i][j]=min(res[i][j],x[i][k]+y[k][j]);
        return res;
    }
}up[200005][19],dn[200005][19];
matrix init(int x)
{
    matrix res;
    if(m==1){res[0][0]=b[x][0];}
    else if(m==2){res[0][0]=res[1][0]=b[x][0];res[0][1]=0;}
    else if(m==3){res[0][0]=res[1][0]=res[2][0]=b[x][0];res[1][1]=b[x][1];res[0][1]=res[1][2]=0;}
    return res;
}
int fa[200005][19],dep[200005];
void dfs(int x,int f)
{
    fa[x][0]=f;dep[x]=dep[f]+1;b[x][1]=f?a[f]:inf;
    for(int i=1;i<=lg[dep[x]];++i)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int y:e[x])
    {
        if(y==f)continue;
        dfs(y,x);b[x][1]=min(b[x][1],a[y]);
    }up[x][0]=dn[x][0]=init(x);
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=lg[dep[x]-dep[y]];i>=0;--i)
        if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
    for(int i=lg[dep[x]];i>=0;--i)
        if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int SON(int x,int y)
{
    for(int i=lg[dep[x]-dep[y]];i>=0;--i)
        if(dep[fa[x][i]]>dep[y])x=fa[x][i];
    return x;
}
matrix jmp(int x,int y,bool op)
{
    matrix res(1);
    if(dep[x]<=dep[y])return res;
    for(int i=lg[dep[x]-dep[y]];i>=0;--i)
        if(dep[fa[x][i]]>=dep[y])
            res=op?dn[x][i]*res:res*up[x][i],x=fa[x][i];
    return res;
}
matrix query(int x,int y)
{
    matrix res(1);
    if(x==y||fa[x][0]==y||fa[y][0]==x)return res;
    int lca=LCA(x,y);if(x!=lca&&y!=lca)res=init(lca);
    return jmp(fa[x][0],lca,0)*res*jmp(fa[y][0],lca,1);
}
signed main()
{
    freopen("transmit.in","r",stdin);
    freopen("transmit.out","w",stdout);
    n=read(),q=read(),m=read();lg[0]=-1;
    for(int i=1;i<=n;++i)lg[i]=lg[i>>1]+1;
    for(int i=1;i<=n;++i)b[i][0]=a[i]=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        e[u].push_back(v);e[v].push_back(u);
    }dfs(1,0);
    for(int j=1;j<=lg[n];++j)
        for(int i=1;i<=n;++i)
            up[i][j]=up[i][j-1]*up[fa[i][j-1]][j-1],
            dn[i][j]=dn[fa[i][j-1]][j-1]*dn[i][j-1];
    for(int i=1;i<=q;++i)
    {
        int x=read(),y=read();
        matrix res;res[0][0]=a[x];
        res=res*query(x,y)*init(y);
        write(res[0][0]),pc('\n');
    }return 0;
}

后记

今年部分分是不是好多啊qwq,似乎不挂分几乎都能300+,然而 HB 的 CSP 没了。

posted @ 2022-10-31 15:28  violetctl39  阅读(255)  评论(0编辑  收藏  举报