2018年全国多校算法寒假训练营练习比赛(第五场)比赛题目题解

本文介绍了线段树和树状数组的基础知识及应用案例,包括逆序数求解、区间更新查询等问题,提供了详细的算法实现和解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这场比赛中的题目考察线段树/树状数组知识的居多。线段树学习:线段树讲解

这场比赛数据不严谨,自己觉得难、当时没做出的题目,比赛后发现别人根本没用什么东西,

都是暴力过的。


A-逆序数

/*******************************************************************************************************
Date 2018/2/25 
Author Wen Yaxin

解题思路:求解逆序数方法很多,可以用线段树去求解,可以用归并排序去做,
如果数据规模不大,甚至可以直接暴力。在此用线段树求解逆序数。线段树的
节点代表的区间用来维护该区间内有多少个数,对于题目给出的数,逆序插入
线段树内,当要把num插入线段树前,寻找已经插入到线段树中的数有多少是小于
num的,则查询【0,num-1】区间内存在多少个数字即可。将查询到的结果一一
累加到答案上。需要注意的问题是,按照题目的范围,如果给出十万个数字,并且
从大到小给出,则最后答案的范围会超int类型,所以解题时候数据类型需要用
long long

变量含义:
sum:sum是线段树的节点需要维护的值,即该区间中存在多少数字。
arr:用来逆序存储题目给出的数据。

方法含义:
push_up:更新当前节点的值。
build:构建线段树
Insert:将元素插入到线段树内
query:查询某个区间内存在多少个数字
***************************************************************************************************/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
 
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
 
using namespace std;
 
const int maxn = 100000+5;
 
long long sum[maxn*4];
int arr[maxn];
void push_up(int root) {
    sum[root] = sum[root<<1] + sum[root<<1|1];
}
void build(int left,int right,int root) {
    sum[root] = 0;
    if(left == right) {
        return;
    }
    int mid = (left+right)/2;
    build(lchild);
    build(rchild);
}
void Insert(int pos,int left,int right,int root) {
    if(left == right) {
        sum[root]++; //注意这里是++,因为某个数可能出现多次。
        return;
    }
    int mid = (left+right)/2;
    if(pos <= mid) Insert(pos,lchild);
    else Insert(pos,rchild);
    push_up(root);
}
long long query(int L,int R,int left,int right,int root)
{
    if(L<=left && right<=R)
    {
        return sum[root];
    }
    int ans = 0;
    int mid = (left+right)>>1;
    if(L<=mid) ans += query(L,R,lchild);
    if(R>mid) ans += query(L,R,rchild);
    return ans;
}
 
