第三届全国大学生算法设计与编程挑战赛 (冬季赛)部分题解

部分题解

  • 树的果实
    • 题目描述
    • 题目思路及代码
  • Error
    • 题目描述
    • 题目思路及代码
  • 吃利息
    • 题目描述
    • 题目思路及代码
  • MP4
    • 题目描述
    • 题目思路及代码
  • 展览
    • 题目描述
    • 题目思路及代码
  • 礼物
    • 题目描述
    • 题目思路及代码
  • 看错题
    • 题目描述
    • 题目思路及代码
  • 结语

来自弱校选手的个人题解,题目难度跨越较大,最简单的是c语言期末考试难度,最难的是区域赛金牌难度,题目质量不错,当作练习

树的果实

题目描述

Description

题目背景
小Y同学是一个高中信息学奥赛里面的蒟蒻,在各位大佬暴切各种dark火题的时候,他还在瑟瑟发抖的学习各种基础知识,在巩固很久的基础知识之后,他终于尝试学习且粗略学懂了他之前一直觉得很高大上的树链剖分,于是他准备尝试去写一写模板题。他点开了做题网站,看到了讨论版一句“萌新学妹刚学oi一个月,树链剖分打错了,求大佬看看”,于是小Y同学立即正义感爆棚点进了这一道题。

题目描述
你梦见了一棵树,这是一棵很茂密的树,因此它有很多的分支。

你注意到这颗树的有 nn 个果实,每一棵果实都有自己的编号,且标号为 11 的果实在最上面,像是一个根节点,树上的一个果实 uu 到另一个果实 vv 的距离,都恰好是一个整数 cc ,因为已经固定好了 11 号果实为根节点,所以这棵树的形状已经确定了,你想知道摘下一颗果实,会连带着把它的子树的果实也给摘下来。

而这个摘下来所得到的贡献为(数字出现的次数*数字)的平方

比如2出现了5次,那么贡献即为(2*5)^2(25) 
2
 
数字为两个果实之间的距离即树的边权值,边权值的范围为 cc 。

所以你有m组询问,想知道当前询问的果实连带着它的子树果实被摘下来时的贡献是多少。


Input
第11行,三个整数n,m,cn,m,c分别表示树的大小,询问的个数,边权的范围。(1 \leq n,m,c \leq 100000)(1≤n,m,c≤100000)2-n2−n行,每行三个整数u,v,viu,v,vi表示从uu到vv有一条vivi边权的边。

接下来mm行,每行一个整数表示询问的节点。


Output
输出mm行,每行一个整数代表子树的权值大小。(保证不会超过long long)


Sample Input 1 

11 6 10
1 2 9
2 3 1
3 4 6
2 5 7
4 6 5
5 7 7
7 8 8
7 9 3
7 10 6
3 11 3
5
7
10
6
1
5
Sample Output 1

158
109
0
0
547
158

题目思路及代码

思路:题目已经指出用树链剖分转化为区间问题,但是不会用线段树维护这种问题!!!题解中是用莫队(大一学的这个几乎没用到,所以忘了),莫队是维护一个区间内每个数的个数,完全模板

代码待补!!!

Error

题目描述

第三届全国大学生算法设计与编程挑战赛 (冬季赛)部分题解_第1张图片

题目思路及代码

思路:通过不断枚举最小误差,是否能够构造合法结果为最终的评判函数

#include
using namespace std;
typedef long long int ll;
const ll maxn=1e5+10;
const ll mod=1e9+7;
ll n;
ll a[maxn],b[maxn];
ll l,r;
ll check(ll x){
    memset(b,0,sizeof(b));
    b[1]=a[1]-x;
    if(b[1]<=0){
        if(1<=(a[1]+x)){
            b[1]=1;
        }else{
            return 0;
        }
    }
    for(int i=2;i<=n;i++){
        if((a[i]-x)>b[i-1]){
            b[i]=a[i]-x;
        }else if((a[i]+x)>b[i-1]){
            b[i]=b[i-1]+1;
        }else{
            return 0;
        }
    }
    return 1;

}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    l=0,r=1e9;
    while(l<r){
        ll mid=(l+r)>>1;
        if(check(mid)){
            r=mid;
        }else{
            l=mid+1;
        }
    }
    cout<<l;
	return 0;
}

吃利息

题目描述

Description

