上午题

  • T1
    考虑让左括号为 \(1\),右括号是 \(-1\),做一遍前缀和,考虑交换两个位置,若交换的位置是 \(i\)
  1. \(s_i=s_{i+1}\),交换 \(i\)\(i+1\) 没有任何意义。
  2. 否则,一定会让 \(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\) 级别,可以通过。

  • AGC035F

  • AGC033D

  • CF1750D
    这么经典的反演为啥不会做啊/fn
    问题转化以后其实就是求 \(\gcd(i,m)=1\) 的数的个数,考虑反演,随便做做就行了。

  • CF1750E