牛客练习赛49

Contest Info


Practice Link

SolvedABCDEF
5/6OOØOØ-
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. 筱玛爱地理

签到题。

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define N 200010
const ll p = 1e9 + 7;
int n;
struct node {
    ll a, b;
    node() {}
    void scan() {
        scanf("%lld%lld", &b, &a);
    }
    bool operator < (const node &other) const {
        return a * other.b > b * other.a;
    }
}a[N];
ll qmod(ll base, ll n) {
    ll res = 1;
    while (n) {
        if (n & 1) {
            res = res * base % p;
        }
        base = base * base % p;
        n >>= 1;
    }
    return res;
}

int main() {
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; ++i) {
            a[i].scan();
        }
        sort(a + 1, a + 1 + n);
        for (int i = 1; i <= n; ++i) {
            printf("%lld\n", a[i].a * qmod(a[i].b, p - 2) % p);
        }
    }
    return 0;
}

B. 筱玛爱阅读

题意:
一共有\(n(1 \leq n \leq 15)\)本书和\(n\)个价格,有\(m(1 \leq m \leq 2^n - 1)\)种促销方案,每本书最多用于一个促销方案,每一个促销方案里面最便宜的一本书免费。
\(n\)个价格可以任意分配给\(n\)本书,即一个价格分配给一本书,问最后最少用多少钱买到所有的书。

思路:
考虑如果我们知道了最后所用的促销方案,里面的书本数分别为\(x_1, x_2, \cdots\),那么我们令\(x_1 \leq x_2 \leq \cdots\),那么免费的书本的价格分别为\(a[x_1], a[x_1 + x_2], \cdots\)
此处\(a_i\)降序排序。
那么知道了这个,我们进行\(dp\),考虑\(f[i]\)为此时购买的书本的状态为\(i\)时的最少价格,那么每一种促销方案显然只能通过它的补集的子集向上转移。
那么转移的时候我怎么知道免费的书本的价格是多少?
显然是促销方案和转移的集合取并后当前有\(x\)本书,就是按价格降序排序后第\(x\)个价格会免费。
这样是最优的吗?
是最优的,只要你枚举促销方案的时候按他们的书本数量升序排序去枚举就好了,因为保证通过你转移上去的方案所应用的方案的书本数量都比你小。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 100010
int n, m, f[N], b[20], num[N];
struct node {
    int sze, s;
    node() {}
    void scan() {
        scanf("%d", &sze);
        s = 0;
        for (int i = 1, x; i <= sze; ++i) {
            scanf("%d", &x);
            s |= (1 << (x - 1));
        }
    }
    bool operator < (const node &other) const {
        return sze < other.sze;
    }
}a[N];
 
int main() {
    for (int i = 0; i < (1 << 15) - 1; ++i) {
        num[i] = 0;
        int t = i;
        while (t) {
            num[i] += t % 2;
            t /= 2;
        }
    }
    while (scanf("%d%d", &n, &m) != EOF) {
        int sum = 0, res = 0;
        for (int i = 1; i <= n; ++i) scanf("%d", b + i), sum += b[i];
        sort(b + 1, b + 1 + n, [](int x, int y) {
            return x > y;       
        });
        for (int i = 1; i <= m; ++i) a[i].scan();
        memset(f, 0, sizeof f);
        sort(a + 1, a + 1 + m);
        int D = (1 << n) - 1;
        for (int i = 1; i <= m; ++i) {
            int now = D ^ a[i].s;
            f[a[i].s] = max(f[a[i].s], b[a[i].sze]); 
            for (int j = now; j > 0; j = (j - 1) & now) {
                f[j | a[i].s] = max(f[j | a[i].s], f[j] + b[num[j] + a[i].sze]);
            }
        }
        for (int i = 0; i < D; ++i) res = max(res, f[i]);   
        printf("%d\n", sum - res);
    }
    return 0;
}

C.筱玛爱历史

题意:
有一个长度为\(n \cdot (n + 1)\)的序列,要求挑选出一个长度为\(2n\)的子序列,使得这个子序列的最大的元素和次大的元素相邻,第三大的元素和第四大的元素相邻,......最小的和次小的相邻。
输出方案。

思路;
本来以为是长度为\(2n\)的上升自序列,但是这样不是最优解,可能存在无解的情况。
我们注意到数的个数一共有\(n \cdot (n + 1)\)个,那么我们将数先离散化后它们就变成了\(1, \cdots, n(n + 1)\),在考虑将所有数分成\(n\)块,那么每块的个数有\(n + 1\)个。
然后分别在每块中取两个。
因为根据鸽笼原理,每\(n + 2\)个数就会有两个同种类的数,然后取让这两个同种类的数相邻即可。
一旦取了之后,问题就变成了\(n(n - 1)\)中取\(2(n - 1)\)个数的子问题。

代码:

#include <bits/stdc++.h>
using namespace std;

