22.12.28 winter training.1

A - Searching Local Minimum

https://codeforces.com/problemset/problem/1480/C

题意

交互题 有一个1~n的序列 最多询问100次 问i位置上的数是什么 最后要找出一个局部最小值(极小值)的位置

思路

二分 每次询问mid 和 mid+1的位置上的值
如果mid位置上的值比mid+1位置上的值小 r->mid-1 记录答案 mid(这里存在边界问题)
如果mid位置上的值比mid+1位置上的值大 l->mid+1 记录答案 mid+1
因为每次都往值更小的方向缩 所以最后二分出来的肯定是比它两边都小的值

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 5;
const int M = 5e5 + 5;
const ll mod = 1e9 + 7;

int n, a[N];

void query(int x){
	cout << "? " << x << "\n";
	cout.flush();
	cin >> a[x];
}

void solve()
{
	cin >> n;
	if(n == 1){
		cout << "! " << 1 << "\n";
		cout.flush();
		return;
	}

	query(1);
	query(2);
	query(n);
	query(n - 1);

	a[0] = a[n + 1] = n + 1;
	if(a[n] < a[n - 1]){
		cout << "! " << n << "\n";
		cout.flush();
		return;
	}

	if(a[1] < a[2]){
		cout << "! " << 1 << "\n";
		cout.flush();
		return;
	}

	int l = 2, r = n;
	int now = a[2], ans = 2;
	while(l <= r){
		int mid = (l + r) / 2;
		query(mid);
		if(!a[mid + 1]) query(mid + 1);
		if(a[mid + 1] > a[mid]){
			r = mid - 1;
			ans = mid;
			//now = a[mid];
		}
		else {
			l = mid + 1;
			//now = a[mid + 1];
			ans = mid + 1;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();
}


signed main()
{
	//IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

B - Painting the Array I

https://codeforces.com/problemset/problem/1480/D1

题意

给定一个长度为n的数组 将这个数组分成两个集合 数字按原顺序排列 然后合并两个集合中相邻且相同的数 问最后两个集合大小和最大是多少

思路

顺序遍历原数组 判断当前一个数放到那个集合中最优
每次让当前这个数与两集合中最后一个数比较
如果和一个数相同与另一个数不同 那就将这个数放到不同的那个数后面
如果都相同 就直接丢掉
如果都不相同 那就判断两个末尾数哪一个数下次出现最近 将当前这个数放到下一次出现更早的那个数后面,这样就保证更不容易出现相同的数

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 5;
const int M = 5e5 + 5;
const ll mod = 1e9 + 7;

int n, a[N];

void solve()
{
	cin >> n;
	for(int i = 1; i <= n; i++)
		cin >> a[i];

	if(n <= 2){
		cout << n << "\n";
		return;
	}
	stack<int>st1, st2;

	for(int i = 1; i <= n; i++){
		int num1 = -1, num2 = -1;
		if(st1.size()) num1 = st1.top();
		if(st2.size()) num2 = st2.top();

		if(num1 < 0 && num2 < 0) {
			st1.push(a[i]);
			continue;
		}
		
		if(num1 < 0 && num2 > 0){
			if(a[i] != num2) st2.push(a[i]);
			else st1.push(a[i]);
			continue;
		}

		if(num1 > 0 && num2 < 0){
			if(a[i] != num1) st1.push(a[i]);
			else st2.push(a[i]);
			continue;
		}

		if(a[i] == num1 && a[i] == num2) continue;
		if(a[i] != num1 && a[i] == num2) st1.push(a[i]);
		else if(a[i] != num2 && a[i] == num1) st2.push(a[i]);
		else {
			if(i < n && a[i + 1] == st2.top()) st2.push(a[i]);
			else st1.push(a[i]);
		}
	}

	cout << (int)st1.size() + (int)st2.size() << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

C - Painting the Array II

上面b题的求最小值版 和上面b题的做法一样 就尽量让当前数与两个尾数相等

D - Continuous City

https://codeforces.com/problemset/problem/1480/E

题意

给定一个区间[l, r] 问是否能构造出一个带权有向图 使得1到u的路径有r - l + 1条 且路径的权值恰好为l到r的每一个数
给出方案(n<=32)

思路

首先这样的图一定是可以构造的 因为\(2^{30}>1e6\)的 其实差不多23个点就可以构造出来
那么我们不妨将n设为23 然后用二进制去考虑
我们将l和r都减去l 即区间变成[0, r - l] 然后让1和[2,23]中每个点都连一条权值为l的边 这样只要考虑构造[0, r-l]就好了
对于在[2, 22]区间内的点i 我们都把它们看作二进制上的第i - 2位,每一位都向它后面的点(除了第23号点)连一条\(2^{i - 2}\)的边
这样如果我们选着a到b段的点 那么就可以表示\([2^{a - 2}, 2^{b - 2} - 1]的值的路径了\)
如果路径经过第i个点(不算上除23外的最后一个点)那么路径权值就会加 \(2^{i - 2}\)
然后我们从高位开始遍历r-l的二进制位 如果第i位是1 那么我们就把对应位的点(i+2)和最后一个点连一条权值为 val = 前缀值(前面位置上是1的位数值加起来)+1 的边
这样连 我们就可以表示出 \([val, val + 2^{i - 2} - 1]\)的值

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n;
vector<pair<pair<int, int>, int>>ans;
void add(int u, int v, int w){
	ans.push_back({{u, v}, w});
}

void solve()
{
	int l, r;
	cin >> l >> r;

	r -= l;
	for(int i = 2; i <= 23; i++)
		add(1, i, l);

	for(int i = 2; i <= 22; i++){
		for(int j = i + 1; j <= 22; j++){
			add(i, j, (1ll<<(i - 2)));
		}
	}

	int pre = 1;
	for(int i = 30; i >= 0; i--){
		if(r & (1ll<<i)){
			add(i + 2, 23, pre);
			pre += (1ll << i);
		}
	}

	cout << "YES\n";
	cout << 23 << " " << ans.size() << "\n";
	for(auto it : ans){
		int u = it.first.first;
		int v = it.first.second;
		int w = it.second;
		cout << u << " " << v << " " << w << "\n";
	}
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

G - Destroyer Takahashi

题意

给n段区间
一个操作 选定一个定长len的区间 让部分或全部出现在选定区间范围内的所有区间都消失
问让所有区间都消失的最小操作次数

思路

n段区间 按右边界排序 然后将每段区间都放进按左边边界排序的优先队列中
遍历按右边界排好序的序列
每次选定的消失区间是 l = 最前面没有消失的序列的右边界, \(r = l + len - 1\),然后在队列中左边界小于消失区间右边界的都消失 标记消失
不用管右边界,因为是按右边界小到大排序的,队列中还没删的区间的右边界肯定>=l
最后计一下用了几个消失区间即可

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 5;
const int M = 5e5 + 5;
const ll mod = 1e9 + 7;

int n, k, vis[N];
pair<int, int>p[N];
void solve()
{
	cin >> n >> k;
	for(int i = 1, l, r; i <= n; i++){
		cin >> l >> r;
		p[i] = {r, l};
	}

	sort(p + 1, p + 1 + n);

	priority_queue<pair<pair<int, int>, int>, vector<pair<pair<int, int>, int>>, greater<pair<pair<int, int>, int>>>q;
	for(int i = 1; i <= n; i++){
		int l = p[i].second;
		int r = p[i].first;
		q.push({{l, r}, i});
	}

	int ans = 0;
	for(int i = 1; i <= n; i++){
		if(vis[i]) continue;
		ans++;
		int pos = p[i].first + k - 1;
		while(q.size()){
			auto now = q.top();
			int l = now.first.first;
			if(l <= pos){
				vis[now.second] = 1;
				q.pop();
			}
			else break;
		}
	}

	cout << ans << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

H - Fraction Floor Sum

整除分块板子题 注意一下 k = n / l就是当前整除的值 用这个去找整除值相同的右边界即可

I - Predilection

https://atcoder.jp/contests/abc230/tasks/abc230_f?lang=en

题意

给定一个长度为n的序列
可以合并若干个连续的数 若干次 问你最后不同的数组有多少个

思路

首先可以得出一个结论 对于一个全正长度为n的数组 你最多可以有\(2^(n - 1)\)个不同的序列
我们可以用dp来写
dp[i]: 1-i这段数组 经过合并操作最多可以形成不同数组的数量
那么 \(dp[i] = dp[i - 1] * 2\)
除此之外我们还要考虑 可能会有重复的情况:
就列如:
1 2 3 0 4
1 2 3+0 4 与 1 2 3 0+4重复了
所以对于某个和为0的区段 它下一位置的dp值得减去它上一位置的dp值(前面是重复的)
那么我们就要记录一下前缀 当某个位置的前缀在前面已经出现过 那它就会影响下一位置的dp值
\(dp[i] = dp[i] - dp[pos[pre[i - 1]]]\)

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, a[N], pre[N], dp[N];

void solve()
{
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
	}

	map<int, int>pos;
	dp[1] = 1;
	for(int i = 2; i <= n; i++){
		dp[i] = dp[i - 1] * 2 % mod;
		if(pos[pre[i - 1]])
			dp[i] = (dp[i] - dp[pos[pre[i - 1]]] + mod) % mod;
		
		pos[pre[i - 1]] = i - 1;
	}

	cout << dp[n] << "\n"; 
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}
posted @ 2022-12-29 10:20  Yaqu  阅读(13)  评论(0编辑  收藏  举报