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