int main() {
    int n,Max;
    long long ans;
    while(~scanf("%d",&n)) {
        ans = 0;
        Max = 0;
        for(int i = n-1; i >= 0; i--) {
            scanf("%d",&arr[i]);
            if(arr[i]>Max) {
                Max = arr[i];
            }
        }
        build(0,Max,1);
        for(int i = 0; i < n; i++) {
            if(arr[i] != 0) {
                ans += query(0,arr[i]-1,0,Max,1);
            }
            Insert(arr[i],0,Max,1);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

B-Big Water Problem

/*******************************************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:线段树单点更新,区间查询,裸的线段树题目。

变量含义:
sum:维护区间的和

方法含义:
push_up:更新当前节点的值。
build:构建线段树
update:完成将某个位置的数加上一个数字
query:查询区间和值
********************************************************************************************/
#include <iostream>
#include <stdio.h>
#include <string.h>
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
using namespace std;

const int maxn = 1e6+2;
int sum[maxn<<2];
void push_up(int root) {
    sum[root] = sum[root<<1] + sum[root<<1|1];
}
void build(int left,int right,int root) {
    sum[root] = 0;
    if(left == right) {
        scanf("%d",&sum[root]);
        return;
    }
    int mid = (left+right)/2;
    build(lchild);
    build(rchild);
    push_up(root);
}
void update(int pos,int value,int left,int right,int root) {
    if(left == right) {
        sum[root] += value;
        return;
    }
    int mid = (left+right)/2;
    if(pos <= mid) update(pos,value,lchild);
    else update(pos,value,rchild);
    push_up(root);
}
int query(int L,int R,int left,int right,int root) {
    if(L<=left && right<=R) {
        return sum[root];
    }
    int mid = (left+right)/2;
    int ans = 0;
    if(L<=mid) ans += query(L,R,lchild);
    if(R>mid) ans += query(L,R,rchild);
    return ans;
}
int main() {
    int n,m,f,x,y;
    while(~scanf("%d%d",&n,&m)) {
        build(1,n,1);
        while(m--) {
            scanf("%d%d%d",&f,&x,&y);
            if(f == 1) {
                update(x,y,1,n,1);
            }
            else {
                printf("%d\n",query(x,y,1,n,1));
            }
        }
    }
    return 0;
}

C-字符串的问题

/***************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:比赛时候没有写,比赛完发现用暴力枚举,使用
C++中string的函数就能过。

变量含义:
str:题目所给字符串
prefix:前缀
suffix:后缀
mid:中间的字符串
**********************************************************************/
#include <iostream>
#include <stdio.h>
#include <string>

using namespace std;

int main() {
    string str,prefix,suffix,mid;
    cin>>str;
    string ans = "Just a legend";
    int len = str.length();
    if(len < 3) {
        cout<<ans<<endl;
    }
    else {
        int index = 1;
        mid = str.substr(1,len-2);
        while(index < len) {
            prefix = str.substr(0,index);
            suffix = str.substr(len-index,len-1);
            if(prefix == suffix) {
                if((int)mid.find(prefix) != -1) {
                    ans = prefix;
                }
            }
            index++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

D-集合问题

/***********************************************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:对于一个元素,只有两种情况,要么属于A集合,要么属于B集合。
如果都不满足,则无解。因此枚举每个元素对其考虑归属问题。
1.用一个数组保存题目给出的原数列,因为输出答案的时候需要进行对应情况的输出。
2.用一个数组对原数列进行排序。由于数列可能出现数字重复。
3.对于以排序的数列,对其去重,即每个数字只保存一次,并在用一个数组对应记录其出现的次数
4.枚举元素,对于元素x,首先判断其是否归属于B,则对b-x,在数组中进行二分查找,只要能找到
就说明数列中有元素可以匹配,如果查找到的是自己,则元素x全属于B集合,查找到的是其他元素,
则要取两个数字的最少数量。该元素剩余的数量,在考虑是否属于集合A。
5.在此过程中,只用记录一下一个元素,假设其为N个,N个中有几个属于集合B。
6.根据题目给出的数列,在存储单次数字的数组进行二分查找。

误区:之前做的时候思路还是有点偏差,同样的思路,开始的时候,我认为只有数字为偶数个
的时候,才可能有解。并且如果元素自己和自己匹配,采用错误的思想,认为两个相同的元素
算成两个数字匹配在一起。事实上自己和自己匹配,只要自身属于某个集合,单个元素就是合法
的。因此,奇数个数字也可能匹配。
比如:
3 2 3
1 1 1
答案:
YES
0 0 0

变量含义:
num[i]:num[i]用来记录某个数字出现的次数
arr1:用来存储去重后的数列。
arr2:用来存储题目给出的原数列
sortedArr:开始和arr2一样,但是该数组用来排序使用
inSetB:记录某元素有多少个属于集合B。

方法含义:
binarySearch:对某元素进行二分查找。
**********************************************************************************************/
#include <iostream>
#include <stdio.h>
#include <string>
#include <algorithm>
#include <string.h>

using namespace std;

const int maxn = 1e5+10;
int num[maxn];
int arr1[maxn];
int arr2[maxn];
int sortedArr[maxn];
int inSetB[maxn];
int binarySearch(int value,int left,int right) {
    int mid;
    while(left <= right) {
        mid = (left+right)/2;
        if(arr1[mid] == value) {
            return mid;
        }
        else if(arr1[mid] > value) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return -1;
}
int main() {
    int n,a,b,cnt,pos;
    while(~scanf("%d%d%d",&n,&a,&b)) {
        for(int i = 0; i < n; i++) {
            scanf("%d",&arr2[i]);
            sortedArr[i] = arr2[i];
        }
        sort(sortedArr,sortedArr+n);   //对数组进行排序
        memset(num,0,sizeof(num));
        //对数字出重并存储该数字出现的次数。
        int pre = sortedArr[0];
        num[0] = 1;
        arr1[0] = pre;
        cnt = 0;
        for(int i = 1; i < n; i++) {
            if(sortedArr[i] != pre) {
                cnt++;
                pre = sortedArr[i];
                arr1[cnt] = pre;
            }
            num[cnt]++;
        }
        memset(inSetB,0,sizeof(inSetB));
        int index = 0,x,Min;
        //枚举每个元素,判断其归属。
        bool flag = true;
        while(index <= cnt) {
            //先考虑是否可以归属于集合B
            if(num[index]) {
               x = b - arr1[index];
               pos = binarySearch(x,0,cnt);
               //arr1[index]不可能属于集合B
               if(pos != -1) {
                    if(index == pos) {  //考虑与它匹配的是其自己
                        inSetB[index] = num[index];
                        num[index] = 0;
                    }
                    else {  //和其他数匹配
                        Min = min(num[index],num[pos]);
                        inSetB[index] += Min;
                        inSetB[pos] += Min;
                        num[index] -= Min;
                        num[pos] -= Min;
                    }
                }
            }
            //然后考虑剩余的属于A
            if(num[index]) {
                x = a - arr1[index];
                pos = binarySearch(x,0,cnt);
                if(pos != -1) {
                    if(index == pos) {
                          num[index] = 0;
                    }
                    else {
                        Min = min(num[index],num[pos]);
                        num[index] -= Min;
                        num[pos] -= Min;
                    }
                }
            }
            //处理过的元素必须完全在数组中找到匹配,处理过后未匹配的个数应该是0
            if(num[index]) {
                flag = false;
                break;
            }
            index++;
        }
        if(flag) {
            printf("YES\n");
            pos = binarySearch(arr2[0],0,cnt);
            if(inSetB[pos]) {
                printf("1");
                inSetB[pos]--;
            }
            else {
                printf("0");
            }
            for(int i = 1; i < n; i++) {
                pos = binarySearch(arr2[i],0,cnt);
                if(inSetB[pos]) {
                    printf(" 1");
                    inSetB[pos]--;
                }
                else {
                    printf(" 0");
                }
            }
            printf("\n");
        }
        else {
            printf("NO\n");
        }
    }
    return 0;
}

E-情人节的电灯泡

/***************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:题目数据弱了,暴力也能过。按照题目数据范围计算,暴力在一秒时间
内极限情况是不能过的。但是正确保险的做法是用二维的树状数组。
**********************************************************************/
#include <iostream>
#include <stdio.h>

using namespace std;

const int maxn = 1003;
int arr[maxn][maxn];
int main() {
    int n,m,op,x1,y1,x2,y2;
    while(~scanf("%d%d",&n,&m)) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                scanf("%d",&arr[i][j]);
            }
        }
        while(m--) {
            scanf("%d",&op);
            if(op == 1) {
                scanf("%d%d",&x1,&y1);
                arr[x1][y1] = !arr[x1][y1];
            }
            else {
                scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
                int ans = 0;
                for(int i = x1; i <= x2; i++) {
                    for(int j = y1; j <= y2; j++) {
                        if(arr[i][j]) ans++;
                    }
                }
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}


F-The Biggest Water Problem

/****************************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:考察数位分离。

方法含义:
fun:对n进行数位分离,并算出数位加和。
**************************************************************************/
#include <iostream>
#include <stdio.h>
#include <string.h>
 
using namespace std;
 
int fun(int n) {
    int ans = 0;
    while(n) {
        ans += n%10;
        n = n/10;
    }
    return ans;
}
int main() {
    int n;
    while(~scanf("%d",&n)) {
        while(n >= 10) {
            n = fun(n);
        }
        printf("%d\n",n);
    }
    return 0;
}

G-送分啦-QAQ

/********************************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:简单博弈题,通过分析,可以知道这个博弈
和斐波那契数列相关。斐波那契数列从2,3,开始往后
推到,如果n是斐波那契数,则小傻子赢,否则小仙女赢。

方法含义:
fun:求以斐波那契数列并制成表
binarySearch:在斐波那契而数列中进行二分查找操作。
********************************************************************************/
#include <iostream>
#include <stdio.h>

using namespace std;

long long f[100];
void fun() {
    f[0] = 2;
    f[1] = 3;
    for(int i = 2; i < 40; i++) {
        f[i] = f[i-1] + f[i-2];
    }
}
int binarySearch(int n) {
    int low,high,mid;
    low = 0;
    high = 39;
    while(low<=high) {
        mid = (low+high)/2;
        if(f[mid] == n){
            return mid;
        }
        else if(f[mid]>n) {
            high = mid-1;
        }
        else {
            low = mid+1;
        }
    }
    return -1;
}
int main() {
    fun();
    int n;
    while(~scanf("%d",&n)) {
       int temp = binarySearch(n);
       if(temp == -1) {
            printf("Xian\n");
       }
       else {
            printf("Sha\n");
       }
    }
    return 0;
}

H-Tree Recovery

/***************************************************************
Date 2018/2/25
Author Wen Yaxin

解题思路:线段树基础题,区间更新,区间查询。

变量含义:
sum:区间和值。
lazy:区间懒惰标记

方法含义:
push_up:更新当前节点值。
push_down:下推标记
build:构建线段树
update:在某个区间上的值都加上一个数
query:查询区间和值
**********************************************************************/
#include <iostream>
#include <stdio.h>
 
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
 
using namespace std;
 
const int maxn = 1e6+10;
int sum[maxn<<2];
int lazy[maxn<<2];
void push_up(int root) {
    sum[root] = sum[root<<1] + sum[root<<1|1];
}
void push_down(int root,int left,int right) {
    //说明该节点有标记
    int mid = (left+right)/2;
    if(lazy[root]>0) {
       //求左区间的长度
       int leftLen = mid-left+1;
       //求右区间的长度
       int rightLen = right-mid;
       sum[root<<1] += leftLen*lazy[root];
       //更新右区间和值
       sum[root<<1|1] += rightLen*lazy[root];
       //下推标记到左区间
       lazy[root<<1] += lazy[root];
       //下推标记到右区间
       lazy[root<<1|1] += lazy[root];
       //当前节点标记下推完毕,恢复成无标记状态
       lazy[root] = 0;
    }
}
void build(int left,int right,int root) {
    sum[root] = 0;
    lazy[root] = 0;
    if(left == right) {
        scanf("%d",&sum[root]);
        return;
    }
    int mid = (left+right)/2;
    build(lchild);
    build(rchild);
    push_up(root);
}
void update(int L,int R,int add,int left,int right,int root) {
    if(L<=left && right<=R) {
        sum[root] += (right-left+1)*add;
        lazy[root] += add;
        return;
    }
    push_down(root,left,right);
    int mid = (left+right)/2;
    if(L<=mid) update(L,R,add,lchild);
    if(R>mid) update(L,R,add,rchild);
    push_up(root);
}
int query(int L,int R,int left,int right,int root) {
    if(L<=left && right<=R) {
        return sum[root];
    }
    push_down(root,left,right);
    int mid = (left+right)/2;
    int ans = 0;
    if(L<=mid) ans += query(L,R,lchild);
    if(R>mid) ans += query(L,R,rchild);
    return ans;
}
int main() {
    int N,M,a,b,c;
    char op;
    while(~scanf("%d%d",&N,&M)) {
        build(1,N,1);
        while(M--) {
            scanf(" %c",&op);
            if(op == 'C') {
                scanf("%d%d%d",&a,&b,&c);
                update(a,b,c,1,N,1);
            }
            else {
                scanf("%d%d",&a,&b);
                printf("%d\n",query(a,b,1,N,1));
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值