2020年牛客算法入门课练习赛1补题

涉及:大量数据的第k小,几何斜率,圆形尺取,离散化,差分约束,前缀和,模拟。
A 第K小数
题意
给n个数,求第k小的元素。(n<=5e6)
思路
直接sort超时。
可以利用快排的思想,每次选一个基准元素,把小的元素放左边,大的元素放右边,如果左边的元素个数>=k,那么只需要从左边找第k小,右边就可以不管了;如果这个基准正好是第k小的位置,那就直接输出这个数;否则第k小就在右边,从右边找第(k-左边元素个数)小就可以了,左边的可以直接放弃,不需要进行排序。时间复杂度O(logn)。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 7;
typedef long long ll;
inline int read() {
    int sum=0; char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch))
        sum=sum*10+(ch^48),ch=getchar();
    return sum;
}
int a[maxn], n, k;
int getKth(int l, int r) {
    if(l == r) return a[l];
    int i = l, j = r;
    int mid = (l + r) / 2;
    int x = a[mid];
    while (i <= j) {
        while (a[i] < x) i++;
        while (a[j] > x) j--;
        if(i <= j) swap(a[i], a[j]), i++, j--;
    }
    if(k <= j) return getKth(l, j);
    else if(i <= k) return getKth(i, r);
    else return a[k];
}
int main()
{
    int t;
    t = read();
    while (t--) {
        n = read(), k = read();
        for (int i = 1; i <= n; i++) a[i] = read();
        printf("%d\n", getKth(1, n));
    }
}

B 不平行的直线
题意
给n个点,问两两形成的线段,从中最多选出多少条,两两之间是不重合且不平行的。(n<=200)
思路
暴力任意两条边,记录斜率,斜率不存在就答案加一,存在就跳过,注意斜率为0和斜率不存在的情况。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
typedef long long ll;
struct node {
    int x, y;
}p[maxn];
int n;
map<int, map<int,int> > mp;//用map嵌套来存储斜率
int Gcd(int x, int y) {
    return !y ? x : Gcd(y, x % y);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d%d", &p[i].x, &p[i].y);
    int ans = 0;
    bool f = false, ff = false;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            int y = p[i].y - p[j].y;
            int x = p[i].x - p[j].x;
            if(y == 0) {//斜率为0特判
                if(!ff) ans++;
                ff = true;
                continue;
            }
            if(x == 0) {//斜率不存在特判
                if(!f) ans++;
                f = true;
                continue;
            }
            int gcd = Gcd(x, y);
            y /= gcd;
            x /= gcd;
            if(mp.count(y)) {
                if(!mp[y].count(x)) {
                    ans++;
                    mp[y][x] = 1;
                }
            }
            else {
                mp[y][x] = 1;
                ans++;
            }
        }
    }
    cout << ans << endl;
}

C 丢手绢
题意
n个人围成一个圈,给出相邻两人的距离(圆上的距离),问相距最远的两人之间的距离(圆上的距离)。
思路
圆上相距最远的距离一定不超过周长sum/2。可以在圆上用尺取的方法。
枚举每个人,顺时钟计算与他最远的人的距离,记为tmp,tmp如果没有大于sum/2,那么tmp加上当前人到下一个顺时钟的人的距离,类似于尺取法,当tmp超过了sum/2的时候,更新答案,ans= max(ans, min(tmp, sum-tmp))。计算下一个人的时候,要把上一个人到他的距离减掉。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
typedef long long ll;
int n, a[maxn];
ll sum;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), sum += a[i];
    int tp = 1;
    ll tmp = sum / 2, maxx = 0, ans = 0;
    for (int i = 1; i <= n; i++) {
        while (maxx < tmp) {
            maxx += a[tp++];
            if(tp == n + 1) tp = 1;
        }
        ans = max(ans, min(maxx, sum - maxx));
        maxx -= a[i];
    }
    printf("%lld\n", ans);
}

D 二分
题意
有一个目标数字,要你去猜,裁判会给出你的数字和目标数字相比是高了还是低了还是正好是目标数字,但是裁判给的答案不一定是对的。然后给出你猜的数和裁判的回答,问裁判最多说了多少次对的答案。(数据范围在int以内)
思路
毫无头绪。。。
假设我猜了一个数5,裁判说低了,如果裁判说的是对的,那么这个目标数字就在[6,max];假设又猜了一个8,裁判说高了,那么如果裁判是对的,那么这个目标数字在[min,7];猜10,裁判说低了,那么目标数字在[11,max]。题目问的是裁判最多说了多少次答案,如果就猜了这三次,那么目标答案在[6,7]或者是[11,max]的时候,裁判说了对的答案最多。解法就出来了,把裁判的回答都表示成一个区间,对区间里的数加一,那么加的最多的数就是裁判最多说的对的答案的次数。离散化后,差分约束+前缀和。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
typedef long long ll;
int n;
struct node {
    int o, x;
}p[maxn];
vector<int> v;
int getid(int x) {
    return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}
int num[2 * maxn];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        char c;
        scanf("%d %c", &p[i].x, &c);
        if(c == '.') p[i].o = 3;
        else if(c == '+') p[i].o = 1, p[i].x--;
        else p[i].o = 2, p[i].x++;
        v.push_back(p[i].x);
    }
    sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());//离散化
    for (int i = 1; i <= n; i++) {
        if(p[i].o == 1) {
            num[1]++; num[getid(p[i].x) + 1]--;
        }
        else if(p[i].o == 2) {
            num[getid(p[i].x)]++; num[2 * maxn - 1]--;
        }
        else {
            num[getid(p[i].x)]++; num[getid(p[i].x) + 1]--;
        }
    }
    int sum = 0, ans = 0;
    for (int i = 1; i <= 2 * maxn; i++) {
        sum += num[i];
        ans = max(ans, sum);
    }
    printf("%d\n", ans);
}

E 交换
题意
n个人站成一排,每个人有一个序号,问将n个序号从小到大排列最少要多少次交换,交换可以不相邻,给的序号可能不连续,序号唯一。
思路
假设4个数字:3 1 4 2 第一个位置排序后应该是1,那么把1和3交换,第二个位置应该是2,把2和3交换,第三个位置应该是3,把3和4交换。这一定是最少的交换次数。模拟过程即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 7;
typedef long long ll;
int n, a[maxn], b[maxn];
map<int, int> mp, tp;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i], tp[a[i]] = i;
    sort (b + 1, b + 1 + n);
    for (int i = 1; i <= n; i++) mp[i] = b[i];
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if(a[i] != b[i]) {
            ans++;
            tp[a[i]] = tp[mp[i]];
            swap(a[i], a[tp[mp[i]]]);
        }
    }
    cout << ans << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值