Atcoder Beginner Contest 107解题报告

这是第一篇博客啦,如有错误请大家在评论区指正~


第一场Atcoder的比赛本来想打ARC,结果由于过于萌新瞎报名到了ABC,23333,结果赛中也只肝掉了三题…
首先让我们略过AB吧
C题
C - Candles
Time limit : 2sec / Memory limit : 1024MB

Score : 300 points

Problem Statement
There are N candles placed on a number line. The i-th candle from the left is placed on coordinate xi. Here, x1 < x2 < .. < xn holds.

Initially, no candles are burning. Snuke decides to light K of the N candles.

Now, he is at coordinate 0. He can move left and right along the line with speed 1. He can also light a candle when he is at the same position as the candle, in negligible time.

Find the minimum time required to light K candles.

Constraints
1≤N≤105
1≤K≤N
xi is an integer.
|xi|≤108
x1 < x2 < … < xN
Input
Input is given from Standard Input in the following format:

N K
Output
Print the minimum time required to light K candles.

Sample Input 1
5 3
-30 -10 10 20 50
Sample Output 1
40
He should move and light candles as follows:

Move from coordinate 0 to −10.
Light the second candle from the left.
Move from coordinate −10 to 10.
Light the third candle from the left.
Move from coordinate 10 to 20.
Light the fourth candle from the left.
Sample Input 2
3 2
10 20 30
Sample Output 2
20
Sample Input 3
1 1
0
Sample Output 3
0
There may be a candle placed at coordinate 0.
Sample Input 4
8 5
-9 -7 -4 -3 1 2 3 4
Sample Output 4
10
题目大意:一条数轴上有各不相同n个点,从0位置出发,求能经过k个点的最短路径长度(输入保证严格上升)

我的想法是,对于任何一条路径来说,我们希望最多只掉一次头,否则就取不到MIN值,所以我们只要枚举在每个点掉头或者不掉头(此时满足从0点到该位置经过了至少k个点)的情况取MIN值就好了

AC Code:

