zoukankan      html  css  js  c++  java
  • 校内模拟赛(一)(2019.9.10)

    第一道题,很zz的题,不多赘述

    简(simple)
    【题目描述】
    大道至简.这就是出题人没有写题目背景的原因.
    给出2n个数字,将它们划分成n组,每组的得分为这一组中两个数字的较小值.
    求最大得分.
    【输入格式】
    第一行一个整数n表示正整数的数目. 
    接下来一行2n个空格隔开的整数a1,a2…a2n
    【输出格式】
    一行一个整数表示最大得分.
    【样例输入】
    2
    1 3 1 2
    【样例输出】
    3
    【数据范围】
    对于10%的数据:n=2
    对于另外20%的数据n<=7
    对于另外20%的数据:n<=1000
    对于另外20%的数据:ai<=100
    对于100%的数据: n<=100000,1<=ai<=10^9
    View Code

    代码:

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int N=2e5+7;
    LL m,n,k,p,ans;
    LL a[N];
    int main()
    {
        freopen("simple.in","r",stdin);
        freopen("simple.out","w",stdout);
        scanf("%lld",&n);
        for (int i=1;i<=2*n;i++) scanf("%lld",&a[i]);
        sort(a+1,a+2*n+1);
        for (int i=n;i>=1;i--) ans+=min(a[i*2],a[i*2-1]);
        cout<<ans<<endl;
        return 0;
    }
    View Code

    第二题就比较有意思了,写题的时候读错题了,上面明明是最短路径上的边数,我却理解为最短路径的总数,真是zz。。。

    单(single)
    【题目描述】
        单车联通大街小巷.这就是出题人没有写题目背景的原因.
    对于一棵树,认为每条边长度为1,每个点有一个权值a[i].dis(u,v)为点u到v的最短路径的边数.dis(u,u)=0.对每个点求出一个重要程度.点x的重要程度b[x]定义为其他点到这个点的距离乘上对应的点权再求和.        即:b[x]=a[1]*dis(1,x)+a[2]*dis(2,x)+....+a[n]*dis(n,x)
    现在有很多树和对应的a数组,并求出了b数组.不幸的是,记录变得模糊不清了.幸运的是,树的形态完好地保存了下来,a数组和b数组至少有一个是完好无损的,但另一个数组完全看不清了.
    希望你求出受损的数组.多组数据.
    【输入格式】
    第一行输入一个T,表示数据组数。接下来T组数据。
    每组数据的第1行1个整数n表示树的点数.节点从1到n编号.
    接下来n-1行每行两个整数u,v表示u和v之间有一条边.
    接下来一行一个整数t,表示接下来数组的类型。
    t=0则下一行是a数组,t=1则下一行是b数组。
    接下来一行n个整数,表示保存完好的那个数组,第i个数表示a[i]或b[i]。
    【输出格式】
    T行,每组数据输出一行表示对应的a数组或b数组,数组的相邻元素用一个空格隔开。忽略行末空格和行尾回车.
    【样例输入】
    2
    2
    1 2
    1
    17 31
    2
    1 2
    0
    31 17
    【样例输出】
    31 17
    17 31
    【数据范围】
    对于100%的数据,T=5,2<=n<=100000,1<=u,v<=n,保证给出的n-1条边形成一棵树
    对于100%的数据,t=0或t=1,1<=a[i]<=100,1<=b[i]<=10^9,t=1时保证给出的b数组对应唯一的一个a数组。
    对于100%的数据,单个输入文件不会包含超过2000000个整数,这段话可以理解为,你不必考虑输入输出对程序运行时间的影响。
    对于100%的数据,保证答案不会超过int能表示的范围
    接下来的表格中描述了每个测试点的具体特征。每个测试点的5组数据均符合表格中对应的特征。
    测试点编号    n    特殊限制
    1    <=1000    均有t=0
    2    <=5    均有t=1,答案中a[i]<=20
    3    <=100    均有t=1
    4    <=100    均有t=1
    5    <=30000    所有边满足v=u+1
    6    <=10^5    均有t=0
    7    <=10^5    均有t=0
    8    <=10^5    无特殊限制
    9    <=10^5    无特殊限制
    10    <=10^5    无特殊限制
    View Code

    上面是题面,由于子任务是对称的,于是我们分开考虑t=1和t=0的情况

    首先先说t=0的情况吧:

    我们考虑从他的父亲节点跑到子节点对于子节点的贡献,通过手动作差发现,其结果的改变值仅为这个边左右两个子树的a数组的差

    由此我们可以先处理出来每个边左右的差值,然后钦定1为根节点,算出1的答案,然后就可以对所有节点进行转移了,最后输出即可

    之后是t=1的情况:

    我们沿用上面的思考方式,即考虑单边对于全局的影响,我们发现我们一些性质是可以从链上转移到树上的

    我们先考虑一条链的情况,这里我将其转换为一个矩阵进行考虑,发现可以直接对于b数组在父节点与子节点之间作差之后

    钦定1为根节点,那么a数组的总和即(所有b数组的差值加上2*b[1])/(n-1),可以用矩阵证明得出。

    然后我们重新考虑作差之后得出了什么,不难理解得到,这个sum(即a数组的总和)同样也可以在树上转移的,在此不再赘述

    参考代码(有一点卡常,所有加了快读快写):

    #include<bits/stdc++.h>
    #define LL long long
    #define I inline
    using namespace std;
    const int N=4e5+7;
    struct edge
    {
        int nx,to;
    } e[N];
    int m,n,ans,cnt;
    int a[N],b[N],head[N],ed[N],T;
    LL sum;
    void add_edge(int a,int b)
    {
        cnt++;e[cnt].nx=head[a];e[cnt].to=b;head[a]=cnt;
    }
    inline void write(int x)
    {
        if(x<0){putchar('-');x=-x;}
        if(x>9) write(x/10);
        putchar(x%10+'0');
    }
    I int read()
    {
        int x=0,f=1;char ch=getchar();
        while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
        while (ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    I void dfs1(int x,int fa)
    {
        ed[x]=a[x];
        for (int i=head[x];i;i=e[i].nx)
        {
            int y=e[i].to;
            if (y==fa) continue;
            dfs1(y,x);
            ed[x]+=ed[y];
        }
    }
    I void dfs2(int x,int fa)
    {
        for (int i=head[x];i;i=e[i].nx)
        {
            int y=e[i].to;
            if (y==fa) continue;
            sum+=b[y]-b[x];
            dfs2(y,x);
        }
    }
    I void calc(int x,int dep,int fa)
    {
        ans+=dep*a[x];
        for (int i=head[x];i;i=e[i].nx)
        {
            int y=e[i].to;
            if (y==fa) continue;
            calc(y,dep+1,x);
        }
    }
    I void build(int x,int fa)
    {
        for (int i=head[x];i;i=e[i].nx)
        {
            int y=e[i].to;
            if (y==fa) continue;
            b[y]=b[x]-ed[y];
            build(y,x);
        }
    }
    I void build2(int x,int fa)
    {
        a[x]=ed[x];
        for (int i=head[x];i;i=e[i].nx)
        {
            int y=e[i].to;
            if (y==fa) continue;
            ed[y]=(sum-b[y]+b[x])/2;
            a[x]-=ed[y];
            build2(y,x);
        }
    }
    void solve()
    {    
        LL s=0;memset(ed,0,sizeof(ed));
        memset(b,0,sizeof(b));
        for (int i=1;i<=n;i++) a[i]=read(),s+=a[i];
        dfs1(1,0);
        for (int i=1;i<=n;i++) ed[i]=ed[i]-(s-ed[i]);
        calc(1,0,1);b[1]=ans;
        build(1,0);
        for (int i=1;i<=n;i++) write(b[i]),putchar(' ');
        cout<<endl;
    }
    void solve2()
    {
        sum=0;memset(a,0,sizeof(a));
        for (int i=1;i<=n;i++) b[i]=read();
        dfs2(1,0);
        sum+=b[1]*2;sum/=n-1;ed[1]=sum;
        build2(1,0);
        for (int i=1;i<=n;i++) write(a[i]),putchar(' ');
        cout<<endl;
    }
    int main()
    {
        freopen("single.in","r",stdin);
        freopen("single.out","w",stdout);
        T=read();
        while (T--)
        {
            n=read();memset(head,0,sizeof(head));cnt=0;sum=0;
            ans=0;
            for (int i=1;i<n;i++) 
            {
                int x=read(),y=read();
                add_edge(x,y);add_edge(y,x);
            }
            int opt=read();
            if (opt==0) solve();
            else solve2();
        }
        return 0;
    }
    View Code

    第三题,考场上本来写得是n3的DP,以为自己能过60,结果挂成了40,退役预定

    题(problem)
    【题目描述】
    出个题就好了.这就是出题人没有写题目背景的原因.
    你在平面直角坐标系上.
    你一开始位于(0,0).
    每次可以在上/下/左/右四个方向中选一个走一步.
    即:从(x,y)走到(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置中的其中一个.
    允许你走的步数已经确定为n.现在你想走n步之后回到(0,0).但这太简单了.你希望知道有多少种不同的方案能够使你在n步之后回到(0,0).当且仅当两种方案至少有一步走的方向不同,这两种方案被认为是不同的.
    答案可能很大所以只需要输出答案对10^9+7取模后的结果.(10^9+7=1000000007,1和7之间有8个0)
    这还是太简单了,所以你给能够到达的格点加上了一些限制.一共有三种限制,加上没有限制的情况,一共有四种情况,用0,1,2,3标号:
    0.没有任何限制,可以到达坐标系上所有的点,即能到达的点集为{(x,y)|x,y为整数}
    1.只允许到达x轴非负半轴上的点.即能到达的点集为{(x,y)|x为非负数,y=0}
    2.只允许到达坐标轴上的点.即能到达的点集为{(x,y)|x=0或y=0}
    3.只允许到达x轴非负半轴上的点,y轴非负半轴上的点以及第1象限的点.即能到达的点集为{(x,y)|x>=0,y>=0}
    【输入格式】
    一行两个整数(空格隔开)n和typ,分别表示你必须恰好走的步数和限制的种类.typ的含义见【题目描述】.
    【输出格式】
    一行一个整数ans,表示不同的方案数对10^9+7取模后的结果.
    【样例输入0】
    100 0
    【样例输出0】
    383726909
    【样例输入1】
    100 1
    【样例输出1】
    265470434
    【样例输入2】
    100 2
    【样例输出2】
    376611634
    【样例输入3】
    100 3
    【样例输出3】
    627595255
    【数据范围】
    10%的数据,typ=0,n<=100
    10%的数据,typ=0,n<=1000
    5%的数据, typ=0,n<=100000
    10%的数据,typ=1,n<=100
    10%的数据,typ=1,n<=1000
    5%的数据, typ=1,n<=100000
    10%的数据,typ=2,n<=100
    15%的数据,typ=2,n<=1000
    10%的数据,typ=3,n<=100
    10%的数据,typ=3,n<=1000
    5%的数据, typ=3,n<=100000
    以上11部分数据没有交集.
    100%的数据,保证n为偶数,2<=n<=100000,0<=typ<=3.
    View Code

    同样的,我们考虑每一个子任务:

    对于opt=0的情况,我们只需要枚举在x轴方向上走了多少步,y轴上走了多少步,直接组合数一搞即可

    对于opt=1的情况,很自然的想到,这是一个经典的catalan数模型

    对于opt=2的情况,事情变得棘手了,我们需要考虑的问题便多了,我们用dp[i]表示用i步之后可以回到原点,那么只需要考虑四个方向的单独情况,直接catalan数累加即可

    对于opt=3的情况,我们同样按照,枚举x轴,y轴方向上的步数,组合数和catalan数乘出来即可

    #include<bits/stdc++.h>
    using namespace std;
    #define LL long long
    const int N=1e5+7;
    const int Mod=1e9+7;
    int n,opt;
    LL inv[N],s[N],dp[N];
    LL ans;
    LL pown(LL a)
    {
        LL b=Mod-2,res=1;
        while (b)
        {
            if (b&1) res=res*a%Mod;
            a=a*a%Mod;b>>=1;
        }
        return res;
    }
    void init()
    {
        s[0]=1;
        for (int i=1;i<=n;i++) s[i]=s[i-1]*i%Mod;
        inv[n]=pown(s[n]);
        for (int i=n;i>=1;i--) inv[i-1]=inv[i]*i%Mod;
    }
    LL C(LL a,LL b)
    {
        return s[a]*inv[b]%Mod*inv[a-b]%Mod;
    }
    LL catalan(int p)
    {
        return C(2*p,p)*pown(p+1)%Mod;
    }
    LL solve0()
    {
        LL ans=0;
        for (int i=0;i<=n;i+=2)
        ans=(ans+C(n,i)*C(i,i/2)%Mod*C(n-i,(n-i)/2)%Mod)%Mod;
        return ans;
    }
    LL solve1()
    {
        LL ans=catalan(n/2);
        return ans;
    }
    LL solve2()
    {
        LL ans;dp[0]=1;dp[2]=4;
        for (int i=4;i<=n;i+=2)
        {
            for (int j=2;j<=i;j+=2)
                dp[i]=(dp[i]+catalan(j/2-1)*4%Mod*dp[i-j]%Mod)%Mod;
        }
        return dp[n];
    }
    LL solve3()
    {
        LL ans=0;
        for (int i=0;i<=n;i+=2)
        ans=(ans+C(n,i)*catalan(i/2)%Mod*catalan((n-i)/2))%Mod;
        return ans;
    }
    int main()
    {
        freopen("problem.in","r",stdin);
        freopen("problem.out","w",stdout);
        scanf("%d%d",&n,&opt);
        init();
        if (opt==0) ans=solve0();
        if (opt==1) ans=solve1();
        if (opt==2) ans=solve2();
        if (opt==3) ans=solve3();
        printf("%lld
    ",ans);
        return 0;
    }
    View Code
    慢即是快,细则是能,于小处铸迤逦
  • 相关阅读:
    Linux下使用wireshark权限问题
    Ubuntu关机出错卡死 PCIe Bus Error: severity=Corrected, type=Physical Layer, id=00e5(Receiver ID)
    东南大学《操作系统》课程作业 第二章
    东南大学《操作系统》课程作业 第一章
    东南大学《操作系统》课程作业 第三章
    回溯算法
    拓扑排序之课程表问题
    C++虚函数多态
    JMETER安装及基本使用
    JMETER正则表达式提取器使用
  • 原文地址:https://www.cnblogs.com/Hale522520/p/11526802.html
Copyright © 2011-2022 走看看