Codeforces Round #772 (Div. 2) A~F

前一段时间总是忙着学校的各种事情,好久没发过博客了,现在更新起来,从一场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 aiaj=xy

思路

看到 或 操作,容易想到从二进制角度来考虑这些数,我们发现,对于这些数中已存在的二进制位上的这些 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} ai1<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 ayaz 代替 a x a_x ax

解题思路

根据操作的特点我们发现,最后两个数的无法变化的,所以如果最后两个数不是有序的,那么一定无法满足条件。

首先判断最开始是否是有序的,如果有序直接输出 0 0 0 就行了。

再判断最后两个数是否有序,如果无序则不行

然后判断最后一个数是否大于等于 0 0 0,如果是的话,我们可以用最后两个数构造出前面 n − 2 n-2 n2 个数,一直满足条件,否则不行(因为一个数减负数会让值变大)。

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 ,这样的集合中有多少个元素。

集合的定义如下,集合中的元素满足一下三个条件中的任意一个

  1. x = a i x=a_i x=ai for some 1 ≤ i ≤ n . 1≤i≤n. 1in.
  2. x = 2 y + 1 x=2y+1 x=2y+1 and y y y is in S . S. S.
  3. 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] [2p1,2p1] 中的元素只能通过上面的第二第三种方法生成出来,而且生成的数是不重复的,因为第二种方法生成的是奇数,而第三种方法生成的是偶数。

为了方便,我们定义 d p [ p ] dp[p] dp[p] 表示集合 S S S 中的元素,在 [ 2 p − 1 , 2 p − 1 ] [2^{p-1}, 2^p -1] [2p1,2p1] 范围中的个数,根据上面的观察,我们可以得出
d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] dp[i]=dp[i1]+dp[i2]

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 无论以什么样的速度行驶都会相遇

解题思路

容易发现, 存在上述关系的两辆车,一定朝向相反

分析

  1. 如果无论以什么样的速度行驶都不会相遇。如果同向的话,会有追击相遇问题,只有背对背行驶,才能满足上述条件
  2. 如果无论以什么样的速度行驶都会相遇。如果同向的话,在前面的车速度快,那么不会相遇。只有面对面行驶,才能满足上述条件。

所以,这个图必须是可以二分图染色的,染完色的 01 01 01 表示 L R LR LR

染完色之后怎么排列结果呢?我们最后的目的是要在数轴上排一个顺序以及确定方向,我们考虑从小到大排列。我们考虑如果 i i i 车与 j j j 车,不会相遇,当 i i i 车的方向为 L L L 时, 建立一个 i → j i \rightarrow j ij 的边,否则建立相反的边。这样我们建立了一个有向无环图,进行一个拓扑排序可以得到最后的答案。

由于存在不合法的情况, 所以在其中要处理一些无解的情况。

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) xixj×(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();
	}
}

比赛打得稀烂,还是赛后好好补题吧~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值