牛客练习赛49
Contest Info
[Practice Link](https://ac.nowcoder.com/acm/contest/946#question)
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
5/6 | O | O | Ø | 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;
}