牛客挑战赛30题解

牛客挑战赛30题解

比赛地址

Orz Anson&Deadecho

A

枚举\(b,c\),这样\(a,d\)的限制也就确定了,二维数点即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N=505;
int n,a[N],sum[N][N];long long ans;
int getsum(int x1,int x2,int y1,int y2){
    return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
int main(){
    n=gi();
    for(int i=1;i<=n;++i)a[i]=gi(),++sum[i][a[i]];
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    for(int i=2;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            if(a[i]>a[j])
                ans+=getsum(1,i-1,1,a[j]-1)*getsum(j+1,n,a[i]+1,n);
    printf("%lld\n",ans);return 0;
}

B

好像被我强行水过去了?

考虑一个区间的贡献\(seed^{(l-1)n+r}\),可以拆成\(seed^{(l-1)n}\times seed^r\),所以依然可以考虑枚举每一个数作为中位数,将大于它的视作\(1\)小于它的视作\(-1\),找到所有包含这个数且和为\(0\)\(\pm 1\)的区间(因为要考虑中位数有两个的情况),将左端点与右端点的权值相乘即可得到答案。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N=10005;
const int mod=1e9+7;
int n,seed,a[N],p[N],P[N],tl[N<<1],tr[N<<1],ans;
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;}
int main(){
    n=gi();seed=gi();
    for(int i=1;i<=n;++i)a[i]=gi();
    p[0]=1;p[1]=seed;
    for(int i=2;i<=n;++i)p[i]=1ll*p[i-1]*p[1]%mod;
    P[0]=1;P[1]=p[n];
    for(int i=2;i<=n;++i)P[i]=1ll*P[i-1]*P[1]%mod;
    for(int i=1;i<=n;++i){
        memset(tl,0,sizeof(tl));
        memset(tr,0,sizeof(tr));
        int res=0;
        for(int j=i-1,s=0;j>=1;--j)s+=(a[j]>a[i]?1:-1),add(tl[s+N],P[j-1]);
        for(int j=i+1,s=0;j<=n;++j)s+=(a[j]>a[i]?1:-1),add(tr[s+N],p[j]);
        for(int x=min(i-1,n-i)+1,j=-x;j<=x;++j)
            res=(res+((ll)tl[j+N]+tl[j+N]+tl[j-1+N]+tl[j+1+N])*tr[-j+N])%mod;
        res=(res+((ll)tr[N]+tr[N]+tr[1+N]+tr[-1+N])*P[i-1])%mod;
        res=(res+((ll)tl[N]+tl[N]+tl[1+N]+tl[-1+N])*p[i])%mod;
        res=(res+(ll)P[i-1]*p[i]*2)%mod;
        ans=(ans+(ll)res*a[i])%mod;
    }
    printf("%d\n",ans);
    return 0;
}

C

考虑强制一个点最后一个删,将其作为根节点,这样删点的顺序就一定是从叶子到根,因而可以做一个简单\(dp\)求出方案数,合并子树时方案数乘上组合数即可。

现在要求以每个点为根(每个点最后一个删)的答案。直接换根\(dp\)即可。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N=1e5+5;
const int mod=998244353;
int n,inv[N],jc[N],jcn[N],sz[N],dp[N],ans;vector<int>E[N];
int fastpow(int a,int b){
    int res=1;
    while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
int C(int n,int m){return 1ll*jc[n]*jcn[m]%mod*jcn[n-m]%mod;}
void work(int u,int v){
    sz[u]+=sz[v],dp[u]=1ll*dp[u]*dp[v]%mod*C(sz[u]-1,sz[v])%mod;
}
void rework(int u,int v){
    dp[u]=1ll*dp[u]*fastpow(1ll*dp[v]*C(sz[u]-1,sz[v])%mod,mod-2)%mod,sz[u]-=sz[v];
}
void dfs1(int u,int f){
    sz[u]=dp[u]=1;
    for(int v:E[u])if(v!=f)dfs1(v,u),work(u,v);
}
void dfs2(int u,int f){
    ans=(ans+dp[u])%mod;
    for(int v:E[u])if(v!=f)rework(u,v),work(v,u),dfs2(v,u),rework(v,u),work(u,v);
}
int main(){
    n=gi();inv[1]=jc[0]=jcn[0]=1;
    for(int i=2;i<=n;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
    for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=1ll*jcn[i-1]*inv[i]%mod;
    for(int i=1;i<n;++i){
        int u=gi(),v=gi();
        E[u].push_back(v);E[v].push_back(u);
    }
    dfs1(1,0);dfs2(1,0);printf("%d\n",ans);return 0;
}

D

不难发现答案式为\(\sum_{i=l}^r\binom{i+n-1}{n-1}\binom{s-i+m}{m}\)

这是一个\(n+m\)次多项式然而没办法在\(O(n+m)\)的时间里算出前\(n+m\)项。

考虑其组合意义。相当于从\((0,0)\)点出发走到\((s,n+m)\),每步可以向上走或是向右走,且保证第\(n\)步向上走时横坐标在\([l,r]\)内的方案数。

这个等价于求第\(l\)步向右走时纵坐标在\([0,n-1]\)的方案数减去第\(r+1\)步向右走时纵坐标在\([0,n-1]\)的方案数,而这些是可以做到\(O(n+m)\)计算的。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N=2e7+5;
const int mod=998244353;
int n,m,s,l,r,inv[N],f[N],g[N];
int cal(int p){
    if(p>s)return 0;int res=0;
    //\sum_{i=0}^{n-1}\binom{i+p-1}{i}\binom{n+m-1+s-p}{n+m-i}
    for(int i=f[0]=g[0]=1;i<=n+m;++i){
        f[i]=1ll*f[i-1]*(s-p+i)%mod*inv[i]%mod;
        g[i]=1ll*g[i-1]*(p-1+i)%mod*inv[i]%mod;
    }
    for(int i=0;i<n;++i)res=(res+1ll*f[n+m-i]*g[i])%mod;
    return res;
}
int main(){
    n=gi(),m=gi(),s=gi(),l=gi(),r=gi();inv[1]=1;
    for(int i=2;i<N;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
    printf("%d\n",(cal(l)-cal(r+1)+mod)%mod);return 0;
}

E

不难发现题目要求的就是仙人掌上每一条路径的长度之和。

考虑分别计算每一条边出现在了多少条路径中。对仙人掌建圆方树,然后这个方案数就可以用简单换根\(dp\)求出。注意每经过一个方点(一个环)时方案数要乘\(2\)

对于环边,显然环上的每一条边的计算次数都是相同的,于是在\(dfs\)到方点的同时计算一下答案即可。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
const int N=2e5+5;
int n,tot,m,mod,fa[N],dep[N],dis[N],sum[N],mrk[N],sz[N],ans;
vector<pi>G1[N],G2[N];
void link(int u,int v,int w,vector<pi>*G){
    G[u].push_back(mk(v,w));G[v].push_back(mk(u,w));
}
void dfs(int u,int f){
    fa[u]=f;dep[u]=dep[f]+1;
    for(pi x:G1[u])
        if(x.fi!=f)
            if(!dep[x.fi])dis[x.fi]=(dis[u]+x.se)%mod,dfs(x.fi,u);
            else if(dep[x.fi]>dep[u]){
                sum[++tot]=(x.se+dis[x.first]-dis[u]+mod)%mod;
                for(int v=x.fi;v!=u;v=fa[v])link(tot,v,0,G2),mrk[v]=1;
                link(tot,u,0,G2);
            }
}
void work(int u,int v){
    sz[u]=(sz[u]+(v<=n?1:2)*sz[v])%mod;
}
void undo(int u,int v){
    sz[u]=(sz[u]-(v<=n?1:2)*sz[v]+mod+mod)%mod;
}
void dfs1(int u,int f){
    sz[u]=(u<=n);
    for(pi x:G2[u])if(x.fi!=f)dfs1(x.fi,u),work(u,x.fi);
}
void dfs2(int u,int f){
    if(u>n){
        int res=0,tmp=0;
        for(pi x:G2[u])res=(res+1ll*tmp*sz[x.fi])%mod,tmp=(tmp+sz[x.fi])%mod;
        ans=(ans+1ll*res*sum[u])%mod;
    }
    for(pi x:G2[u])
        if(x.fi!=f){
            undo(u,x.fi);ans=(ans+1ll*sz[u]*sz[x.fi]%mod*x.se)%mod;
            work(x.fi,u);dfs2(x.fi,u);undo(x.fi,u);work(u,x.fi);
        }
}
int main(){
    n=tot=gi();m=gi();mod=gi();
    for(int i=1,x,y,z;i<=m;++i)x=gi(),y=gi(),z=gi(),link(x,y,z,G1);
    dfs(1,0);
    for(int i=2;i<=n;++i)if(!mrk[i])for(pi x:G1[i])if(x.fi==fa[i])link(i,fa[i],x.se,G2);
    dfs1(1,0);dfs2(1,0);printf("%d\n",ans);return 0;
}

F

考虑一下存在二次剩余的数\(x\)满足一些什么样的性质。

\(x^{\frac{p-1}{2}}\equiv1\mod p\)

\(\mod p\)意义下恰有\(\frac{p-1}{2}\)个数存在二次剩余,即恰有\(\frac{p-1}{2}\)个数满足上式。

根据某些代数高论我们可以得到

\(存在二次剩余x^{\frac{p-1}{2}}-1=\prod_{i\mbox{存在二次剩余}}(x-i)\)

而我们要求的东西即为等式右边的\(k\)次方与\(f(x)\)\(\gcd\)

可以先求出\((x^{\frac{p-1}{2}}-1)^k\mod f(x)\)后再与\(f(x)\)\(\gcd\)。复杂度\(O(n^2\log p)\)

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define vi vector<int>
const int mod=998244353;
int inv(int a){
    int res=1,b=mod-2;
    while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void print(vi a){
    int n=a.size();printf("%d\n",n-1);
    for(int i=0;i<n;++i)printf("%d ",a[i]);puts("");
}
vi mul(vi a,vi b){
    int n=a.size(),m=b.size();vi c;c.resize(n+m-1);
    for(int i=0;i<n;++i)
        for(int j=0;j<m;++j)
            c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
    while(c.size()&&!c.back())c.pop_back();return c;
}
vi Mod(vi a,vi b){
    int n=a.size(),m=b.size();
    for(int i=n-1;i>=m-1;--i)
        if(a[i]){
            int t=1ll*(mod-a[i])*inv(b[m-1])%mod;
            for(int j=0;j<m;++j)a[i-j]=(a[i-j]+1ll*b[m-1-j]*t)%mod;
        }
    while(a.size()&&!a.back())a.pop_back();return a;
}
vi fastpow(vi a,int b,vi c){
    vi res;res.push_back(1);
    while(b){
        if(b&1)res=Mod(mul(res,a),c);
        a=Mod(mul(a,a),c);b>>=1;
    }
    return res;
}
vi gcd(vi a,vi b){
    if(!b.size())return a;
    return gcd(b,Mod(a,b));
}
int main(){
    int n=gi(),k=gi();vi f,g;
    for(int i=0;i<=n;++i)f.push_back(gi());
    g.push_back(0);g.push_back(1);
    g=fastpow(g,mod-1>>1,f);
    if(!g.size())g.push_back(mod-1);else g[0]=(g[0]+mod-1)%mod;
    g=fastpow(g,k,f);g=gcd(f,g);
    int t=inv(g[g.size()-1]);
    for(int i=0;i<g.size();++i)g[i]=1ll*g[i]*t%mod;
    print(g);return 0;
}

猜你喜欢

转载自www.cnblogs.com/zhoushuyu/p/10513912.html