#define N 2000010
int n, m, a[N], b[N], used[N], last[N];
vector <int> res;
stack <int> sta;

int main() {
    while (scanf("%d", &n) != EOF) {
        m = n * (n + 1);
        for (int i = 1; i <= m; ++i) {
            scanf("%d", a + i);
            b[i] = i;
        }
        sort(b + 1, b + 1 + m, [&](int x, int y) {
            return a[x] < a[y];     
        });
        for (int i = 1; i <= m; ++i) a[b[i]] = i;
        res.clear();
        while (!sta.empty()) sta.pop();
        memset(used, 0, sizeof used);
        memset(last, -1, sizeof last);
        for (int i = 1; i <= m; ++i) {
            int x = a[i], g = x / n;
            if (used[g]) continue;
            if (last[g] == -1) {
                last[g] = i;
                sta.push(g); 
            } else {
                res.push_back(last[g]);
                res.push_back(i);
                used[g] = 1;
                while (!sta.empty()) {
                    last[sta.top()] = -1;
                    sta.pop();
                }
            }
        }
        for (int i = 0; i < 2 * n; ++i) printf("%d%c", res[i], " \n"[i == 2 * n - 1]);
    }
    return 0;
}

D. 筱玛爱线段树

题意:
初始有一个序列\(a_1, \cdots, a_n\),初始值为空,要求支持两种操作:

  • 1 l r 将\([l, r]\)区间内的数都\(+1\)
  • 2 l r 将\([l, r]\)区间内的操作都执行一次,保证$r < $当前操作编号

思路:
倒着维护一下每个操作会被执行几次。
遇到\(1\)操作的时候就知道这个操作一共被执行了\(x\)次,然后区间\(+x\)即可。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define N 100010
const ll p = 1e9 + 7;
template <class T>
void add(T &x, T y) {
    x += y;
    if (x >= p) x -= p;
}
int n, m; ll a[N];
struct node {
    int op, l, r;
    node() {}
    void scan() {
        scanf("%d%d%d", &op, &l, &r);
    }
}q[N];
struct BIT {
    ll a[N];
    void init() {
        memset(a, 0, sizeof a);
    }
    void update(int x, int v) {
        for (; x < N; x += x & -x) {
            add(a[x], 1ll * v);
        }
    }
    ll query(int x) {
        ll res = 0;
        for (; x > 0; x -= x & -x) {
            add(res, a[x]);
        }
        return res;
    }
}bit;
 
int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= m; ++i) q[i].scan();
        bit.init();
        memset(a, 0, sizeof a);
        for (int i = m, t, l, r; i >= 1; --i) {
            t = bit.query(i) + 1;
            l = q[i].l; r = q[i].r;
            switch(q[i].op) {
                case 1 :
                    add(a[l], 1ll * t);
                    add(a[r + 1], (p - t) % p);
                    break;
                case 2 :
                    bit.update(l, t);
                    bit.update(r + 1, (p - t) % p);
                    break;
            }
        }
        for (int i = 1; i <= n; ++i) {
            add(a[i], a[i - 1]);
            printf("%lld%c", a[i], " \n"[i == n]);
        }
    }
    return 0;
}

E. 筱玛爱游戏

题意:
两个人轮流从\(n\)个数中取数,每次取出来的数放入一个集合中,当谁的操作之后使得集合中存在一个非空子集的异或和为\(0\),那么谁就输了。
或者谁没得取了谁就输。

思路:
考虑取的人不输的话,那么它取的数一定是和以取的数构成的向量组线性无关,那么最多能取的数即为这堆数的线性基的大小。
而一堆数的线性基的大小是确定的。

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define N 100010
#define MAXL 62
int n, res;
ll a[N];

struct LinearBasis {
    ll a[MAXL + 1];
    LinearBasis() {
        fill(a, a + MAXL + 1, 0);
    }
    void insert(ll t) {
        for (int j = MAXL; j >= 0; --j) {
            if (!(t & (1ll << j))) continue;
            if (a[j]) t ^= a[j];
            else {
                for (int k = 0; k < j; ++k) {
                    if (t & (1ll << k)) {
                        t ^= a[k];
                    }
                }
                for (int k = j + 1; k <= MAXL; ++k) {
                    if (a[k] & (1ll << j)) {
                        a[k] ^= t;
                    }
                }
                a[j] = t;
                return;
            }
        }
    }
}li;

int main() {
    while (scanf("%d", &n) != EOF) {
        res = 0;
        li = LinearBasis();
        for (int i = 1; i <= n; ++i) {
            scanf("%lld", a + i);
            li.insert(a[i]);
        }
        for (int i = 0; i <= MAXL; ++i) {
            res += (li.a[i] != 0);
        }
        puts(res & 1 ? "First" : "Second");
    }
    return 0;
}

转载于:https://www.cnblogs.com/Dup4/p/11143453.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值