Codeforces Round 827 (Div. 4)C~G
Codeforces Round 835 (Div. 4)D~G
D. Challenging Valleys(模拟)
题意
给一个数组,判断一下这个数字是否只存在一个连续子数组满足那些条件
思路
直接去枚举,时间是线性的,不怕超
枚举左端点l,然后右端点r如何确定?如果遇到了a[l] != a[r],那么r-1就是右端点,因为如果要满足条件,那么子数组的所有数值都必须英语
枚举出来子数组之后就去判断后面两个条件
if ((l == 0 || a[l - 1] > a[l]) && (r == n - 1 || a[r] < a[r + 1]))cnt++;
如果不符合呢,就让右端点l跳到r+1,继续去枚举
如果最后cnt等于1,就合法(我写了个特判数组长度为1的时候,应该可以不要)
关键是要写出健壮通用一点的代码, 要不然有很多边界情况要去讨论很麻烦
代码
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
void solve() {
int n; cin >> n;
vector<int>a(n);
for (int i = 0; i < n; i++)cin >> a[i];
int l = 0, r = 0, cnt = 0;
for (int i = 0, j = 0; i < n; i++) {
j = i;
while (j < n && a[i] == a[j]) {
j++;
}
l = i; r = j - 1;
if ((l == 0 || a[l - 1] > a[l]) && (r == n - 1 || a[r] < a[r + 1]))cnt++;
i = j - 1;
}
if (n == 1)cnt = 1;
if (cnt == 1)cout << "YES" << endl;
else cout << "NO" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _; _ = 1;
cin >> _;
while (_--) solve();
return 0;
}
E. Binary Inversions(前缀和)
题意
给一个只包含1/0的数组,然后最多可以操作1次(翻转某个位置的1/0)。使得数组里面逆序对最大是多少
思路
搞两个数组cnt_0和cnt_1分别表示某个位置(包含自己)前面有多少个0或者1
然后枚举每个位置(每个位置都尝试翻转一下,看看得到的答案是多少)
代码
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;
void solve() {
int n; cin >> n;
vector<int>a(n + 1), cnt_0(n + 1), cnt_1(n + 1);
long long sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt_1[i] = cnt_1[i - 1];
cnt_0[i] = cnt_0[i - 1];
if (a[i]) {
cnt_1[i]++;
}
else {
cnt_0[i]++;
sum += cnt_1[i];
}
}
// cout << "sum:" << sum << endl;
long long maxSum = sum;
// 如果把0换成1,那就要看前面1的数量和后面0的数量
// cnt_1[i] -> cnt_0[n]-cnt_0[i]
//
// 如果把1换成0,那也要看前面1的数量和后面0的数量
// cnt_1[i] <- cnt_0[n]-cnt_0[i]
for (int i = 1; i <= n; i++) {
long long pre = cnt_1[i-1];
long long back = cnt_0[n] - cnt_0[i];
if (a[i]) {
maxSum = max(maxSum, sum + (pre - back));
}
else {
maxSum = max(maxSum, sum + (back - pre));
}
}
ans.push_back(maxSum);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _; _ = 1;
cin >> _;
while (_--) solve();
for (auto x : ans)cout << x << endl;
return 0;
}
F. Quests(二分、前缀和、模拟)
题意
每天只能做一个任务,然后得到金币,需要判断d天能能不能搞到c个金币。
可以设置一个k,做完某个任务之后,只有隔k天之后才能再做这个任务。问k最大是多少。
思路
Infinity
这时候肯定就是不管你k多大,我把所有任务(优先做金币多的)做完。根本不需要重复做任务,就可以得到c个金币,那说明k随便你多大,都能满足。所以是Infinity
Impossible
如果根本没有限制间隔时间,你就在d天内一直调金币最多的那个任务去做,做满d天,都不够c个金币,这时候就是Impossible
因为k越大,搞到的金币肯定越少。所以可以二分k。这里关键就是check函数怎么写。
k的意思是,做完一个任务之后,间隔k天,才可以重新做
我们做任务肯定是优先挑金币多的去做,假设我们的d挺大的,我们的任务可以做好几轮。那么k+1就是一个轮回,在做完最多金币的任务之后,第二天肯定做次多金币的任务。在过了k天之后,肯定是赶紧再去做金币最多的任务,然后又是次多的那个任务。
所以k+1是一个周期,我们在d天内可以有 cycle = d / (k+1)个周期
然后会多出来remain = d % (k+1)天
另外就是计算金币的话不需要一天一天的去加,前面也说了,每次做任务肯定挑当前能做的,而且金币数量最多的去做。所以可以对金币的数组排序,再求前缀和。每个周期获得的金币自然就是sum[k+1](当然,如果周期长度大于任务的数量,那么我们做任务会有一段真空期,这段时间没有任务可以做)
代码
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;
int n; long long c, d, maxA;
vector<int>a;
vector<long long>sumA;
bool check(int k) {
// 一个周期可以做k+1个任务
int cycle = d / (k + 1);
// 还会剩几天
int remain = d % (k + 1);
long long sum = 0;
sum += sumA[min(k + 1, n)] * cycle;
sum += sumA[min(remain, n)];
return sum >= c;
}
bool cmp(int& a, int& b) {
return a > b;
}
void solve() {
cin >> n >> c >> d;
a.resize(n + 1);
sumA.assign(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a.begin() + 1, a.end(), [&](int a, int b) {return a > b; });
for (int i = 1; i <= n; i++) {
sumA[i] = sumA[i - 1] + a[i];
}
long long sum = 0;
// d天最多多少,每天都不一样
for (int i = 1; i <= min((int)d, n); i++) {
sum += a[i];
}
if (sum >= c) {
cout << "Infinity" << endl;
return;
}
maxA = a[1];
if (maxA * d < c) {
cout << "Impossible" << endl;
return;
}
int l = 0, r = 2e5;
int k = 0, mid;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
k = mid;
l = mid + 1;
}
else {
r = mid - 1;
}
}
cout << k << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _; _ = 1;
cin >> _;
while (_--) solve();
for (auto x : ans)cout << x << endl;
return 0;
}
G. SlavicG's Favorite Problem(DFS,树)
题意
给一颗树,点a和b,点a每次移动都会让自己的状态异或上边权,如果想要到达b点,那么到达b点的前一步,必须能够使得这时候的状态异或上去b那条边之后变成0(也就是到达b之前,状态必须和去b的那条边权一样,异或的结果才可能为0)。并且可以传送,但是不能直接传到b点
思路
因为传送的存在,所以可以让a出发,到所有点,记录所有能变成的状态。再从b出发,同样到所有点,也记录所有状态。如果发现这两堆状态,有相同的状态i。**a 异或得到了 i,b 异或得到了 i。让a先到 i 状态,然后传送到b异或成 i 的那个点,就可以到达b了。 **
由于树是连通的,而且没有环,所以a可以深搜到每一个点,得到每一个点的状态。b也同样会深搜到所有点。并且由于没有环,从同一个点出发的时候,每个点的状态也是唯一的,不会重复到达。
代码
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;
const int N = 1e5 + 5;
int n, m, a, b, cnt;
int head[N], state[N];
struct edge {
int to, nex, w;
}e[2 * N];
void add(int x, int y, int w) {
cnt++;
e[cnt] = { y,head[x],w };
head[x] = cnt;
}
void dfs(int u, int par) {
for (int j = head[u]; j != -1; j = e[j].nex) {
int v = e[j].to;
if (v == par || v == b)continue;
state[v] = state[u] ^ e[j].w;
dfs(v, u);
}
}
void solve() {
cin >> n >> a >> b;
cnt = 0;
memset(e, 0, sizeof(e));
memset(head, -1, sizeof(head));
memset(state, -1, sizeof(state));
for (int i = 0, u, v, w; i < n - 1; i++) {
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
state[a] = 0;
dfs(a, a);
set<int>s;
for (int i = 1; i <= n; i++) {
if (state[i] != -1)s.insert(state[i]);
}
memset(state, -1, sizeof(state));
state[b] = 0;
dfs(b, b);
for (int i = 1; i <= n; i++) {
if (i != b && s.count(state[i])) {
cout << "YES" << endl;
return;
}
}
cout << "NO" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _; _ = 1;
cin >> _;
while (_--) solve();
for (auto x : ans)cout << x << endl;
return 0;
}

浙公网安备 33010602011771号