校内模拟赛(一)(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

代码:

#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;
}

第二题就比较有意思了,写题的时候读错题了,上面明明是最短路径上的边数,我却理解为最短路径的总数,真是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    无特殊限制

上面是题面,由于子任务是对称的,于是我们分开考虑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;
}

第三题,考场上本来写得是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.

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

对于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;
}

原文地址:https://www.cnblogs.com/Hale522520/p/11526802.html

时间: 2024-08-29 21:21:48

校内模拟赛(一)(2019.9.10)的相关文章

2017.6.11 校内模拟赛

题面及数据及std(有本人的也有原来的) :2017.6.11 校内模拟赛 T1 自己在纸上模拟一下后就会发现 可以用栈来搞一搞事情 受了上次zsq 讲的双栈排序的启发.. 具体就是将原盘子大小copy一下排个序 用两个指针维护两个数组(原数据 和 排序后的数据), 即分为1数据和2数组 将小于1指针指向的数据的2数组中的数据全部压入栈中 后进行消除, 将栈栈顶元素与当前1数组中的1指针指向的元素进行比较 相同则消除 后重复过程 直至指针超过N 后判断一下是否两个指针都超过了N... #incl

校内模拟赛 Zbq&#39;s Music Challenge

Zbq's Music Challenge 题意: 一个长度为n的序列,每个位置可能是1或者0,1的概率是$p_i$.对于一个序列$S$,它的得分是 $$BasicScore=A\times \sum_{i=1}^{n}{S_i} \tag{1}$$ $$ combo(i)=\left\{ \begin{aligned} &S_i & &i=1 \\ &combo(i-1)+1 & &i\neq 1 ~\mathrm{and}~ S_i=1 \\ &

2019.08.25校内模拟赛Graph

其实这是道很难的容斥. 所以我考场上直接考虑了\(m=0\)的暴力和白给的\(m=\cfrac{n(n-1)}{2}\)的\(10\)分. 白给的那十分是完全图,根据题意就只需要输出\(0\)就行了. 而至于\(m=0\)的\(40pts\),稍加思索就会发现它和错排是双射关系... 于是,就直接错排就好了. 但我当时忘了错排公式是什么了...递推式也没想起来... 于是我就妄想手推容斥形式的错排,但是我死了,没推出来. 于是我就\(10pts\)走人了. 后来在\(wqy\)的指导下推了出来,

2019/8/27 校内模拟赛 考试报告

A.挑战(challenge.cpp) 首先令\(x_i=max(a_i-k,0)\),即破坏第\(i\)个防御区域的代价. 设\(dp_i\)表示从\(1...i\)需要的最小代价,有状态转移方程:\(dp_i=min(dp_j)+x_i\),其中\(i-L\le j\le i-1\). 初始化边界\(dp_0=0,dp_{1...n+1}=inf\).动态规划求出\(dp_{n+1}\)即可. 时间复杂度\(O(n^2)\),可以用ST表/线段树优化到\(O(nlogn)\). B.乌龟(t

[20180816]校内模拟赛

T1 清理(clear) 问题描述 小 C 最近自己开发了一款云盘软件,目前已有??个用户.小C 的云盘上的文件会被后台分成两种类型,活动 文件和非活动文件,活动文件即可能常用的文件,会被放在高速服务器上给用户提供高速下载服务.用户 上传一个文件时,这个文件会被设置为活动文件.由于高速服务器内存大小有限,小 C 需要把一些文件 设为非活动文件,有以下两种设置方式:1.把上传时间前??早的文件全部设为非活动文件:2.把第??个用户上 传的文件全部设为非活动文件.注意这两种方式操作的对象都是所有文件

校内模拟赛T1大美江湖

这就是一个模拟题,注意1234分别对应左右上下横坐标和纵坐标的判断就好了 题解: 需要注意的是,向上取整ceil函数是对于一个double值返回一个double值,也就是说在ceil里面的类型一定要是double,否则会炸 代码: #include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #include<cstring> #include<string>

校内模拟赛:确定小组

  [问题描述] 有n个人坐成一排,这n个人都在某一个小组中,同一个小组的所有人所坐的位置一定是连续的. 有一个记者在现场进行采访,他每次采访都会询问一个人其所在的小组有多少人,被询问的每个人都给出了正确的答案,但是由于时间仓促,记者不一定询问了每个人,我们记录一个长度为n的答案序列,序列的第i个数表示第i个人的回答,如果为0则表示记者没有询问过这个人. 记者发现,对于一些情况,他可以唯一确定这排所有人的分组,而对于另外一些情况则不能,于是记者开始好奇,对于某一个答案序列,他能不能做到这一点,如

校内模拟赛20170604

香蕉锤--双向链表 #include<iostream> #include<cstdio> using namespace std; inline int read(){ int num=0,t=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')t=-1;c=getchar();} while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();} ret

5.13 校内模拟赛

... 果然是dalao们做难题做多了后 简单题水不起来了吗.. 5.13解题报告 300分 T1写了差不多30~40分钟 T2写了不到5min (当时怀疑有坑..) T3推了大概1个多小时的式子, 然后又加上调试差不多一个半小时 时间分配就这样..感觉T2出的太过简单了..直接是个模板 T1 并查集 + 乱搞 T2 快速幂模板 T3 Dp T1 : codevs 2796 最小完全图 二次联通门 : codevs 2796 最小完全图 /* codevs 2796 最小完全图 并查集 + 乱搞