牛客练习赛3
题意:
一只南美洲亚马孙河流域热带雨林中的蝴蝶,偶尔扇动几下翅膀,可
以在两周以后引起美国德克萨斯州的一场龙卷风。――蝴蝶效应
由于这个理论的存在,大多数人认为将未来的事物送回过去将会引发
严重的时间悖论,但事实上还存在另外一套理论。
自然会对这类不和谐的蝴蝶效应做出调整,具体地来说就是触发一些
小概率的恶性事件来抹杀穿越者来消除其对未来的影响。
虽然听上去很荒诞,但Alicebell决定去验证这一假说,她将按1 ∼ n的
顺序依次到访过去的n个时间点。
这n个时间点各有一个能源参数A𝑖,即到达这个时间点时,身上必须
保证有A𝑖单位的能量,那之后将会消耗掉一单位的能量。
Alicebell想知道依次到访这n个时间点,最初需要携带至少多少能量。
题解:
直接二分答案
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
ll n, a[N];
bool judge(ll x) {
for (int i = 1; i <= n; i++) {
if (x >= a[i]) {
x--;
} else {
return false;
}
}
return true;
}
int main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
ll l = 1, r = 1e18, ans;
while (l <= r) {
ll mid = (l + r) / 2;
if (judge(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
printf("%lld\n", ans);
}
题意:
贝伦卡斯泰露,某种程度上也可以称为古手梨花,能够创造几率近乎
为0的奇迹,通过无限轮回成功打破了世界线收束理论。
和某科学者不同,贝伦并不在意世界线收束的那套理论,作为奇迹
之魔女,贝伦的爱好只在于品茶。
作为品茶的消遣,贝伦正在解一道简单的谜题。
给出一个长度为n的数列A𝑖,问是否能将这个数列分解为两个长度
为n/2的子序列,满足
∙ 两个子序列不互相重叠。
∙ 两个子序列中的数要完全一样,{1, 2} = {1, 2},{1, 2} ≠ {2, 1}。
题解:
如果暴力枚举的话,会有 2 40 2 ^ {40} 240 中方案, 但是加上一些剪枝会远远小于 2 40 2 ^ {40} 240的
这题还有另一种写法, 类似与括号匹配的一直方法, 复杂度可以做到 o ( n ) o(n) o(n)
用一个队列维护, 队列为空的时候加入元素, 当队列不为空的时候, 判断当前
元素是否与, 队列的第一个元素相等, 如果相等就pop掉, 否则将改元素加入队列
中。 然后在到这按照上面的方法扫一遍 , 最后判断只有这两次中有一次队列为空, 说明
一定有可行方案。
dfs版代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 50;
int A[N], n;
vector<int>a, b;
int dfs(int p, int sum, int ct) {
if (sum == n / 2 && ct == n / 2) {
if (sum == ct && sum) {
if (a[sum - 1] != b[sum - 1]) {
return 0;
}
}
return 1;
}
if (sum == ct && sum) {
if (a[sum - 1] != b[sum - 1]) {
return 0;
}
} else if (sum > ct && ct) {
if (a[ct - 1] != b[ct - 1]) {
return 0;
}
} else if (ct > sum && sum) {
if (a[sum - 1] != b[sum - 1]) {
return 0;
}
}
if ( p > n || sum + (n - p + 1) < n / 2 || ct + (n - p + 1) < n / 2) return 0;
if (sum < n / 2) {
a.push_back(A[p]);
int ans = dfs(p + 1, sum + 1, ct);
if (ans) return 1;
a.pop_back();
}
if (ct < n / 2) {
b.push_back(A[p]);
int f = dfs(p + 1, sum, ct + 1);
if (f) return 1;
b.pop_back();
}
return 0;
}
int main() {
int t; scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &A[i]);
}
a.clear(), b.clear();
if (dfs(1, 0, 0)) {
puts("Frederica Bernkastel");
} else {
puts("Furude Rika");
}
}
}
题意:
𝑅𝑒𝑘𝑖是一名武侦高狙击科的学生,武侦高也设有基础学科,现在她正
在完成生物课的作业。
给出一张𝑛个点𝑚条边的无向图,这张无向图描述了一个细胞,细胞有
三种:X型、Y型还是I型。
题解:
直接判断度数为1的点个数。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 507;
vector<int> g[N];
int d[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
d[u]++;
d[v]++;
}
int ct = 0;
for (int i = 1; i <= n; i++) {
if (d[i] == 1) {
ct++;
}
}
if (ct == 2) {
puts("I");
} else if (ct == 3) {
puts("Y");
} else if (ct == 4) {
puts("X");
} else {
puts("NotValid");
}
}
题意:
𝑅𝑒𝑘𝑖是一名狙击手,凭借肉眼视觉可以做到精确命中绝对半径2051公尺的一切目标。
作为一名优秀的狙击手,𝑅𝑒𝑘𝑖不仅经常保养枪支,也经常保养弹药。
𝑅𝑒𝑘𝑖有𝑛枚子弹,第𝑖枚的型号为𝐶𝑖,𝑅𝑒𝑘𝑖打算扔掉其中最多𝑘枚。
大多数优秀的狙击手都有艺术癖好,𝑅𝑒𝑘𝑖希望扔掉一部分子弹后,最
长的连续相同子弹序列的长度尽量长。
题解:
如果我们知道某个区间出现最多的次数那么肯定是留出现次数最多的最优, 删掉其它的元素, 如果删掉掉的个数大于k,那么可以说明这个区间肯定不合法。
所以我们可以用两个双指针 l , r l, r l,r 当 [ l , r ] [l, r] [l,r]区间不合法时 我们开始走 l l l区间直到找到合法的位置, 然后在走 r r r区间
遇到不合法时再走 l l l …………, 至于怎么判断是否合法, 可以用权值线段树维护, 每个数出现的次数, 然后线段树再维护最大值。
时间复杂度 o ( n ∗ l o g ( n ) ) o(n * log(n)) o(n∗log(n))
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int n, a[N], rt[N], vis[2 * N];
vector<int> g;
int tree[4 * N];
#define m (l + r) / 2
#define lson 2 * node
#define rson 2 * node + 1
void update(int v, int pos, int l, int r, int node) {
if (l == r) {
tree[node] += v;
return;
}
if (pos <= m) update(v, pos, l, m, lson);
else update(v, pos, m + 1, r, rson);
tree[node] = max(tree[lson], tree[rson]);
}
int get_id(int x) {
return lower_bound(g.begin(), g.end(), x) - g.begin() + 1;
}
int main() {
int k;
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
g.push_back(a[i]);
}
sort(g.begin(), g.end());
g.erase(unique(g.begin(), g.end()), g.end());
for (int i = 1; i <= n; i++) {
a[i] = get_id(a[i]);
}
int r = 1, res = 0;
for (int i = 1; i <= n; i++) {
int maxn = 0;
while (r <= n) {
update(1, a[r], 1, n, 1);
if (r - i + 1 - tree[1] <= k) {
res = max(res, tree[1]);
} else {
update(-1, a[r], 1, n, 1);
break;
}
r++;
}
update(-1, a[i], 1, n, 1);
}
cout << res << endl;
}
题意:
𝑅𝑒𝑘𝑖在课余会接受一些民间的鹰眼类委托,即远距离的狙击监视防卫。
𝑅𝑒𝑘𝑖一共接到了𝑚份委托,这些委托与𝑛个直线排布的监视点相关。
第𝑖份委托的内容为:对于区间[𝑙𝑖, 𝑟𝑖]中的监视点,至少要防卫其中的𝑘𝑖个。
𝑅𝑒𝑘𝑖必须完成全部委托,并且希望选取尽量少的监视点来防卫。
题解:
首先肯定会想到贪心, 先再公用区域最多的位置排布监视点, 然后你会发现如果直接进行线段覆盖
然后取覆盖最多的会很麻烦。
如果换个思路这题就简单了。
将所有区间按从r从小到大排序。
竟然是出现次数最多, 那么直接贪心从 r r r 开始, 这样操作是不是简单了很多。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 7;
int n, m;
struct node {
int l, r, k;
}p[N];
bool cmp(node x, node y) {
return x.r < y.r;
}
int sum[N];
int lowbit(int x) {
return x & (-x);
}
int query(int x) {
int res = 0;
while (x >= 1) {
res += sum[x];
x -= lowbit(x);
}
return res;
}
void update(int x, int v) {
while (x <= n) {
sum[x] += v;
x += lowbit(x);
}
}
int vis[N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &p[i].l, &p[i].r, &p[i].k);
}
sort(p + 1, p + m + 1, cmp);
for (int i = 1; i <= m; i++) {
int l = p[i].l, r = p[i].r, k = p[i].k;
int cnt = query(r) - query(l - 1);
if (cnt >= k) continue;
for (int j = r; j >= l; j--) {
if (vis[j] == 0) {
update(j, 1);
cnt++;
vis[j] = 1;
}
if (cnt == k) break;
}
}
cout << query(n) << endl;
}