当前位置 : 主页 > 编程语言 > 其它开发 >

[游记]来自学长的馈赠2-2022.7.21

来源:互联网 收集:自由互联 发布时间:2022-07-22
A. 随 B. 单 C. 题 D. DP搬运工1 再次感谢学长…… A. 随 B. 单 C. 题 D. DP搬运工1 赛时得分:$172/400$ 赛时排行:Rank5 好像有点感冒……不舒服 A. 随 这,又是一道期望鬼题 首先显然地,设 $
A. 随 B. 单 C. 题 D. DP搬运工1

再次感谢学长……

A. 随

B. 单

C. 题

D. DP搬运工1

赛时得分:$172/400$

赛时排行:Rank5

好像有点感冒……不舒服

 

A. 随

这,又是一道期望鬼题

首先显然地,设 $sum=\sum\limits_{i=1}^{n}a_i$

那么我们有 $ans=(\dfrac{sum}{n})^m \operatorname{mod} mod$

设 $ans=\dfrac{p}{q}$ ,那么我们需要输出 $ans$ 关于 $10^9+7$ 的逆元,也就是 $p\times q^{10^9+5} \operatorname{mod} (10^9+7)$

考试的时候推出了一个柿子,但是这个柿子无法取模

于是 $10$ 分走人

然后可以注意到,它给的题解非常离谱

所以我们不使用题解方法(

注意到原式 $sum=\sum\limits_{i=1}^{n}a_i$ 那么我们不用计算 $sum$ 直接把 $sum$ 拆开了算

$ans=(\frac{\sum\limits_{i=1}^{n}a_i}{n})^m \operatorname{mod} mod$

不妨开一个桶 $cnt$ 记录一下每个数出现的次数

 考虑构造一个多项式(鸣谢@artalter @Kamisato_Ayaka 两位大佬对蒟蒻进行了一个题目的讲解

多项式为 $\sum\limits_{i=0}^{mod-1} cnt_ix^i$

这里的 $x$ 没有任何意义,只是为了防止系数合并

那么原式就可以使用一个多项式乘法快速地计算系数

 这种方法的巧妙之处就在于避免了矩阵乘法中大量无用的计算

在统计答案的时候只需要将得出的最终数组加和即可~

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=2010,mod=1e9+7,INF=2147483647;
int n,m,md;
int cnt[WR],sum;
int x[WR],y[WR],z[WR];
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-48;
        ch=getchar();
    }
    return s*w;
}
void mul(int a[],int b[],int ans[],int modu){
    memset(x,0,sizeof(x));
    memset(y,0,sizeof(y));
    memset(z,0,sizeof(z));
    for(int i=0;i<modu;i++) x[i]=a[i],y[i]=b[i];
    for(int i=0;i<modu;i++){
        for(int j=0;j<modu;j++){
            z[(i*j)%modu]=(z[(i*j)%modu]+x[i]*y[j])%mod;
        }
    }
    for(int i=0;i<modu;i++) ans[i]=z[i];
}
int res[WR];
void quick_pow(int b,int modu){
    res[1]=1;
    while(b){
        if(b&1) mul(res,cnt,res,modu);
        mul(cnt,cnt,cnt,modu);
        b>>=1;
    }
}
int qpow(int a,int b,int modu){
    int base=a,res=1;
    while(b){
        if(b&1) res=res*base%modu;
        base=base*base%modu;
        b>>=1;
    }
    return res;
}
signed main(){
    n=read(),m=read(),md=read();
    for(int i=1;i<=n;i++){
        int x=read();
        cnt[x%md]++;
    }
    int ny=qpow(n,m,mod);
    ny=qpow(ny,mod-2,mod);
    int ans=0;
    quick_pow(m,md);
    for(int i=0;i<md;i++){
        ans=(ans+res[i]*i)%mod;
    }
    printf("%lld",ans*ny%mod);
    return 0;
}
View Code

 

关于原根的解法:

 并没有要求涉及,可以参考 fengwu大佬的博客

当然我会写的!应该会吧……

B. 单

个人认为 $30$ 分的 $t=0$ 和前面几个点的高斯消元是比较好写的

但我的高消炸了于是喜提 $40$ 分……

首先考虑已知 $a$ 数组求 $b$ 数组

那么首先可以一遍 $\operatorname{DFS}$ 求出所有点到根节点(我设为 $1$ )的深度 $dpt$ 

还可以求出子树的节点权值和 $sze$ ,顺手求出 $b[1]=\sum\limits_{i=2}^{n}a[i]\times dpt[i]$

