上午题
- T1
考虑让左括号为 \(1\),右括号是 \(-1\),做一遍前缀和,考虑交换两个位置,若交换的位置是 \(i\)。
- 若 \(s_i=s_{i+1}\),交换 \(i\) 与 \(i+1\) 没有任何意义。
- 否则,一定会让 \(i\) 这个位置的值 \(+2\) 或 \(-2\)。
显然,我们的目标是让所有位置 \(\geq 0\),所以 \(-2\) 没有任何意义,考虑所有是负数的位置,不断 \(+2\) 直到值 \(\geq 0\) 为止。
- T2
考场垃圾做法:
考虑最终线段数量一定 \(\leq 2\)。
显然,如果线段有交,那么显然可以随便用 set 维护出所有线段的交集,设端点为 \(l,r\),那么我们可以维护出左端点为 \(l\) 的线段的 \(r\) 的最小值,右端点为 \(r\) 的 \(l\) 的最大值,两者相减即可。
如果交集为空集,我们考虑其实就是要求出两条不交线段,使得 \(rmax-lmin\) 最小。
先开 2e6 个 set,维护出来左端点为 \(l\) 的 rmin,右端点为 \(r\) 的 lmax。
考虑没有删除,我们只需要维护距离每个线段最近的线段(距离定义根据提题意编一编),这就要求我们求出左端点 \(\geq r\) 的所有线段中,右端点最小的那个,这部分可以用线段树,由于 set 只是作为底层的值出现,所以这部分是 \(O(n\log n)\) 的。
删除只需要开一个堆,维护线段长度,最短的线段编号,另外的一个线段编号,这部分均摊下来也是 \(n\log n\) 的。
std 做法:
又短又好写,只考虑交集为空的情况,只有这部分是困难的。
考虑用线段树维护,区间 \([l,r]\) 代表分界点包括在 \([l,r]\) 的线段组的长度,转移分类讨论即可,代码如下:
点击查看代码
#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
#define pb emplace_back
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
inline int rd()
{
int s = 0, w = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')
w = -1;
ch = getchar();
}
while (isdigit(ch))
{
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
const int N=1e6+100,inf=1e9;
int m;
struct maxHP
{
priority_queue<int>s,t;int cnt;
void push(int x){s.push(x);cnt++;}
void pop(int x){t.push(x);cnt--;}
int size() {return cnt;}
int top(){
while(t.size()&&s.top()==t.top()) s.pop(),t.pop();
return s.top();
}
}u[N],p;
struct minHP
{
priority_queue<int,vector<int>,greater<int> >s,t;int cnt;
void push(int x){s.push(x);cnt++;}
void pop(int x){t.push(x);cnt--;}
int size() {return cnt;}
int top(){
while(t.size()&&s.top()==t.top()) s.pop(),t.pop();
return s.top();
}
}v[N],q;
//multiset<int>u[N],v[N];
//multiset<int>p,q;
struct node{
int l,r,mn,mxl,mnr;
}t[N<<2];
void build(int x,int l,int r)
{
t[x].l=l,t[x].r=r;
t[x].mn=inf;t[x].mnr=inf;t[x].mxl=-inf;
if(l==r) return;
int mid=l+r>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
}
void pushup(int x)
{
t[x].mn=min(t[x<<1].mn,t[x<<1|1].mn);
t[x].mn=min(t[x].mn,t[x<<1|1].mnr-t[x<<1].mxl);
t[x].mxl=max(t[x<<1].mxl,t[x<<1|1].mxl);
t[x].mnr=min(t[x<<1].mnr,t[x<<1|1].mnr);
}
void change(int x,int pos)
{
if(t[x].l==t[x].r){
if(u[pos].size()) t[x].mxl=u[pos].top();
else t[x].mxl=-inf;
if(v[pos].size()) t[x].mnr=v[pos].top();
else t[x].mnr=inf;
t[x].mn=t[x].mnr-t[x].mxl;
return ;
}
int mid=t[x<<1].r;
if(pos<=mid) change(x<<1,pos);
else change(x<<1|1,pos);
pushup(x);
}
signed main()
{
m=rd();
build(1,1,1e6);
while(m--)
{
int op=rd(),l=rd(),r=rd();
if(op&1){
p.push(l),q.push(r);
u[r].push(l);v[l].push(r);
}
else
{
p.pop(l);q.pop(r);
u[r].pop(l);v[l].pop(r);
}
change(1,l);change(1,r);
l=p.top(),r=q.top();
if(l>=r){
cout<<t[1].mn<<'\n';
}
else
{
int R=v[l].top(),L=u[r].top();
cout<<R-L<<'\n';
}
}
return 0;
}
题目:
-
AGC058D
虑定义一个连续的 \(abc,bca,cab\) 是极长的上升子段,考虑若干个极长的上升子段一定是不交的,所以这么定义没有问题。
考虑容斥,顷定有 \(k\) 个极长上升子段,每一段的限制是说这一段开头必须是 \(abc,bca,cab\) 的形式,并且 $\neq $ 上一段 \(+1\)(mod 3) 意义下。
先把没有顷定的位置随便选,考虑对于连续段,在他前面填好的情况下,他有 \(2\) 种方案,否则有 \(3\) 种,剩下的都可以组合数,然后枚举一下这段开头是否是极长连续段,随便组合数一下可能就行了。 -
AGC035D
考虑删的最后一个数,假设是 \(i\),那么显然 \(i\) 的值对左右的贡献都是 \(1\),那么 \([1,i-1]\) 的值加到左边的贡献是 \(1\),加到 \(i\) 的贡献是 \(2\)(因为 \(i\) 还要再加),这启发我们进行 dp。
设 \(f(l,r,a,b)\) 代表我们考虑区间 \([l,r]\) ,他加到左边会贡献 \(a\) 次,加到右边会贡献 \(b\) 次的答案,显然有如下转移:
\(f(l,r,a,b)=\min(f(l,p-1,a,a+b)+f(p+1,r,a+b,b)+(a+b)x_i)\)。(这里认为 \(x_i\) 是序列)。
这么转移显然是 \(2^n n^2\) 级别,可以通过。 -
CF1750D
这么经典的反演为啥不会做啊/fn
问题转化以后其实就是求 \(\gcd(i,m)=1\) 的数的个数,考虑反演,随便做做就行了。
本文来自博客园,作者:zjrqwq,转载请注明原文链接:https://www.cnblogs.com/zjrqwq/p/16911850.html
浙公网安备 33010602011771号