D. In Love
原题链接:https://codeforces.com/contest/1883/problem/D
题目大意:
维护一个区间的集合,然后每次加减区间后,判断集合中是否存在两个区间不相交。
思路:
方法1: 由于区间散布很大,所以常规的离线做法+离散化区间,这种区间问题容易想到差分,每次加区间就在对应位置上加 \(1\),然后每次查询数值最大的位置是否为当前集合内区间的个数,如果是那么在该点所有区间相交,否则存在一对区间不相交,差分虽然为 \(O(1)\),但查询为 \(O(n)\),但多次查询明显不行,所以利用线段树去实现,具体为查询区间 \(max\),且可区间修改的线段树,然后每次判断全部范围内的最大值,再结合上面说过的判断条件就做出来了。
还有因为 \(q\) 为 \(1e5\),所以端点至多为 \(2e5\),要注意 \(N\) 的取值!
方法2(官解):维护一下左端点(l)最大的线段和右端点(r)最小的线段,若他俩没交点显然存在,否则所有线段在 \(r\) ~ \(l\) 间相交。 这里只用两个 \(mutilset\) 即可。
时间复杂度 \(O(nlg(n))\)
代码(线段树):
cpp
#include <bits/stdc++.h>
using namespace std;
#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;
int n, m;
struct Node{
int l, r;
int mx;
int lazy;
}tr[4 * N];
void push_up(Node &U, Node L, Node R){
U.mx = max(L.mx, R.mx);
}
void push_up(int u){
push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r){
if(l == r) tr[u] = {l, r, 0, 0};
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
}
void push_down(int u){
auto &U = tr[u];
if(U.lazy == 0) return;
auto &L = tr[u << 1], &R = tr[u << 1 | 1];
L.mx += U.lazy, L.lazy += U.lazy;
R.mx += U.lazy, R.lazy += U.lazy;
U.lazy = 0;
}
void modify(int u, int l, int r, int c){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].mx += c;
tr[u].lazy += c;
}else{
push_down(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, c);
if(r > mid) modify(u << 1 | 1, l, r, c);
push_up(u);
}
}
void solve()
{
cin >> m;
vector<PII> rng(m);
vector<int> num, op(m);
string s;
for(int i = 0; i < m; i ++){
cin >> s;
op[i] = (s[0] == '+');
auto &[l, r] = rng[i];
cin >> l >> r;
num.push_back(l), num.push_back(r);
}
sort(all(num));
num.erase(unique(all(num)), num.end());
for(int i = 0; i < m; i ++){
auto &[l, r] = rng[i];
l = lower_bound(all(num), l) - num.begin() + 1;
r = lower_bound(all(num), r) - num.begin() + 1;
}
n = num.size() + 5;
build(1, 1, n);
int c = 0;
for(int i = 0; i < m; i ++){
auto [l, r] = rng[i];
if(op[i]) modify(1, l, r, 1), c ++;
else modify(1, l, r, -1), c --;
if(c > tr[1].mx) cout << "YES\n";
else cout << "NO\n";
}
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
E. Look Back
原题链接:https://codeforces.com/contest/1883/problem/E
题目大意:
给定 \(n\) 个数的序列,每次可以任选一个位置的数乘 \(2\),求使得序列不递减的最小操作次。
思路:
一开始直接暴力,喜提 \(wa\),后面没意识到最后一个数可以被构造乘好多 \(2\),进而爆 \(long long\),喜提 \(10\) 几发 \(wa\),(一个可爆 long long 的构造: \(1e9,1e9 - 1,1e9 - 2 ... 1e9 - 1e5 + 1\))。
用\(b[i]\)去表示第\(i\)个位置要乘以\(2\)的个数,这里要分两种情况如果:当\(a[i] >= a[i + 1]\) 的那么我们先令 \(b[i + 1] == b[i]\),除此之外,还要给\(b[i + 1]\) 加上一个 \(t\),使\(a[i + 1] * 2 ^ t ≥ a[i]\),令 \(p = (a[i] + a[i + 1] - 1) / a[i + 1]\),令 \(p\) 的最高位二进制\(1\)所在位数为\(lg(p)\),且显然我们可以给\(a[i + 1]\)乘的数的二进制位只能有一位 \(1\),所以当 \(p\) 只有一位\(1\)时,\(t = lg(p)\),否则\(t = lg(p) + 1\);当\(a[i] < a[i + 1]\)时,令\(p = a[i + 1] / a[i]\),当\(b[i] <= lg(p)\) 时,显然令 \(b[i + 1]=0\)即可,因为\(p >= 2^{b[i]}\),否则我们只需要使得\(p\)的最高位\(1\)移到\(b[i]\)即可。
时间复杂度:O(nlg(n))
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;
int n, m;
void solve()
{
cin >> n;
vector<ll> a(n + 1);
for(int i = 1; i <= n; i ++) cin >> a[i];
vector<int> b(n + 1, 0);
for(int i = 1; i < n; i ++){
if(a[i] >= a[i + 1]){
b[i + 1] = b[i];
ll p = (a[i] + a[i + 1] - 1) / a[i + 1];
ll s = p, c = 0;
while(s){
s -= s & -s;
c ++;
}
int t = __lg(p);
if(c > 1) t ++;
b[i + 1] += t;
}else{
ll p = a[i + 1] / a[i];
int t = __lg(p);
b[i + 1] = max(0, b[i] - t);
}
}
cout << accumulate(b.begin(), b.end(), 0ll) << endl;
// for(int i = 1; i <= n; i ++) cout << b[i] << ' ';
// cout << endl;
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
G2. Dances (Hard Version)
题目大意:
给定一个长度为\(n-1\)的序列\(a\),长度为\(n\)的序列\(b\),构造序列\(c\),\(c[1]\)的取值可为\(1\) ~ \(m\),\(c[2\) ~ \(n] = a\),\(c和b\)均可重排,每次操作可以从\(c和b\)中各取一个值丢弃,最后各剩余\(k\)个数,且满足\(c[i]<b[i]\) \(i∈[1,k]\),求\(c[1]\)所有情况下,对应\(k\)值最大的情况下操作数的总和。
思路:
其实就是一个匹配问题,直接排序后从小到大匹配即可(每个去匹配小于它最大的),只不过我们一开始不能确定\(c[1]\),所以先匹配\(c[1]\)之外的部分,看最后\(b[]\)中能剩下的的最大值为多少,然后分情况讨论即可。
时间复杂度:O(nlg(n))
代码:
#include <bits/stdc++.h>
using namespace std;
#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;
int n, m;
void solve()
{
cin >> n >> m;
multiset<int> st;
for(int i = 1; i < n; i ++){
int x;
cin >> x;
st.insert(x);
}
vector<int> b(n);
for(int i = 0; i < n; i ++) cin >> b[i];
sort(b.begin(), b.end());
ll res = 0, mx = 0;
for(int i = 0; i < n; i ++){
if(st.lower_bound(b[i]) != st.begin()){
st.erase(-- st.lower_bound(b[i]));
}else{
res ++;
mx = b[i];
}
}
ll ans = res * m;
if(m < mx) ans -= m;
else ans -= (mx - 1);
cout << ans << endl;
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}