cf1860f

发布时间 2023-09-17 09:36:59作者: weakpyt

萌萌题,但是细节比较麻烦。

首先注意到,\(ax+by=\lambda\),由于我们只需要若干括号的相对顺序,其中一个未知数完全可以舍去,因为可以通过另一个未知数达到相同值。

设我们只关心 \(x\) 的取值,变为按照 \(ax+b\) 排序。那么设 \(k'=a\),变成 \(\lambda =k'x+b\),这是一个直线方程,而我们需要选取一个 \(x\) 坐标。

那么如何判断一个括号序列合法呢?将左括号看作 \(1\),右括号看作 \(-1\)。则对于该括号序列而言,任意前缀之和不小于 \(0\),且全局和为零,即满足条件。

通过以上分析,原问题化为:给定若干条直线,第 \(i\) 条权值为 \(w_i(w_i\in \lbrace 1,-1\rbrace)\),需要选出一个 \(x\),使得将对应 \(y\) 坐标排序后,前缀和任意位置不小于零。

这是一个简单的问题。注意到 \(n\le 1500\),这说明直线不超过 \(3000\) 条,也即交点最多只有 \(9\times 10^6\) 个,而一看时间限制有十秒,稳稳地 \(n^2\log n\)

可以考虑将每一个交点处理出来。排序,从左往右处理每一个交点,并且动态维护前缀和即可。

详细地说:

根据直线求交公式:\(x=\frac{b_2-b_1}{k_1-k_2}\),我们处理出 \(x>0\) 的两条直线的交点。(注意选择的横坐标需要是正实数。)

然后记录一下这两条直线的编号。将这些交点排序,依次处理。

处理时,我们动态维护每个直线的排名 \(rk_i\),执行到点 \((x,y,p_1,p_2)\) 时,先撤销 \(p_1,p_2\) 对前缀和的影响,再互换二者的 \(rk\),加上其影响。

最后判断全局最小值是否小于零即可。

前缀和的维护可以使用线段树,执行区间修改和最小值维护。

说一些细节和实现上的技巧:

  1. 在最初的时候,我们以 \(x=0\) 做一次排序为最初的 \(rk\),这里可以方便地直接将所有直线排序。排序规则比较复杂,首先按 \(b\) 自小到大,\(b\) 相同的时候按照 \(k\) 自小到大排序。若 \(k\) 仍然相同,说明两直线重合,此时按照其权值贪心地自大到小即可。

  2. 在求交的时候需要注意通过对 \(a\) 的排序,我们只需要枚举 \((i,j),j>i\),但需要满足 \(k_i>k_j\)。注意是严格大于。

  3. 为了保证精度,交点的横坐标可以存下分子分母,否则可能 \(eps\) 需要设置的非常小才可以通过,比如 \(eps=10^{-18}\)

  4. 在对点排序时,先按照 \(x\) 坐标排序,若相同则按照左右两端点的权值排序。需要注意的是右端点优先左端点,原因是右端点在交换后排名更靠前。

  5. 在修改点的时候,若遇到 \(rk_l>rk_r\) 的情况,可以直接略去。

    这种情况是很巧妙的解决。此时会有多线共交,两个端点在与其他直线操作的时候,就已经更改了排名,此处是满足了更改后 \(rk'_l>rk'_r\) 的情况,故不用操作。

    事实上,这里它潜在地完成了一次排序操作,即将排名倒转。

    如果不进行该操作会在第三个点出错。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 3050
struct line{
	int k,b,w;
	bool operator<(const line t)const {
		return b==t.b?(k==t.k?w>t.w:k<t.k):b<t.b;
	} 
}a[N];
struct node{
	int l,r,lz,mn;
}t[N<<2];
#define lc x<<1
#define rc x<<1|1
void pushdown(int x){
	if(t[x].lz!=0){
		t[lc].mn+=t[x].lz;t[lc].lz+=t[x].lz;
		t[rc].mn+=t[x].lz;t[rc].lz+=t[x].lz;
		t[x].lz=0;
	}	
}
void pushup(int x){
	t[x].mn=min(t[lc].mn,t[rc].mn);
}
void build(int x,int l,int r){
	t[x].l=l,t[x].r=r,t[x].mn=t[x].lz=0;
	if(l==r)return ;
	int mid=l+r>>1;
	build(lc,l,mid);build(rc,mid+1,r);
	pushup(x);
}
void change(int x,int l,int r,int k){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].mn+=k,t[x].lz+=k;return ;
	}
	pushdown(x);
	if(t[lc].r>=l)change(lc,l,r,k);
	if(t[rc].l<=r)change(rc,l,r,k);
	pushup(x);
} 
#define db double
struct point{
	int p,q,l,r;
	bool operator<(const point  b)const {
		return p*b.q==q*b.p?(a[r].w==a[b.r].w?a[l].w<a[b.l].w:a[r].w>a[b.r].w):p*b.q<q*b.p;
	}
}p[N*N];
int tot,rk[N];
signed main(){
	ios::sync_with_stdio(false);
	int T;cin>>T;
	while(T--){
		int n;cin>>n;tot=0;
		n<<=1;build(1,1,n);
		for(int i=1;i<=n;i++){
			cin>>a[i].k>>a[i].b;char c;
			cin>>c;a[i].w=(c==')'?-1:1);
			rk[i]=i;
		} 
		sort(a+1,a+n+1);build(1,1,n);
		for(int i=1;i<=n;i++)change(1,i,n,a[i].w);
		for(int i=1;i<=n;i++)
			for(int j=n;j>i;j--)
				if(a[i].k>a[j].k)p[++tot]=(point){a[j].b-a[i].b,a[i].k-a[j].k,i,j};
		sort(p+1,p+tot+1);int tag=0;
		if(t[1].mn>=0){
			cout<<"Yes\n";continue;
		}
		for(int i=1;i<=tot;i++){
			int x=p[i].l,y=p[i].r;
			if(rk[x]>rk[y])continue;
			change(1,rk[x],n,-a[x].w);
			change(1,rk[y],n,-a[y].w);
			swap(rk[x],rk[y]);
			change(1,rk[x],n,a[x].w);
			change(1,rk[y],n,a[y].w);
			if(t[1].mn>=0){
				tag=1;break;
			}
		}
		if(tag)cout<<"Yes\n";
		else cout<<"No\n";
	}
} 
// 2 9
// x+7,2x+5,4x+1