前一段时间总是忙着学校的各种事情,好久没发过博客了,现在更新起来,从一场Codeforces的div2开始吧
A. Min Or Sum
题目大意
给你 nnn 个数记为 aia_iai,你可以进行任意次数的操作,使得这 nnn 个数之和最小。
操作:选择两个不同的数 i,ji, ji,j ,以及两个非负整数 x,yx, yx,y,用 xxx 代替 aia_iai,yyy 代替 aja_jaj,且满足 ai∣aj=x∣ya_i | a_j = x|yai∣aj=x∣y
思路
看到 或 操作,容易想到从二进制角度来考虑这些数,我们发现,对于这些数中已存在的二进制位上的这些 111,我们是无法通过上述操作删除的,但是我们总是有办法使得这些 111,在这么 nnn 个数中只出现一次。
所以将这 nnn 个数都或起来就行了。
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
题目大意
给你 nnn 个数,让你进行最少次操作,使得这些数中不存在局部最大值(即不存在 ai−1<ai<ai+1a_{i-1} < a_i < a_{i+1}ai−1<ai<ai+1)
操作:你可以将一个数变为任意一个数,记为一次操作
解题思路
首先我们考虑第 iii 个位置为局部最大值,那我们改变第 i+1i+1i+1 位置的值,我们考虑第 i+1i+1i+1 位置的值改为 iii 位置的值或者 i+2i+2i+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
题目大意
给你 nnn 个数,让你进行一些操作(不限定要最小次数),使得这 nnn 个数成为非递减的序列,输出操作次数以及如何操作的,如果不能成立则输出 −1-1−1
操作:选三个不同的数作为下标 x<y<zx < y < zx<y<z,用 ay−aza_y - a_zay−az 代替 axa_xax
解题思路
根据操作的特点我们发现,最后两个数的无法变化的,所以如果最后两个数不是有序的,那么一定无法满足条件。
首先判断最开始是否是有序的,如果有序直接输出 000 就行了。
再判断最后两个数是否有序,如果无序则不行
然后判断最后一个数是否大于等于 000,如果是的话,我们可以用最后两个数构造出前面 n−2n-2n−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
题目大意
给你 nnn 个不同的数组成的数列 aaa,以及一个数 ppp,问你集合中最大元素不超过 2p2^p2p ,这样的集合中有多少个元素。
集合的定义如下,集合中的元素满足一下三个条件中的任意一个
- x=aix=a_ix=ai for some 1≤i≤n.1≤i≤n.1≤i≤n.
- x=2y+1x=2y+1x=2y+1 and yyy is in S.S.S.
- x=4yx=4yx=4y and yyy is in S.S.S.
解题思路
这样的题目看起来很费解,有点无从下手的感觉。
观察到 ppp 的范围很大 [1,2×105][1, 2\times 10^5][1,2×105], 数列 aaa 的范围[1,1×109][1, 1\times 10^9][1,1×109]
可以发现,当 ppp 很大时 [2p−1,2p−1][2^{p-1}, 2^p -1][2p−1,2p−1] 中的元素只能通过上面的第二第三种方法生成出来,而且生成的数是不重复的,因为第二种方法生成的是奇数,而第三种方法生成的是偶数。
为了方便,我们定义 dp[p]dp[p]dp[p] 表示集合 SSS 中的元素,在[2p−1,2p−1][2^{p-1}, 2^p -1][2p−1,2p−1] 范围中的个数,根据上面的观察,我们可以得出
dp[i]=dp[i−1]+dp[i−2]dp[i] = dp[i-1] + dp[i-2]dp[i]=dp[i−1]+dp[i−2]
当 ppp 不够大时,也就是在 303030 以内时,我们需要考虑 dp[p]dp[p]dp[p] 中的元素不一定都是通过上述第二、三种方法生成的,而是来自于数列 aaa 中。
我们能否直接将数列 aaa 全部计入答案呢?实际上是不行的,因为数列 aaa 中的数可能也满足上述第二、三种条件,这样我们会重复计数。所以,我们需要对数列 aaa 进行预处理,挑出其中最 “基本” 的元素。这里我们直接按第二、三种方法删掉一些数就行了(见代码)。然后将这些数划分到对应的区间即可。
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
题目大意
数轴上有 nnn 辆车,两两不在同一个位置,然后每辆车有一个朝向(向左或向右),不能更改,让你构造出一个合法的排列情况(即每辆车的朝向与位置),满足以下条件。
1 i j1\ i\ j1 i j — iii-th car and jjj-th car 无论以什么样的速度行驶都不会相遇
2 i j2\ i \ j2 i j — iii-th car and jjj-th car 无论以什么样的速度行驶都会相遇
解题思路
容易发现, 存在上述关系的两辆车,一定朝向相反
分析
- 如果无论以什么样的速度行驶都不会相遇。如果同向的话,会有追击相遇问题,只有背对背行驶,才能满足上述条件
- 如果无论以什么样的速度行驶都会相遇。如果同向的话,在前面的车速度快,那么不会相遇。只有面对面行驶,才能满足上述条件。
所以,这个图必须是可以二分图染色的,染完色的 010101 表示 LRLRLR。
染完色之后怎么排列结果呢?我们最后的目的是要在数轴上排一个顺序以及确定方向,我们考虑从小到大排列。我们考虑如果 iii 车与 jjj 车,不会相遇,当 iii 车的方向为 LLL 时, 建立一个 i→ji \rightarrow ji→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
题目大意
在数轴上存在一些点,每个点有坐标 xix_ixi 和一个权值 wiw_iwi,有 qqq 次询问,问你第 lll 个点,到第 rrr 个点中,点对的最小值为多少。
点对的值定义为 ∣xi−xj∣×(wi+wj)|x_i - x_j| \times (w_i + w_j)∣xi−xj∣×(wi+wj)
我们可以将权值看做第二维,建立一个平面,通过观察可以发现一些性质。

观察上图(横轴为 xxx 轴),我们发现(1,3)永远不可能成为答案,因为(1,2),(2,3)更小,而(4,7)可能成为答案。这时我们可以发现,能成为答案的点对实际上并不多。两个点能成为答案,必须两个点之间不存在权值小于等于两个点权值的最大值。
为了求出这些点对,我们可以维护一个权值上升的单调栈,新加入栈中的值将权值比它小的弹出构成点对,然后与栈顶的点构成点对(这很重要,忘记了这一点会漏掉许多可能的值,这里本质上来说是相邻的两个点能够成答案)
处理完之后,我们最多得到 2n2n2n 个点对,然后为了得到答案,我们枚举右端点,用树状数组来维护前缀最小值即可(这个数据结构要好好想想)。
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();
}
}
比赛打得稀烂,还是赛后好好补题吧~


被折叠的 条评论
为什么被折叠?



