校内模拟赛(2019.9.10)

Posted hale522520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了校内模拟赛(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\n",ans);
    return 0;
View Code

 

以上是关于校内模拟赛(2019.9.10)的主要内容,如果未能解决你的问题,请参考以下文章

校内模拟赛20170604

校内模拟赛(20170917)

校内模拟赛(20170921)

[20180817]校内模拟赛

校内模拟赛(20170922)

httpClient模拟登陆校内某系统