小Y同学在上了大学之后觉得自己解放了,于是他想要尝试玩各种游戏,但是慢慢的他发现steam的游戏他都玩不来,没有办法于是他又回归了从小玩到大的英雄联盟,在经历了日日夜夜的单排之后,小Y同学觉得来下棋,但是他之前一直不知道吃利息这个操作,所以他一直卡在铂金上不去,某一天室友告诉了他之后,他才知道要吃利息,现在小Y同学决定来问问你要怎么算利息。

我们先在来简化一下规则,不考虑连胜或者连败的经济和其他因素的影响,比如海克斯的利滚利等。

我们只考虑,每回合结束后可以多5金币,每10块钱可以多1块钱(最多只考虑50块钱吃5块钱利息)。先计算利息,再加上多的5金币

小Y想知道平稳的情况下怎么安排利息最合理,所以他想问你,假设知道开局给定的初始金币总量,n轮以后有多少金币。


Input
两个整数k,nk,n,第一个整数k(0≤k≤1000)表示初始的金币量,第二个整数n(1≤n≤100000)表示要经过多少轮

Output
一个整数,代表最后的金币量

Sample Input 1 

10 2
Sample Output 1

22

题目思路及代码

签到题目

#include
using namespace std;
typedef long long int ll;
const ll maxn=1e5+10;
const ll mod=1e9+7;
ll n,k;
int main()
{
    cin>>n>>k;
    while(k--){
        ll t=n/10;
        if(t>5){
            t=5;
        }
        n=n+t+5;
    }
    cout<<n;
	return 0;
}

MP4

题目描述

Description

Yassin 最近在整理他的 64g 的来自没啥用科技发布的 Uphone11pro 的内存。

由于手头资金紧张,一直没有机会换手机,使用三年的数据量已经将他的手机内存活活填满,以至于他每天早上一起床都有人关心 —— “您的 Uphone 储存空间已满,请释放空间”

Yassin 准备着手开始清理他。

首先映入眼帘的是 techat 和 pilipili,考虑到他每天的使用频率,这两个 app 无法被清理。

接下来是照片,由于 uphone 的奇妙设计,即使已经备份也不能在本机删除,这一部分也不再能再清理。

在下面是文件,他打开文件文件夹,被震惊了

这里足足有 nn 个视频文件!分别命名 1.mp4,2.mp4,...,n.mp4

现在 Yassin 准备向他们动手了!他要将他们从小到大按字典序排序,并且删除掉其中的前 50 个,现在请你告诉他,按照字典序排序的前 50 个分别是谁呢?


Input
一行一个数字 nn, 表示存在的视频个数


Output
一共 50 行,每行一个字符串,分别为字典序第 ii 小的视频文件名,注意,这个文件名里需要包含 .mp4

题目思路及代码

思路:首先能想到的自然是构造全部,然后排序,但是通过思考发现只需要构造前50个即可

#include
using namespace std;
typedef long long int ll;
const ll maxn=1e5+10;
const ll mod=1e9+7;
string s[maxn],t;
ll n;
ll b[100];
ll cnt=1;
int main()
{
    cin>>n;
    if(n<=200){
        for(int i=1;i<=n;i++){
            string t=to_string(i);
            s[i]=t+".mp4";
        }
        sort(s+1,s+1+n);
        for(int i=1;i<=50;i++){
            cout<<s[i]<<endl;
        }
    }else{
        ll cn=1;
        ll sum=0;
        while(cn<=n){
            cn=cn*10;
            sum++;
        }
        for(int i=1;i<=sum;i++){
            b[i]=pow(10,i-1);
        }
        ll cnt=sum;
        for(int i=sum+1;i<=50;i++){
            b[i]=b[i-1]+1;
            if(b[i]>n){
                cnt--;
                b[i]=b[cnt]+1;
                continue;
            }
            if(b[i]%10==0){
                b[i]=b[i]/10;
                b[i+1]=b[i]*10;
                i++;
            }
        }
        for(int i=1;i<=50;i++){
            cout<<b[i]<<".mp4"<<endl;
        }
    }
	return 0;
}

展览

题目描述

Description

题目背景
Y市是一座奇妙的城市,它一开始并不富裕,在人们到来之前,这里全是片荒凉的沙漠,为了抵御沙尘暴,人们开始尝试种植各种各样的树木,在年复一年的努力后,这个城市终于成为了一片绿洲。在树木很茂盛之后,人们开始不止追求绿油油的一片,所以他们开始尝试种植各种各样的花朵,来让自己的审美变得更加舒服,终于年复一年之后,Y市以花朵闻名。