特别注意,如果深度是从 $1$ 开始计算(根节点深度是 $1$ )要在计算 $b[i]$ 时将 $dpt$ 减去 $1$

接着考虑如何求出 $b_i$ 

不妨设当前点为 $u$ ,父节点为 $rt$ ,$u$ 的子树集合为 $U$

$u$ 就相当于 $rt$ 移动了一条边的距离,可以利用换根的思想

那么 $b[u] = b[rt] - \left\vert U \right\vert \sum\limits_{i\in U}a[i] + (n-\left\vert U \right\vert)\sum\limits_{i\notin U}a[i]$

化简得到 $b[u]=b[rt]+sze[1]-2*sze[u]$

这样我们就解决了第一部分

观察第二部分

对于上式移项,可以发现 $b[u]-b[rt]=sze[1]-2*sze[u]$

然后可以求出 $a[1]$,从而差分求出 $a[i]$

题解写得比较详细了

 

 

 

 

 

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=2010000,mod=1e9+7,INF=2147483647;
const double eps=1e-6;
struct Edge{
    int pre,to;
}edge[WR];
int head[WR],tot;
int n;
int a[WR],b[WR];
int mns[WR];
int fa[WR],son[WR],sze[WR],dpt[WR];
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-48;
        ch=getchar();
    }
    return s*w;
}
void add(int u,int v){
    edge[++tot].pre=head[u];
    edge[tot].to=v;
    head[u]=tot;
}
void dfs1(int u,int root){
    fa[u]=root,dpt[u]=dpt[root]+1;
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v==root) continue;
        dfs1(v,u);
        sze[u]+=sze[v];
        if(sze[v]>sze[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int root){
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v==root) continue;
        b[v]=b[u]+sze[1]-2*sze[v];
        dfs2(v,u);
    }
}
void dfs3(int u,int root){
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v==root) continue;
        mns[v]=b[v]-b[u];
        dfs3(v,u);
    }
}
void dfs4(int u,int root){
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v==root) continue;
        a[v]=sze[v]=(sze[1]-mns[v])/2;
        a[u]-=sze[v];
        dfs4(v,u);
    }
}
signed main(){
    int t=read();
    while(t--){
        n=read();tot=0;
        for(int i=1;i<=n;i++) head[i]=sze[i]=0;
        for(int i=1;i<n;i++){
            int u=read(),v=read();
            add(u,v);add(v,u);
        }
        int opt=read();
        if(opt==0){
            for(int i=1;i<=n;i++) a[i]=sze[i]=read(),b[i]=0;
            dfs1(1,0);
            for(int i=1;i<=n;i++) b[1]+=(dpt[i]-1)*a[i];
            dfs2(1,0);
            for(int i=1;i<=n;i++) printf("%lld ",b[i]);
            printf("\n");
        }else{
            for(int i=1;i<=n;i++) b[i]=read();
            dfs3(1,0);
            int sum=0;
            for(int i=1;i<=n;i++) sum+=mns[i];
            sze[1]=(sum+b[1]*2)/(n-1);
            dfs4(1,0);
            a[1]=sze[1];
            for(int i=2;i<=n;i++) a[1]-=a[i];
            for(int i=1;i<=n;i++) printf("%lld ",a[i]);
            printf("\n");
        }
    }
    return 0;
}
View Code

 

C. 题

组合数大杂烩

首先考虑到 $typ=0$ 的情况

typ=0

$\quad$ 不妨设横向一共走了 $2i$ 步(需要返回)

$\quad$ 那么纵向就一共走了 $n-2i$ 步

$\quad$ 考虑到横着走的 $2i$ 步是从 $n$ 步里随机挑出来的,所以答案中肯定有一个 $C_n^{2i}$ 的因子

$\quad$ 发现横着走的步数中有向正方向走的有向逆方向走的,因此考虑选择向正方向走的步数(共 $i$ 步)即有 $C_{2i}^i$ 种选法

$\quad$ 同理对于纵向移动共有 $C_{n-2i}^{\frac{2}{n-2i}}$ 种走法

typ=1

$\quad$ 这个有点意思,可以手模一下几组小样例

$\quad$ 发现对于输入的 $n$ ,答案就是卡特兰数的第 $\frac{n}{2}$ 项

$\quad$ 也就是 $\dfrac{C_{n}^{\frac{n}{2}}}{\frac{n}{2}+1}$

$\quad$ 补充一下卡特兰数的知识:$Catalan(i)=\dfrac{C_{2i}^{i}}{i+1}$

$\quad$ 硬算即可

