zoukankan      html  css  js  c++  java
  • 2019.7.27 NOIP模拟测试9 反思总结

    先来整理题目

    T1题目大意:给出n个数字和一个质数作为模数,一个变量x初始值为1。进行m次操作,每次让x随机乘上n个数中的一个,问m次操作以后x的期望值。

    答案一定可以用分数表示,输出分子乘分母逆元的值。

    数学一直不好,期望这块更是似懂非懂。上来看了一眼数据范围和测试点提示,写了两行骗分走了。

    其实这个骗分我没看全也没理解对,阴差阳错碰对了一个点。正解是原根把矩阵乘法转化成加法,循环矩阵快速幂。【当然没有搞懂,之后去翻别人的博客学习】

    留个坑。

    T2题目大意:给出一棵边权为1的树,每个点有一个权值记作a[i]。对于一个点,其他每个点到这个点的距离乘上那个点的a[i]再求和,记作这个点的重要程度b[i]。

    给出全部点的a[i]或者全部点的b[i],求出没给出的a[i]或b[i]。

    对于给出a[i]求b[i]:一眼换根DP,切了。

    然后出分一看快乐爆零。后来研究研究代码,换根的式子推错了。【后来改的时候又错了一次】

    对于给出b[i]求a[i]:一眼高斯消元,但是其实我没写过高斯消元,折腾了半天放弃调试滚去T3了。

    后来了解到求a[i]正解不是高斯消元。考虑每两个相邻b[i],b[j]之间的差别,会发现以它们为分界,对于b[i]一边的点贡献多1,对于b[j]另一边的点贡献多1。

    尝试用b[i]减去它父亲的b[fa[i]]。先看b[i]为根的子树的a[i]之和siz[i],siz[i]对于b[i]的贡献比对于b[fa[i]]的贡献少1。而看全部a[i]之和sum扣去siz[i]的这一部分,对于b[i]的贡献比对于b[fa[i]]的贡献多1。那么b[i]-b[fa[i]]=-siz[i]+(sum-siz[i])=sum-2*siz[i]。

    这样能够对于每一个非根节点列出n-1个式子。那么对于我们选定的根节点【这里设为1】,发现b[1]=siz[2]+siz[3]+siz[4]+...+siz[n]。b[1]中有2-n所有的siz[i],而之前我们列出的n-1个式子里面各有一个siz[i]。

    那么把所有b[i]-b[fa[i]]加起来,再加上b[1]*2,就是全部a[i]之和sum。

    得到sum以后我们可以考虑从根一路跑到叶节点,把sum代入回式子能得到每一个siz[i],发现叶节点的siz[i]就是a[i]。然后向上处理,用当前节点的siz减去儿子的siz,最后得到的就是a。对于根节点,它没有siz,但是sum1减去所有的siz就是a[1],也可以在同一个dfs里面特判一下解决。

    代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int t,n,opt;
    int ver[200010],Next[200010],head[100010],tot;
    int a[200010];
    long long sum[200010],siz[200010],sum1,b[200010];
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void dfs(int x,int fa){
        siz[x]=a[x];
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(y==fa)continue;
            dfs(y,x);
            siz[x]+=siz[y];
            sum[x]+=sum[y]+siz[y];
        }
    }
    void dfs1(int x,int fa){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(y==fa)continue;
            sum[y]=sum[x]+sum1-2*siz[y];
            dfs1(y,x);
        }
    }
    void dfs2(int x,int fa){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(y==fa)continue;
            sum1+=b[y]-b[x];
            dfs2(y,x);
        }
    }
    void dfs3(int x,int fa){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(y==fa)continue;
            sum[y]=(sum1-(b[y]-b[x]))/2;
            dfs3(y,x);
            siz[x]+=siz[y];
        }
        if(x!=1){
            a[x]=sum[x]-siz[x];
            siz[x]+=a[x];
        }
        else{
            a[x]=sum1-siz[x];
        }
    }
    void clear(){
        sum1=tot=0;
        for(int i=1;i<=n;i++)a[i]=b[i]=sum[i]=siz[i]=head[i]=0;
    }
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&t);
        while(t--){
            scanf("%d",&n);
            for(int i=1,x,y;i<n;i++){
                scanf("%d%d",&x,&y);
                add(x,y),add(y,x);
            }
            scanf("%d",&opt);
            if(opt==0){
                for(int i=1;i<=n;i++){
                    scanf("%d",&a[i]);
                    sum1+=a[i];
                }
                dfs(1,1);
                dfs1(1,1);
                for(int i=1;i<=n;i++)printf("%lld ",sum[i]);
                printf("
    ");
            }
            else{
                for(int i=1;i<=n;i++){
                    scanf("%lld",&b[i]);
                }
                dfs2(1,1);
                sum1+=2*b[1];
                sum1/=(n-1);
                dfs3(1,1);
                for(int i=1;i<=n;i++)printf("%d ",a[i]);
                printf("
    ");
            }
            clear();
        }
        return 0;
    }

    T3题目大意:起点为(0,0),给出步数n,每次可以朝上下左右其中一个方向走一步,问n步的时候停在(0,0)的方案有多少种。

    每次给出四种限制条件中的任意一个:

    没有限制;只能走x轴非负半轴;只能走坐标轴;只能走x、y轴非负半轴以及第一象限。

    考试的时候没什么时间了【在T2的自造高斯消元浪费了很久……】,写只走x轴非负半轴的时候不知道为什么打爆了计算组合数,写只走坐标轴的情况又死活弄不出来,最后愤怒地写了个四种情况通用的三维DP,居然拿了50分,三道题最高分…

    对于没有限制的情况,和前两天的visit这道题思路一样。枚举左右走的步数为i,因为在n步里面选i步左右走【i为偶数】,在i步里面选i/2步朝左走,在n-i步里面朝上走,最后得出式子:

    C(n,i)*C(i,i/2)*C(n-i,(n-i)/2)

    对于只能走x轴非负半轴,显然是一个向左走不能超过向右走,类似于出栈不能多于进栈的问题,即卡特兰数。因为向左走向右走步数一共为n,求cat(n/2)。

    对于只能走x、y轴非负半轴以及第一象限,会发现这里也是限制的向左走不能超过向右走,并且向下走不能超过向上走。枚举左右走的步数i【i为偶数】,同时限制左右和上下。即对于每一个i,求出cat(i/2)*cat((n-i)/2)*C(n,i)

    最后对于只能走坐标轴,看数据范围n<=1000考虑DP。

    DP是从前往后推的,那么设一个ans数组表示走i步回到原点的方案数。对于一个n步的方案,可以拆分成两个过程:从原点走i步,除了第i步中间不再回到原点+随便走n-i步回到原点。前面的这i步的方案数是cat(i/2-1),减一是因为catalan数表示的是可以回到原点的,那么就让这i步中先走一步出原点【之后还要走一步回来】,这样catalan的意义中最多回到走出来的这一个点,满足条件。随便走n-i步回到原点,就是ans[n-i]。枚举所有i【i为偶数】即可做到不重不漏。DP方程:ans[i]=∑ans[j]*car(j/2-1)。

    阶乘和阶乘的逆元要预处理出来,做到O(1)计算catalan数和组合数【我改题的时候智障地先打了个分解质因数,T得比原来的50分还低】。

    到这里四种情况分别讨论完,这题是个四合一问题。

    代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const long long mod=1000000007;
    int n,opt;
    long long rec[200010],inv[200010],f[200010];
    long long ks(long long x,long long k){
        long long num=1;
        while(k){
            if(k&1)num=num%mod*x%mod;
            x=x%mod*x%mod;
            k>>=1;
        }
        return num;
    }
    void work(int x){
        inv[0]=rec[0]=rec[1]=1;
        for(int i=2;i<=x;i++)rec[i]=(rec[i-1]%mod*(long long)i%mod)%mod;
        inv[x]=ks(rec[x],mod-2);
        for(int i=x-1;i>=1;i--)inv[i]=(inv[i+1]%mod*(long long)(i+1)%mod)%mod;
    }
    long long cat(long long x){
        if(x==0)return 1;
        return((rec[2*x]*inv[x])%mod*inv[x+1])%mod;
    }
    int main()
    {
        scanf("%d%d",&n,&opt);
        work(n);
        if(opt==1){
            printf("%lld",cat(n/2));
        }
        else if(opt==2){
            f[0]=1;
            for(int i=2;i<=n;i+=2){
                for(int j=2;j<=i;j+=2){
                    f[i]=(f[i]+f[i-j]%mod*4%mod*cat(j/2-1)%mod)%mod;
                }
            }
            printf("%lld",f[n]);
        }
        else if(opt==3){
            long long ans=0;
            for(int k=0;k<=n;k+=2){
                long long ans1=1;
                ans1=ans1*cat(k/2)%mod;
                ans1=ans1*cat((n-k)/2)%mod;
                ans1=ans1*rec[n]%mod*inv[k]%mod*inv[n-k]%mod;
                ans=(ans+ans1)%mod;
            }
            printf("%lld",ans);
        }
        else{
            long long ans=0;
            for(int k=0;k<=n;k+=2){
                long long ans1=1;
                ans1=ans1*rec[n]%mod*inv[k/2]%mod*inv[k/2]%mod*inv[(n-k)/2]%mod*inv[(n-k)/2]%mod;
                ans=(ans+ans1)%mod;
            }
            printf("%lld",ans);
        }
        return 0;
    }

    反思…想了想,写一篇游记吧。那么就到这里了。

  • 相关阅读:
    Java中的集合Queue
    CountDownTimer
    CountDownTimer
    CountDownTimer
    CountDownTimer
    【10月新版】Aspose.Pdf 10月新版V17.10发布 | 附下载
    【10月新版】Aspose.Pdf 10月新版V17.10发布 | 附下载
    【10月新版】Aspose.Pdf 10月新版V17.10发布 | 附下载
    【10月新版】Aspose.Pdf 10月新版V17.10发布 | 附下载
    什么是区块链 Layer 0 扩容
  • 原文地址:https://www.cnblogs.com/chloris/p/11256661.html
Copyright © 2011-2022 走看看