题目描述
Y市每年都会举行花节,来展览独有的美丽的花,但是每朵花都有自己的特性,比如两朵花放在一起,发的香味就会受影响,亦或是不同色调的花放一起,就很影响大家的视觉美感。假设我们简化问题,对于每一朵花,有它独自的美感为x_ix 
i
​
 ,如果两朵花摆在一起,它们的美感度就是异或的,现在有nn朵花,展览馆的人先问你,选取任意数量的花,摆在一起能得到的最大值是多少。

本题为程序填空题,已在提交框提供c++、java、python3的代码模板(切换语言时点击返回默认代码设置)


Input
第一行一个数 n(1 \leq n \leq 50)n(1≤n≤50),表示元素个数

接下来一行 nn 个数,数值为 02^{50}-102 
501

Output
仅一行,表示答案。


Sample Input 1 

4
13 4 12 15
Sample Output 1

15

题目思路及代码

思路:线性基算法模板,通过注释进行构造代码

#include
#define ll long long
using namespace std;
const ll N=1e6+5;
ll n,b[N],a[N],ans;
//b[i]表示二进制下的第i位
void update(ll x){
	for(ll i=60;i>=0;i--){
		if( x&(1ll<<i)){//如果x在二进制表示下含有第i位
			if(b[i])x^=b[i];//如果b[i]存在则让x^b[i],
			//因为之前b[i]也是由已经保存过的a[]数组贡献的
			//所以,这样异或x可以看作x于之前的a[]数组进行异或
			//然后一直异或到为0或者当前b[i]还没有被赋值
			else { b[i]=x;break;}//否则b[i]赋值为x,
			//表示当前二进制下的第i位可以被异或出来,且x的最高位就是i
		}
	}
}

int main(){
	cin>>n;
	for(ll i=1;i<=n;i++){cin>>a[i];update(a[i]);}//读入数据对于每一个数字都下放来维护b[i]
	for(ll i=60;i>=0;i--){
        if((ans^(1ll<<i))>ans)ans^=b[i];

	}
	//贪心的过程,ans看作一个二进制数,从高位开始,如果b[i]存在,
	//肯定优先跟b[i]异或,倒着让小值不会影响到大值
	cout<<ans<<endl;
	return 0;
}

礼物

题目描述

Description

题目背景
卧槽我居然在情人节前恋爱了!!
她一直陪我聊天,情商好高,还长得特别好看也很照顾我,终于能体验到谈恋爱的感觉了呜呜呜呜呜及正没事千不如和我一起 复制粘贴做白日梦吧

题目描述
又到了一年一度的情人节,小Y同学依旧是母胎solo,但是他是个资深的舔狗(舔狗不得好死),因为看多了各种恋爱番《路人女主的养成方法》,《总之就是非常酸》...,所以他总是会幻想自己某天遇到一个属于自己的女孩,在情人节要到来之际,他准备给自己幻想里的女朋友选礼物。

因为小Y同学老幻想着给不存在的女朋友过情人节,所以他很熟悉商店里面要送的礼物的性价比,比如给女朋友买性价比低的奢移品包包,或者买性价比高的实用品等等,而他已经收集到了n(1 \leq n \leq 100000)n(1≤n≤100000)个礼物的信息,现在他想挑出性价比最高的物品,你能帮他选出来吗?


Input
一个整数nn代表nn个物品

第二行nn个整数,代表不同物品的性价比k_ik 
i
​
 (int范围内)


Output
一个整数xx,表示最高的性价比


Sample Input 1 

5
1 2 3 4 5
Sample Output 1

5

题目思路及代码

签到题目

#include
using namespace std;
typedef long long int ll;
const ll maxn=1e5+10;
const ll mod=1e9+7;
ll n;
ll a[maxn];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+1+n);
    cout<<a[n];
	return 0;
}

看错题

题目描述

题目背景
小Y同学发现自己的数学思维真是太差了,自己写题目的时候总喜欢去写数据结构题,因为数据结构大都是模板,就是背了板子就能强行干题,但是数据结构只要有一点思维在里面,他就不会做了,因此他决定去做题网站写一写题。

巧合的是他看到了一道题,这道题要求的是,给定一颗线段树,每次区间加一个xx,如果每次在线段树区间加操作做完后,从根节点开始等概率的选择一个子节点进入,直到进入叶子结点为止,将一路经过的节点权值累加,最后能得到的期望值是多少?

不巧的是,小Y同学看错题辣,他把进入叶子节点看成了,依次选一个叶子节点进入,然后一直走到根节点,问最后得到的期望值是多少,巧合的是,他这么写了以后还把样例都过了,然后喜提WA声一片。