typ=2

$\quad$ 设 $dp[i]$ 表示走了 $i$ 步回到原点的方案数,枚举第一次回到原点时走过的步数 $j$ (为了存在合法解, $j$ 为偶数)

$\quad$ 则此时方案数为 $4*f[i-j]*Catalan(j/2-1)$

$\quad$ 为什么还要减一呢?智慧的学长gjk(pl_er)慷慨地解答了我的问题

$\quad$ 不妨设只在横轴上移动,设向正方向走为 $($ ,向反方向走为 $)$

$\quad$ 那么可以发现整个过程可以被一个括号序列模拟出来

$\quad$ 考虑如下的序列 $(())(())$ 我们在行走中间也经过了一次原点

$\quad$ 但是这个答案在 $(())$ 时已经被记录了,也就是说会有重复记录的情况

$\quad$ 所以我们减去一的意义就是在括号序列两端人为地加入一对括号,这样可以保证在一次 $dp$ 内不会有在中途回到原点的情况

$\quad$ 然后对于所有方向,乘四

typ=3

$\quad$ 像 $typ0$ 一样枚举横向步数,又因为只能向一个方向走,所以像 $typ1$ 一样用卡特兰数计算纵向步数

 

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=2010000,mod=1e9+7,INF=2147483647;
int n,typ,ans;
int g[WR],ny[WR];
int dp[210][210][210];
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-48;
        ch=getchar();
    }
    return s*w;
}
int quick_pow(int a,int b){
    int base=a,res=1;
    while(b){
        if(b&1) res=res*base%mod;
        base=base*base%mod;
        b>>=1;
    }
    return res;
}
int comb(int n,int m){
    if(m>n) return 0;
    if(n==m) return 1;
    return g[n]*ny[m]%mod*ny[n-m]%mod;
}
int Catalan(int n){
    return quick_pow(n+1,mod-2)*comb(n<<1,n)%mod;
    //不是ny[n+1]!!!
}
signed main(){
    int n=read(),typ=read();
    g[0]=ny[0]=1;
    for(int i=1;i<=(n<<1);i++) g[i]=g[i-1]*i%mod;
    ny[n<<1]=quick_pow(g[n<<1],mod-2);
    for(int i=(n<<1)-1;i>=1;i--) ny[i]=ny[i+1]*(i+1)%mod;
    if(typ==1) printf("%lld",Catalan(n>>1));
    else if(typ==2){
        int dp[1050];
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(int i=0;i<=n;i+=2){
            for(int j=0;j<=i;j+=2){
                dp[i]=(dp[i]+(dp[i-j]<<2)%mod*Catalan(j/2-1)%mod)%mod;
            }
        }
        printf("%lld",dp[n]);
    }else if(typ==0){
        for(int i=0;i<=(n>>1);i++){
            ans=(ans+comb(n,i<<1)*comb(i<<1,i)%mod*comb((n-(i<<1)),(n-(i<<1))>>1)%mod)%mod;
        }
        printf("%lld",ans);
    }else{
        for(int i=0;i<=(n>>1);i++){
            ans=(ans+comb(n,i<<1)*Catalan(i)%mod*Catalan((n-(i<<1))>>1)%mod)%mod;
        }
        printf("%lld",ans);
    }
    return 0;
}
View Code

 

(前面三道题目好像是liu_runda学长出的%%%)

 

D. DP搬运工1

 

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=2010000,mod=998244353,INF=2147483647;
int n,K;
int dp[51][51][2550];
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-48;
        ch=getchar();
    }
    return s*w;
}
signed main(){
    n=read(),K=read();
    dp[1][0][0]=1;
    for(int i=2;i<=n;i++){
        for(int j=0;j<=n-i+1;j++){
            for(int k=0;k<=K;k++){
                if(dp[i-1][j][k]==0) continue;
                if(j!=0){
                    dp[i][j-1][k+(i<<1)]=(dp[i][j-1][k+(i<<1)]+dp[i-1][j][k]*j%mod)%mod;
                    dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*(j<<1)%mod)%mod;
                    dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*j%mod)%mod;
                }
                dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*2%mod)%mod;
                dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*2%mod)%mod;
            }
        }
    }
    int ans=0;
    for(int i=0;i<=K;i++){
        ans=(ans+dp[n][0][i])%mod;
    }
    printf("%lld",ans);
    return 0;
}
View Code

 

【文章原创作者:武汉网站制作公司 http://www.wh5w.com提供,感恩】

上一篇:[译] Swift 编译器性能
下一篇:没有了
网友评论