ARC 杂题

ARC150C Path and Subsequence

考试题削弱版。

给你一个无向图,点 i 有点权 vi。给你一个长为 k 的序列 b,求 1n 的所有路径是否全部包含子序列 b

kn,m105

枚举 bi,从 1 开始,将所有不经过点权为 bi 的点打上标记(时间戳) i,接下来再对没经过的点往下跑。若 n 的时间戳为 k 则说明包含了。时间复杂度 O(n+m+k)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+3;
int n,m,s;
vector<int>e[maxn];
queue<int>q,p;
int vis[maxn];
int c[maxn],b[maxn];
void dfs(int u,int t){
    for(int v:e[u]){
        if(vis[v]==-1){
            vis[v]=t;
            if(c[v]!=b[t+1]){
                dfs(v,t);
            }else{
                p.push(v);
            }
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin>>n>>m>>s;
    for(int i=1,u,v;i<=m;i++){
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i=1;i<=n;i++) cin>>c[i],vis[i]=-1;
    for(int i=1;i<=s;i++) cin>>b[i];
    e[0].push_back(1);
    vis[0]=0;
    q.push(0);
    for(int i=0;i<=s;i++){
        while(!q.empty()){
            int j=q.front();
            q.pop();
            vis[j]=i;
            dfs(j,i);
        }
        q=p;
        while(!p.empty()) p.pop();
    }
    if(vis[n]==s) cout<<"Yes";
    else cout<<"No";
    return 0;
}

ARC185E Adjacent GCD

给你一个数列 a,对于每个 m[1,n],求对于每个 a[1,m] 的子序列 S 相邻两项的 GCD 的和的和。

n5×105,ai105

f(i) 为以 i 结尾的答案。则 f(i) 的答案可以由选/不选 i 继承过来,再计算从每个 j<i 转移过来的系数即可,即

f(i)=2f(i1)+j<i2j1gcd(ai,aj)

由于有 gcd(ai,aj)=dgcd(ai,aj)φ(d)=d[dai][daj]φ(d),所以

f(i)=2f(i1)+j<i2j1d[dai][daj]φ(d)

把与 j 无关的往前扔

f(i)=2f(i1)+daiφ(d)j<i2j1[daj]

g(d,i)=j<i2j1[daj],显然这一坨可以 O(d(ai)) 动态维护,考虑每次加入 ai 统计完当前答案后,就在 dai 的位置加上 2i1 的贡献即可。

所以最后的式子就是 f(i)=2f(i1)+daiφ(d)g(d,i),时间复杂度 O(nd(n))

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+7;
const int mod=998244353;
int n;
int a[maxn];
int f[maxn],g[maxn],po2[maxn],k[maxn],val[maxn];
vector<int>v[maxn];
signed main(){
    cin>>n;
    po2[0]=1;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        po2[i]=po2[i-1]*2%mod;
    }
    for(int i=1;i<=100000;i++){
        k[i]+=i; v[i].emplace_back(i);
        for(int j=2*i;j<=100000;j+=i){
            k[j]-=k[i];
            v[j].emplace_back(i);
        }
    }
    for(int x=1;x<=n;x++){
        f[x]=2*f[x-1];
        for(int d:v[a[x]]){
            f[x]=(f[x]+k[d]*val[d]%mod)%mod;
            val[d]=(val[d]+po2[x-1])%mod;
        }
        cout<<f[x]<<'\n';
    }
    return 0;
}

ARC183C Not Argmax

求满足 m 个形如【[li,ri] 中最大值的不为 pxi】的排列 p 的数量。

n500,m2×105

考虑没有限制时怎么 DP,设 f(l,r) 表示 [l,r] 的答案,枚举区间内最大值的位置,则有

f(l,r)=k=lrf(l,k1)f(k+1,r)(rlkl)

加上限制即记个标记 t(l,r,k),当满足标记时不转移即可,注意转移顺序。标记转移为 t(l,r,k)|=t(l+1,r,k)|t(l,r1,k)。时间复杂度 O(n3+m)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=507;
const int maxm=2e5+7;
const int mod=998244353;
int n,m;
int f[maxn][maxn],fac[maxn],ifac[maxn];
bitset<maxn>is[maxn][maxn];
int qpow(int a,int b){
    int res=1;
    for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
    return res;
}
int C(int a,int b){
    if(a<b) return 0;
    return fac[a]*ifac[a-b]%mod*ifac[b]%mod;
}
signed main(){
    cin>>n>>m;
    for(int i=1,l,r,x;i<=m;i++){
        cin>>l>>r>>x;
        is[l][r][x]=1;  
        if(l==r){
            cout<<"0\n";
            return 0;
        }
    }
    for(int len=2;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            is[l][r]|=is[l+1][r];
            is[l][r]|=is[l][r-1];
        }
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    ifac[n]=qpow(fac[n],mod-2);
    for(int i=n-1;~i;i--) ifac[i]=ifac[i+1]*(i+1)%mod; 
    f[1][0]=1;
    for(int i=1;i<=n;i++) f[i][i]=f[i+1][i]=1;
    for(int len=2;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            for(int k=l;k<=r;k++) if(!is[l][r][k])
                f[l][r]=(f[l][r]+f[l][k-1]*f[k+1][r]%mod*C(r-l,k-l)%mod)%mod;
        }
    cout<<f[1][n];
    return 0;
}

ARC186B Typical Permutation Descriptor

给你一个序列 a 满足 ai<i,求满足以下条件的排列 p 的数量:

  • pj>pi>pai(j(ai,i))

n3×105保证有解

由于保证有解,考虑观察有解的情况所带来的性质:

  1. 区间 [ai,i] 要么把前面的若干区间完全包含,要么左端点与相邻区间端点相交;

由性质 1 与偏序关系可知,假如以偏序关系(大于号连接的两边)连边,pi 为点权,以 p0 为根,则形成一棵满足 u 子树内的点权大于 u 点权的树。

这棵树的性质很好啊,当你用拓扑序遍历这棵树他一定合法,即为充分必要条件了,虽然我没看出来

接下来就是一个裸的树上拓扑序计数了,也是个结论,即

n!i=1nsiz(i)

证明:
考虑树形 DP。设 f(u) 为以 u 为根子树的拓扑序数量。
考虑合并两棵子树 v1,v2,先把两棵子树的方案数乘起来然后考虑顺序,即在 siz(v1)+siz(v2) 个数里选掉 siz(v1) 个数。更一般的,多个子树相当于叠加,而且组合约掉了,则有转移

f(u)=(siz(v1)+siz(v2)siz(v1))(siz(v1)+siz(v2)+siz(v3)siz(v1)+siz(v2))(siz(u)1siz(v1)+siz(v2)++siz(vx1))vson(u)f(v)=(siz(u)1)!vson(u)siz(v)!vson(u)f(v)=(siz(u)1)!vson(u)siz(v)!vson(u)f(v)

考虑把每个 (siz(u)1)!siz(v)! 相抵消,剩下 i=2n1siz(i) 以及 (siz(1)1)!,写得好看点,都乘个 siz(1),即得上式。

时间复杂度 O(n)

ARC187B Sum of CC

给你一个长为 n 的数列 aai=1 表示未确定,可以填入 [1,m],对于一个填完的序列,连边 (i,j) 当且仅当 i<j,aiaj,求所有可能的序列的连通块数量和。

n,m2×103

开始直接看错题,以为是图中的边数,直接干瞪 2h 无果。

考虑发现一些性质,手玩发现所有的连通块形成区间。且左边的最小值大于右边的最大值。这个证明随便找三个数玩一下就出来了。

所以记录前缀 min p,后缀 max q,前后缀 -1 数量 r,s。然后枚举每个断点 i 表示 i,i+1 不在同一联通块的方案数(贡献显然为多出了一个连通块)。即要求前缀 min> 后缀 max,所以 再枚举前缀 min=j,前缀的 -1 取值范围为 [j,m],后缀 -1 的取值范围为 [1,j1],但是这样会算重。记 g1(j) 为前缀 minj 的方案数 =(mj+1)rig2(j) 为后缀 maxj 的方案数 =(j1)si+1,则有 g3(j)=g2(j)g2(j1) 为后缀 max=j 的方案数,这样固定一边乘以另一边就不会记重了。统计 g1(j)g3(j1) 即可。时间复杂度 O(nm)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2007;
const int mod=998244353;
int n,m;
int a[maxn],fac[maxn],ifac[maxn];
int h[maxn],g[maxn],mx[maxn],mi[maxn],pmx[maxn],pmi[maxn];
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
void mul(int &x,int y){x=x*y%mod;}
int qpow(int a,int b){
    int res=1;
    for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
    return res;
}
int C(int a,int b){
    if(a<b) return 0;
    return fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
signed main(){
    cin>>n>>m;
    fac[0]=1;
    for(int i=1;i<=n;i++)
        fac[i]=fac[i-1]*i%mod;
    ifac[n]=qpow(fac[n],mod-2);
    for(int i=n-1;~i;i--)
        ifac[i]=ifac[i+1]*(i+1)%mod;
    pmi[0]=m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        g[i]=g[i-1]+(a[i]==-1);
        if(a[i]!=-1) pmi[i]=min(pmi[i-1],a[i]);
        else pmi[i]=pmi[i-1];
    }
    for(int i=n;i;i--){
        pmx[i]=max(pmx[i+1],a[i]);
        h[i]=h[i+1]+(a[i]==-1); 
    }   
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) mi[j]=mx[j]=0;
        for(int j=1;j<=pmi[i];j++) mi[j]=qpow(m-j+1,g[i]);
        for(int j=pmx[i+1];j<=m;j++) mx[j]=qpow(j,h[i+1]);
        for(int j=2;j<=m;j++) add(ans,mi[j]*(mx[j-1]-mx[j-2]+mod)%mod);
    }
    add(ans,qpow(m,g[n]));
    cout<<ans;
    return 0;
}
posted @   view3937  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
Title
点击右上角即可分享
微信分享提示