小Y同学很难受,于是把他看错的题意给你,看看你是否能做出这道题。
题目描述
首先题目描述里面,对于线段树的下放过程是这样的


void build(int rt,int l,int r){

if(l==r){tr[rt]=a[l];return ;}

int mid=(l+r)>>1;

build(ls,l,mid);

build(rs,mid+1,r);

tr[rt]=tr[ls]+tr[rs];

}

void update(int rt,int l,int r,int q,int v){

if(l==r){tr[rt]+=v;return;}

int mid=(l+r)>>1;

if(q<=mid)update(ls,l,mid,q,v);

else update(rs,mid+1,r,q,v);

}

//主函数中建树和下放的代码为

build(1,1,n);

for(int i=l;i<=r;i++)

update(1,1,n,i,v);
小Y同学为了让题目更简单,对于n个整数,n是一个2^x 的整数,来保证这个线段树是一棵满二叉树。

他现在想问你,每次给一段区间加上一个整数(每次操作对后续有影响),然后依次在线段树中选一个叶子节点,一直走到根节点,将一路经过的节点权值累加,问把每一个叶子节点选择一遍后的全部的总和是多少。
Input
第一行整数  n,mn,m, 表示线段树维护的原序列的长度,询问次数。

第二行  n(1\leq n \leq 2^{17} )n(1≤n≤2 
17
 )  个数,表示原序列。

接下来  m(1\leq m \leq 100000)m(1≤m≤100000)  行,每行三个数  l,r,xl,r,x  表示对区间[l,r][l,r]  加上  xx

Output
共 mm 行,表示查询的权值和。


Sample Input 1 

8 2 
1 2 3 4 5 6 7 8
1 3 4
1 8 2
Sample Output 1

720
960

题目思路及代码

思路:这个题目原题目非常难,但是这个题目非常简单,只需要知道线段树的构造方式就可以推出结论!!!
代码:

#include
using namespace std;
typedef long long int ll;
const ll maxn=1e5+10;
const ll mod=1e9+7;
ll tr[maxn*4];
ll a[maxn];
ll n,m;
ll val;
void build(int rt,int l,int r){
    //cout<
    val=max(val,(ll)rt);
    if(l==r){
        tr[rt]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(rt*2,l,mid);
    build(rt*2+1,mid+1,r);
    tr[rt]=tr[rt*2]+tr[rt*2+1];
    return ;
}

void update(int rt,int l,int r,int q,int v){
   // cout<
    if(l==r){
        {
            tr[rt]+=v;
            return ;
        }
    }
    int mid=(l+r)>>1;
    if(q<=mid){
        update(rt*2,l,mid,q,v);
    }
    else{
        update(rt*2+1,mid+1,r,q,v);
    }
    tr[rt]=tr[rt*2]+tr[rt*2+1];
    return ;
}
ll getsum(int rt,int l,int r,int q){
    if(l==q&&r==q){
        return tr[rt];
    }
    int mid=(l+r)>>1;
    ll ans=tr[rt];
    if(q<=mid){
        ans+=getsum(rt*2,l,mid,q);
        //ans+=tr[rt*2];
    }else{
        ans+=getsum(rt*2+1,mid+1,r,q);
        //ans+=tr[rt*2+1];
    }
    return ans;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    //build(1,1,n);
    ll cnt=0;
    for(int i=1;i<=n;i++){
        //cout<
        //cnt+=getsum(1,1,n,i);
        cnt=cnt+a[i]*(2*n-1);
    }
    //cout<
    while(m--){
        ll l,r,v;
        cin>>l>>r>>v;
        ll ans=0;
        ans=cnt+(r-l+1)*v*(2*n-1);
        cnt=ans;
        cout<<ans<<endl;
        //cout<<(ans-cnt)/(r-l+1)/v<
        cnt=ans;
    }

	return 0;
}

结语


“遇事不决可问春风,春风不语即随本心”的意思是:对一件事犹豫不决,就问春风该如何做,春风给不出答案,就凭自己本心做出决断。“遇事不决可问春风,春风不语即随本心”一句出自网络作家“烽火戏诸侯”的《剑来》,其原文是:“遇事不决,可问春风。春风不语,遵循己心”。

第三届全国大学生算法设计与编程挑战赛 (冬季赛)部分题解_第2张图片


你可能感兴趣的:(竞赛,数据结构,区间查找--莫队和线段树,算法,蓝桥杯,c++)