cf1860f

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

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

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

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

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

这是一个简单的问题。注意到 n1500,这说明直线不超过 3000 条,也即交点最多只有 9×106 个,而一看时间限制有十秒,稳稳地 n2logn

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

详细地说:

根据直线求交公式:x=b2b1k1k2,我们处理出 x>0 的两条直线的交点。(注意选择的横坐标需要是正实数。)

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

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

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

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

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

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

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

  3. 为了保证精度,交点的横坐标可以存下分子分母,否则可能 eps 需要设置的非常小才可以通过,比如 eps=1018

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

  5. 在修改点的时候,若遇到 rkl>rkr 的情况,可以直接略去。

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

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

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

#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
posted @   spdarkle  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示