前一段时间总是忙着学校的各种事情,好久没发过博客了,现在更新起来,从一场Codeforces的div2开始吧
A. Min Or Sum
题目大意
给你
n
n
n 个数记为
a
i
a_i
ai,你可以进行任意次数的操作,使得这
n
n
n 个数之和最小。
操作:选择两个不同的数
i
,
j
i, j
i,j ,以及两个非负整数
x
,
y
x, y
x,y,用
x
x
x 代替
a
i
a_i
ai,
y
y
y 代替
a
j
a_j
aj,且满足
a
i
∣
a
j
=
x
∣
y
a_i | a_j = x|y
ai∣aj=x∣y
思路
看到 或 操作,容易想到从二进制角度来考虑这些数,我们发现,对于这些数中已存在的二进制位上的这些 1 1 1,我们是无法通过上述操作删除的,但是我们总是有办法使得这些 1 1 1,在这么 n n n 个数中只出现一次。
所以将这 n n n 个数都或起来就行了。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 7;
void solve(){
int n;
cin >> n;
int sum = 0;
for(int i = 1; i <= n; i++){
int x;
cin >> x;
sum |= x;
}
cout << sum << "\n";
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
for(int i = 1; i <= t; i++){
solve();
}
}
B. Avoid Local Maximums
题目大意
给你
n
n
n 个数,让你进行最少次操作,使得这些数中不存在局部最大值(即不存在
a
i
−
1
<
a
i
<
a
i
+
1
a_{i-1} < a_i < a_{i+1}
ai−1<ai<ai+1)
操作:你可以将一个数变为任意一个数,记为一次操作
解题思路
首先我们考虑第 i i i 个位置为局部最大值,那我们改变第 i + 1 i+1 i+1 位置的值,我们考虑第 i + 1 i+1 i+1 位置的值改为 i i i 位置的值或者 i + 2 i+2 i+2 位置的值(尽可能让后面也没有局部最大值),判断一下即可。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 7;
void solve(){
int n;
cin >> n;
vector<int> a(n+1);
for(int i = 1; i <= n; i++){
cin >> a[i];
}
int num = 0;
for(int i = 2; i < n; i++){
if(a[i] > a[i-1] && a[i] > a[i+1]){
num++;
if(i + 3 <= n && a[i] < a[i+2] && a[i+2] > a[i+3]){
a[i+1] = a[i+2];
}
else{
a[i+1] = a[i];
}
}
}
cout << num << "\n";
for(int i = 1; i <= n; i++){
cout << a[i] << " \n"[i==n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
for(int i = 1; i <= t; i++){
solve();
}
}
C. Differential Sorting
题目大意
给你
n
n
n 个数,让你进行一些操作(不限定要最小次数),使得这
n
n
n 个数成为非递减的序列,输出操作次数以及如何操作的,如果不能成立则输出
−
1
-1
−1
操作:选三个不同的数作为下标
x
<
y
<
z
x < y < z
x<y<z,用
a
y
−
a
z
a_y - a_z
ay−az 代替
a
x
a_x
ax
解题思路
根据操作的特点我们发现,最后两个数的无法变化的,所以如果最后两个数不是有序的,那么一定无法满足条件。
首先判断最开始是否是有序的,如果有序直接输出 0 0 0 就行了。
再判断最后两个数是否有序,如果无序则不行
然后判断最后一个数是否大于等于 0 0 0,如果是的话,我们可以用最后两个数构造出前面 n − 2 n-2 n−2 个数,一直满足条件,否则不行(因为一个数减负数会让值变大)。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 7;
void solve(){
int n;
cin >> n;
vector<ll> a(n);
for(int i = 0; i < n; i++){
cin >> a[i];
}
vector<ll> b = a;
sort(b.begin(), b.end());
if(b == a){
cout << "0\n";
return;
}
if(a[n-2] > a[n-1]){
cout << "-1\n";
return;
}
if(a[n-1] < 0){
cout << "-1\n";
return;
}
else{
cout << n-2 << "\n";
for(int i = n-2; i >= 1; i--){
cout << i << " " << i+1 << " " << n << "\n";
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
for(int i = 1; i <= t; i++){
solve();
}
}
D. Infinite Set
题目大意
给你 n n n 个不同的数组成的数列 a a a,以及一个数 p p p,问你集合中最大元素不超过 2 p 2^p 2p ,这样的集合中有多少个元素。
集合的定义如下,集合中的元素满足一下三个条件中的任意一个
- x = a i x=a_i x=ai for some 1 ≤ i ≤ n . 1≤i≤n. 1≤i≤n.
- x = 2 y + 1 x=2y+1 x=2y+1 and y y y is in S . S. S.
- x = 4 y x=4y x=4y and y y y is in S . S. S.
解题思路
这样的题目看起来很费解,有点无从下手的感觉。
观察到 p p p 的范围很大 [ 1 , 2 × 1 0 5 ] [1, 2\times 10^5] [1,2×105], 数列 a a a 的范围 [ 1 , 1 × 1 0 9 ] [1, 1\times 10^9] [1,1×109]
可以发现,当 p p p 很大时 [ 2 p − 1 , 2 p − 1 ] [2^{p-1}, 2^p -1] [2p−1,2p−1] 中的元素只能通过上面的第二第三种方法生成出来,而且生成的数是不重复的,因为第二种方法生成的是奇数,而第三种方法生成的是偶数。
为了方便,我们定义
d
p
[
p
]
dp[p]
dp[p] 表示集合
S
S
S 中的元素,在
[
2
p
−
1
,
2
p
−
1
]
[2^{p-1}, 2^p -1]
[2p−1,2p−1] 范围中的个数,根据上面的观察,我们可以得出
d
p
[
i
]
=
d
p
[
i
−
1
]
+
d
p
[
i
−
2
]
dp[i] = dp[i-1] + dp[i-2]
dp[i]=dp[i−1]+dp[i−2]
当 p p p 不够大时,也就是在 30 30 30 以内时,我们需要考虑 d p [ p ] dp[p] dp[p] 中的元素不一定都是通过上述第二、三种方法生成的,而是来自于数列 a a a 中。
我们能否直接将数列 a a a 全部计入答案呢?实际上是不行的,因为数列 a a a 中的数可能也满足上述第二、三种条件,这样我们会重复计数。所以,我们需要对数列 a a a 进行预处理,挑出其中最 “基本” 的元素。这里我们直接按第二、三种方法删掉一些数就行了(见代码)。然后将这些数划分到对应的区间即可。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 7;
const ll mod = 1e9 + 7;
void solve(){
int n, p;
cin >> n >> p;
map<int, int> mp;
for(int i = 0; i < n; i++){
int x;
cin >> x;
mp[x] = 1;
}
set<int> v;
for(auto [x, y] : mp){
int t = x;
int f = 0;
while(t > 0){
if(v.count(t)){
f = 1;
break;
}
if(t & 1){
t = (t - 1) / 2;
}
else if(t % 4 == 0){
t /= 4;
}
else{
break;
}
}
if(!f){
v.insert(x);
}
}
vector cnt(32, 0ll);
for(auto x : v){
cnt[__lg(x)]++;
}
ll ans = 0;
vector dp(p, 0ll);
for(int i = 0; i < p; i++){
if(i < 32){
dp[i] = cnt[i];
}
if(i >= 1)
dp[i] = (dp[i] + dp[i-1]) % mod;
if(i >= 2)
dp[i] = (dp[i] + dp[i-2]) % mod;
ans = (ans + dp[i]) % mod;
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
// cin >> t;
t = 1;
for(int i = 1; i <= t; i++){
solve();
}
}
E. Cars
题目大意
数轴上有
n
n
n 辆车,两两不在同一个位置,然后每辆车有一个朝向(向左或向右),不能更改,让你构造出一个合法的排列情况(即每辆车的朝向与位置),满足以下条件。
1
i
j
1\ i\ j
1 i j —
i
i
i-th car and
j
j
j-th car 无论以什么样的速度行驶都不会相遇
2 i j 2\ i \ j 2 i j — i i i-th car and j j j-th car 无论以什么样的速度行驶都会相遇
解题思路
容易发现, 存在上述关系的两辆车,一定朝向相反
分析
- 如果无论以什么样的速度行驶都不会相遇。如果同向的话,会有追击相遇问题,只有背对背行驶,才能满足上述条件
- 如果无论以什么样的速度行驶都会相遇。如果同向的话,在前面的车速度快,那么不会相遇。只有面对面行驶,才能满足上述条件。
所以,这个图必须是可以二分图染色的,染完色的 01 01 01 表示 L R LR LR。
染完色之后怎么排列结果呢?我们最后的目的是要在数轴上排一个顺序以及确定方向,我们考虑从小到大排列。我们考虑如果 i i i 车与 j j j 车,不会相遇,当 i i i 车的方向为 L L L 时, 建立一个 i → j i \rightarrow j i→j 的边,否则建立相反的边。这样我们建立了一个有向无环图,进行一个拓扑排序可以得到最后的答案。
由于存在不合法的情况, 所以在其中要处理一些无解的情况。
Code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 7;
int n,m;
vector<int> v[MAXN];
vector<array<int, 3>> vv;
void solve() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int x, y, r;
cin >> r >> x >> y;
vv.push_back({r, x, y});
v[x].pb(y);
v[y].pb(x);
}
vector col(n+1, -1);
auto dfs = [&](auto self, int x) ->void{
for(int j : v[x]){
if(col[j] == -1){
col[j] = col[x] ^ 1;
self(self, j);
}
}
};
for(int i = 1; i <= n; i++) col[i] = -1;
for(int i = 1; i <= n; i++) {
if(col[i] == -1) {
col[i] = 0;
dfs(dfs, i);
}
}
vector<int> in(n+1), ans(n+1);
for(int i = 1; i <= n; i++) v[i].clear();
for(auto [r, x, y] : vv) {
if(col[x] == col[y]){
cout << "NO\n";
return;
}
if(r == col[x] + 1) {
v[x].push_back(y);
in[y]++;
} else {
v[y].push_back(x);
in[x]++;
}
}
queue<int> q;
for(int i = 1; i <= n; i++) {
if(!in[i]) q.push(i);
}
int cnt = 0;
while(!q.empty()) {
int tmp = q.front();
q.pop();
ans[tmp] = ++cnt;
for(int i : v[tmp]) {
in[i]--;
if(in[i] == 0)
q.push(i);
}
}
if(cnt == n) {
cout << "YES\n";
for(int i = 1; i <= n; i++)
cout << "LR"[col[i]] << " " << ans[i] << "\n";
} else {
cout << "NO\n";
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
// cin >> t;
t = 1;
for(int i = 1; i <= t; i++) {
solve();
}
}
F. Closest Pair
题目大意
在数轴上存在一些点,每个点有坐标 x i x_i xi 和一个权值 w i w_i wi,有 q q q 次询问,问你第 l l l 个点,到第 r r r 个点中,点对的最小值为多少。
点对的值定义为 ∣ x i − x j ∣ × ( w i + w j ) |x_i - x_j| \times (w_i + w_j) ∣xi−xj∣×(wi+wj)
我们可以将权值看做第二维,建立一个平面,通过观察可以发现一些性质。
观察上图(横轴为
x
x
x 轴),我们发现(1,3)永远不可能成为答案,因为(1,2),(2,3)更小,而(4,7)可能成为答案。这时我们可以发现,能成为答案的点对实际上并不多。两个点能成为答案,必须两个点之间不存在权值小于等于两个点权值的最大值。
为了求出这些点对,我们可以维护一个权值上升的单调栈,新加入栈中的值将权值比它小的弹出构成点对,然后与栈顶的点构成点对(这很重要,忘记了这一点会漏掉许多可能的值,这里本质上来说是相邻的两个点能够成答案)
处理完之后,我们最多得到 2 n 2n 2n 个点对,然后为了得到答案,我们枚举右端点,用树状数组来维护前缀最小值即可(这个数据结构要好好想想)。
Code
#include <bits/stdc++.h>
#define pb push_back
#define ll long long
const ll inf = 8e18;
using namespace std;
const int MAXN = 2e5 + 7;
template <typename T>
struct Fenwick {
const int n;
std::vector<T> a;
Fenwick(int n) : n(n), a(n+1, inf) {}
void update(int x, T v) {
for (int i = x; i <= n; i += i & -i) {
a[i] = min(a[i], v);
}
}
T sum(int x) {
T ans = inf;
for (int i = x; i > 0; i -= i & -i) {
ans = min(ans, a[i]);
}
return ans;
}
T rangeSum(int l, int r) {
return sum(r) - sum(l-1);
}
};
void solve() {
int n, q;
cin >> n >> q;
vector<ll> x(n+1), w(n+1);
Fenwick<ll> fen(n);
stack<int> st;
vector<pair<int, ll>> p[n+1];
auto add = [&](int i, int j){
p[j].push_back(make_pair(i, 1ll*(x[j] - x[i])*(w[i] + w[j])));
};
for(int i = 1; i <= n; i++){
cin >> x[i] >> w[i];
if(st.empty())
st.push(i);
else{
while(!st.empty() && w[st.top()] >= w[i]){
add(st.top(), i);
st.pop();
}
if(!st.empty()) add(st.top(), i);
st.push(i);
}
}
vector<pair<int, int>> que[n+1];
vector ans(q+1, 0ll);
for(int i = 1; i <= q; i++){
int l, r;
cin >> l >> r;
que[r].pb({l, i});
}
for(int i = 1; i <= n; i++){
for(auto [fi, se] : p[i]){
fen.update(n+1-fi, se);
}
for(auto [fi, se] : que[i]){
ans[se] = fen.sum(n+1-fi);
}
}
for(int i = 1; i <= q; i++)
cout << ans[i] << "\n";
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
// cin >> t;
t = 1;
for(int i = 1; i <= t; i++) {
solve();
}
}
比赛打得稀烂,还是赛后好好补题吧~