#include<iostream>
#include<cstdio>'
#include<cmath>
#define rg register
#define il inline
#define ll long long
#define maxn 500005
using namespace std;
il int read(){rg int x = 0 ,w = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') w = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){x = (x<<3) + (x<<1) + ch - '0';ch = getchar();}return x * w;}
struct edge{
    int to,next;    
}e[maxn << 1];
ll a[maxn];
int main(){
    rg int n = read() , m = read();
    for (rg int i = 1;i <= n;++i) {scanf("%lld",&a[i]);if (!a[i]) --m;}
    rg ll res = 99999999999999 , k = 0;
    if (!m) {cout<<'0';return 0;}
    for (rg int i = 1;i <= n;++i){
        if (a[i] < 0) ++k;
        else break; 
    }
    for (rg int i = 1;i <= n;++i){
        if (a[i] < 0) {
            if (k - i + 1 >= m) res = min (res , -a[i]);
            else if (i + m - 1 <= n) res = min (res , ((-a[i]) << 1) + a[i + m - 1] );
        }
        else if (a[i] > 0){
            if (i - k >= m) res = min(res , a[i]);
            else if (i - m + 1 >= 1) res = min (res , (a[i] << 1) - a[i - m + 1]) ;
        }
    }
    cout<<res;
    return 0;   
}

D题
D - Median of Medians
Time limit : 2sec / Memory limit : 1024MB

Score : 700 points

Problem Statement
We will define the median of a sequence b of length M, as follows:

Let b’ be the sequence obtained by sorting b in non-decreasing order. Then, the value of the (M⁄2+1)-th element of b’ is the median of b. Here, ⁄ is integer division, rounding down.
For example, the median of (10,30,20) is 20; the median of (10,30,20,40) is 30; the median of (10,10,10,20,30) is 10.

Snuke comes up with the following problem.

You are given a sequence a of length N. For each pair (l,r) (1≤l≤r≤N), let ml,r be the median of the contiguous subsequence (al,al+1,…,ar) of a. We will list ml,r for all pairs (l,r) to create a new sequence m. Find the median of m.

Constraints
1≤N≤105
ai is an integer.
1≤ai≤109
Input
Input is given from Standard Input in the following format:

N
a1 a2 … aN
Output
Print the median of m.

Sample Input 1
3
10 30 20
Sample Output 1
30
The median of each contiguous subsequence of a is as follows:

The median of (10) is 10.
The median of (30) is 30.
The median of (20) is 20.
The median of (10,30) is 30.
The median of (30,20) is 30.
The median of (10,30,20) is 20.
Thus, m=(10,30,20,30,30,20) and the median of m is 30.

Sample Input 2
1
10
Sample Output 2
10
Sample Input 3
10
5 9 5 9 8 9 3 5 4 3
Sample Output 3
8

题目大意:给你一个有n个数的序列,现在将所有的子序列的中位数加入到一个新序列中,求这个新序列的中位数

这个题还是比较脑洞的,解题的关键在于你如何理解中位数这个概念,我们设一个序列的中位数为x,可以理解成x为满足小于等于x的数共有(序列长度 / 2 + 1)个这个条件的最小的数,因此我们可以采用二分答案的方法。
由于新序列都是由各个子序列的中位数组成的,所以题目转化成了求解有多少个子序列的中位数小于等于x。我们不妨用一个数组b,如果原序列中的某个数小于等于x,那么bi = 1,否则bi = - 1,我们再次观察,发现此时各个子序列的中位数只可能是1或-1,当然我们希望这个子序列的中位数为1,那么子序列中位数为1的话,说明该序列中至少要有(序列长度 / 2 + 1)个1,因此本题又进行了一次奇妙的变换:求解b数组子序列有多少个子序列的和是正数
使用前缀和,若sumi > sumj-1,即(i,j)的和为正数,题目又变成了求解有多少个sumi > sumj 且 i > j,现在我们要求解二维偏序问题,但我们甚至不需要掌握CDQ分治,因为题目已经转化成了一个求逆序对类型的问题,我们只要用求逆序对的经典算法:归并排序和树状数组就好了
在这里我使用树状数组解题
注意:
①考虑到子序列为一个数,所以我们可以在树状数组的“0位置”插入一个1,但是树状数组仿佛不支持0的插入,我们在注意②里说明此问题
②考虑到树状数组仿佛不支持非正整数的插入(我们的sum可能为 -n),因为逆序对类型的问题只考虑数值之间的大小关系,因此我们将所有要插入的位置 += n(包括注意①中的“0位置”)即可
③本题树状数组在每次check应当清空,所以不要把c数组开得过大,因为memset的复杂度也是接近于O(n),只不过常数较小罢了。
复杂度:O(n(logn)^2)
AC Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define rg register
#define il inline
#define maxn 300005
#define ll long long
using namespace std;
int a[maxn] , b[maxn] , c[maxn << 2] , sum[maxn];
il int lowbit(int x){return x & (-x);}
void insert(int i,int x,int n){
    while (i <= n){
        c[i] += x;
        i += lowbit(i); 
    }
}
ll getsum(int i){
    rg ll ans = 0;
    while (i > 0){
        ans += c[i];
        i -= lowbit(i);
    }
    return ans;
}
bool check(int x , int n){
    memset(c,0,sizeof(c));
    ll sum0 = (((ll)n * (ll)(n + 1) >> 1) >> 1) + 1;
    ll cnt = 0;
    int maxsum = -999999999;
    for (rg int i = 1;i <= n;++i) if (a[i] <= x) b[i] = 1;else b[i] = -1;
    for (rg int i = 1;i <= n;++i) {sum[i] = sum[i - 1] + b[i] , maxsum = (sum[i] > maxsum)?sum[i]:maxsum;}
    for (rg int i = 1;i <= n;++i) sum[i] += n;
    maxsum += n;
    insert(n , 1 , maxsum);
    for (rg int i = 1;i <= n;++i){
        cnt += getsum(sum[i] - 1);
        insert(sum[i], 1 , maxsum);
    }
    //cout<<cnt<<endl;
    return cnt >= sum0;
}
int main(){
    rg int n;
    scanf("%d",&n);
    rg int l = 1000000002 , r = -1;
    for (rg int i = 1;i <= n;++i) {scanf("%d",&a[i]);l = (a[i] < l)?a[i]:l , r = (a[i] > r)?a[i]:r;}
    while (l < r){
        rg int mid = (l + r) >> 1;
        if (check(mid , n)) r = mid;
        else l = mid + 1;   
    }
    cout << r;
    return 0;   
}

一点小拓展:假如值域过大树状数组开不了怎么办啊?使用离散化即可,复杂度不变
AC Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define rg register
#define il inline
#define maxn 300005
#define ll long long
using namespace std;
int a[maxn] , b[maxn] , c[maxn] , sum[maxn] , d[maxn];
il int lowbit(int x){return x & (-x);}
void insert(int i,int x,int n){
    while (i <= n){
        c[i] += x;
        i += lowbit(i); 
    }
}
ll getsum(int i){
    rg ll ans = 0;
    while (i > 0){
        ans += c[i];
        i -= lowbit(i);
    }
    return ans;
}
bool check(int x , int n){
    memset(c,0,sizeof(c));
    ll sum0 = (((ll)n * (ll)(n + 1) >> 1) >> 1) + 1;
    ll cnt = 0;
    for (rg int i = 1;i <= n;++i) if (a[i] <= x) b[i] = 1;else b[i] = -1;
    for (rg int i = 1;i <= n;++i) sum[i] = sum[i - 1] + b[i] , d[i] = sum[i];
    sort(sum + 1 , sum + 1 + n);
    int e = unique(sum + 1,sum + 1 + n) - sum;
    for (rg int i = 1;i <= n;++i) 
        d[i] = lower_bound(sum + 1 , sum + e + 1 , d[i]) - sum;
    rg int tmp = lower_bound(sum + 1 , sum + 1 + e , 0) - sum;
    insert(tmp , 1 , n);
    for (rg int i = 1;i <= n;++i){
        cnt += getsum(d[i] - 1);
        insert(d[i], 1 , n);
    }
    //cout<<cnt<<endl;
    return cnt >= sum0;
}
int main(){
    rg int n;
    scanf("%d",&n);
    rg int l = 1000000002 , r = -1;
    for (rg int i = 1;i <= n;++i) {scanf("%d",&a[i]);l = (a[i] < l)?a[i]:l , r = (a[i] > r)?a[i]:r;}
    while (l < r){
        rg int mid = (l + r) >> 1;
        if (check(mid , n)) r = mid;
        else l = mid + 1;   
    }
    cout << r;
    return 0;   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值