ARC 杂题

ARC150C Path and Subsequence

考试题削弱版。

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

\(k\le n,m\le 10^5\)

枚举 \(b_i\),从 \(1\) 开始,将所有不经过点权为 \(b_i\) 的点打上标记(时间戳) \(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\in [1,n]\),求对于每个 \(a_{[1,m]}\) 的子序列 \(S\) 相邻两项的 GCD 的和的和。

\(n\le 5\times 10^5,a_i\le 10^5\)

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

\[f(i)=2f(i-1)+\sum\limits_{j<i} 2^{j-1} \gcd(a_i,a_j) \]

由于有 \(\gcd(a_i,a_j)=\sum\limits_{d\mid \gcd(a_i,a_j)} \varphi(d)=\sum\limits_{d} [d\mid a_i][d\mid a_j]\varphi(d)\),所以

\[f(i)=2f(i-1)+\sum\limits_{j<i} 2^{j-1} \sum\limits_{d} [d\mid a_i][d\mid a_j]\varphi(d) \]

把与 \(j\) 无关的往前扔

\[f(i)=2f(i-1)+\sum\limits_{d\mid a_i} \varphi(d)\sum\limits_{j<i} 2^{j-1} [d\mid a_j] \]

\(g(d,i)=\sum\limits_{j<i} 2^{j-1} [d\mid a_j]\),显然这一坨可以 \(O(d(a_i))\) 动态维护,考虑每次加入 \(a_i\) 统计完当前答案后,就在 \(d\mid a_i\) 的位置加上 \(2^{i-1}\) 的贡献即可。

所以最后的式子就是 \(f(i)=2f(i-1)+\sum\limits_{d\mid a_i} \varphi(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\) 个形如【\([l_i,r_i]\) 中最大值的不为 \(p_{x_i}\)】的排列 \(p\) 的数量。

\(n\le 500,m\le 2\times 10^5\)

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

\[f(l,r)=\sum\limits_{k=l}^r f(l,k-1)f(k+1,r)\binom{r-l}{k-l} \]

加上限制即记个标记 \(t(l,r,k)\),当满足标记时不转移即可,注意转移顺序。标记转移为 \(t(l,r,k)|=t(l+1,r,k)|t(l,r-1,k)\)。时间复杂度 \(O(n^3+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\) 满足 \(a_i<i\),求满足以下条件的排列 \(p\) 的数量:

  • \(p_j>p_i>p_{a_i}(j\in(a_i,i))\)

\(n\le 3\times 10^5\)保证有解

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

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

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

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

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

\[\frac{n!}{\prod\limits_{i=1}^n siz(i)} \]

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

\[\begin{aligned} f(u)&=\binom{siz(v_1)+siz(v_2)}{siz(v_1)}\binom{siz(v_1)+siz(v_2)+siz(v_3)}{siz(v_1)+siz(v_2)}\cdots\binom{siz(u)-1}{siz(v_1)+siz(v_2)+\cdots+siz(v_{x-1})}\prod\limits_{v\in son(u)}f(v)\\ &=\frac{(siz(u)-1)!}{\prod\limits_{v\in son(u)}siz(v)!}\prod\limits_{v\in son(u)}f(v)\\ &=\frac{(siz(u)-1)!}{\prod\limits_{v\in son(u)}siz(v)!}\prod\limits_{v\in son(u)}f(v)\\ \end{aligned}\]

考虑把每个 \((siz(u)-1)!\)\(siz(v)!\) 相抵消,剩下 \(\prod\limits_{i=2}^n\frac{1}{siz(i)}\) 以及 \((siz(1)-1)!\),写得好看点,都乘个 \(siz(1)\),即得上式。

时间复杂度 \(O(n)\)

ARC187B Sum of CC

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

\(n,m\le 2\times 10^3\)

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

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

所以记录前缀 \(\min\) \(p\),后缀 \(\max\) \(q\),前后缀 -1 数量 \(r,s\)。然后枚举每个断点 \(i\) 表示 \(i,i+1\) 不在同一联通块的方案数(贡献显然为多出了一个连通块)。即要求前缀 \(\min>\) 后缀 \(\max\),所以 再枚举前缀 \(\min=j\),前缀的 -1 取值范围为 \([j,m]\),后缀 -1 的取值范围为 \([1,j-1]\),但是这样会算重。记 \(g_1(j)\) 为前缀 \(\min\ge j\) 的方案数 \(=(m-j+1)^{r_i}\)\(g_2(j)\) 为后缀 \(\max\le j\) 的方案数 \(=(j-1)^{s_{i+1}}\),则有 \(g_3(j)=g_2(j)-g_2(j-1)\) 为后缀 \(\max=j\) 的方案数,这样固定一边乘以另一边就不会记重了。统计 \(g_1(j)g_3(j-1)\) 即可。时间复杂度 \(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 @ 2024-11-01 15:19  view3937  阅读(2)  评论(0编辑  收藏  举报
Title