Loading

20260412 紫题训练

P2497 [SDOI2012] 基站建设

设点 \(i\) 直接收到点 \(j\) 信号的最小代价为 \(\sqrt{x}\),由两圆相切的条件可知:

\[\sqrt{(x_i-x_j)^2+(x-r_j)^2}=x+r_j\\ (x_i-x_j)^2+(x-r_j)^2=(x+r_j)^2\\ (x_i-x_j)^2=4xr_j\\ x=\dfrac{(x_i-x_j)^2}{4r_j}\\ \]

DP,设 \(f_i\) 为基站 \(i\) 收到信号的最小代价。

转移式:

\[f_i=\min_{j=1}^i (f_j+\sqrt{\frac{(x_i-x_j)^2}{4r_j}})+v_i\\ =\min_{j=1}^i (f_j+\frac{x_i-x_j}{2\sqrt{r_j}})+v_i\\ =\min_{j=1}^i (\frac{x_i}{2\sqrt{r_j}}+f_j-\frac{x_j}{2\sqrt{r_j}})+v_i\\ \]

\(\min\) 里面的式子实际上是斜率为 \(\dfrac{1}{2\sqrt{r_j}}\),截距为 \(f_j-\dfrac{x_j}{2\sqrt{r_j}}\) 的直线在 \(x=x_i\) 处的值。

可以使用李超线段树优化,由于值域较大要动态开点,时间复杂度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define N 500005
using namespace std;
using db=double;
using ll=long long;
ll m,p[N];
int n,k,v[N],r[N];
db f[N],ans=INFINITY;
class SGT{
	#define v(i) tr[i].val
	#define l(i) tr[i].lch
	#define r(i) tr[i].rch
	private:
		struct Seg{
			db a,b;
			db get(int x){return a*x+b;}
		};
		struct node{
			Seg val;int lch,rch;
			node(){val={0,INFINITY},lch=rch=0;}
		}tr[N*20];int rt,idx;
		static bool cmp(Seg x,Seg y,int k){
			return x.get(k)>y.get(k);
		}
		void ins(Seg k,int &x,ll l=0,ll r=1e12){
			if(!x) x=++idx;
			int mid=l+r>>1;
			if(cmp(v(x),k,mid)) swap(v(x),k);
			if(l==r) return;
			if(cmp(v(x),k,l)) ins(k,l(x),l,mid);
			if(cmp(v(x),k,r)) ins(k,r(x),mid+1,r);
		}
		db query(int p,int &x,ll l=0,ll r=1e12){
			if(!x) return INFINITY;
			if(l==r) return v(x).get(p);int mid=l+r>>1;
			if(p<=mid) return min(v(x).get(p),query(p,l(x),l,mid));
			return min(v(x).get(p),query(p,r(x),mid+1,r));
		}
	public:
		void ins(Seg x){ins(x,rt);}
		db query(ll x){return query(x,rt);}
	#undef v
	#undef l
	#undef r
}tr;
int main(){
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld%d%d",p+i,r+i,v+i);
	tr.ins({0.5/sqrt(r[1]),(f[1]=v[1])-p[1]/(2*sqrt(r[1]))});
	for(int i=2;i<=n;i++)
		f[i]=tr.query(p[i])+v[i],
		tr.ins({0.5/sqrt(r[i]),f[i]-p[i]/(2*sqrt(r[i]))});
	for(int i=1;i<=n;i++) if(p[i]+r[i]>=m) ans=min(ans,f[i]);
	printf("%.3f",ans);
	return 0;
}

P3714 [BJOI2017] 树的难题

点分治,设当前分治中心为 \(x\)

\(x\)\(y\) 的路径权值为 \(w_y\)\(x\)\(y\) 的距离为 \(d_y\)

若不考虑分治中心处的重复计算,用线段树维护最大值。

线段树下标为 \(i\) 的位置存储 \(\max_{d_y=i} w_y\),若不存在这样的 \(y\) 则为 \(-\infty\)

这样对于每个递归到的节点 \(y\),拿 \(\max([\max(l-d_y,0),r-d_y])+w_y\) 尝试更新答案。

接下来考虑如何处理分治中心处的重复计算。

按颜色排序,相同的颜色一次性处理。

开两棵线段树记为 tr1,tr2。

tr1 与上面线段树相同,但要求是从 \(x\) 过去的颜色与当前处理颜色不同

tr2 与上面线段树相同,但要求是从 \(x\) 过去的颜色与当前处理颜色相同

处理颜色 \(i\) 的时候,用 \(\operatorname{tr1max}([\max(l-d_y,0),r-d_y])+w_y\)\(\operatorname{tr1max}([\max(l-d_y,0),r-d_y])+w_y-c_i\) 尝试更新答案。

#include<bits/stdc++.h>
#define N 200005
using namespace std;
using ll=long long;
const ll inf=0x3f3f3f3f3f3f3f3fll;
struct edge{
	int x,c;
	bool operator<(const edge &t){
		return c<t.c;
	}
};
bitset<N>vis;
vector<edge>s[N];
int siz[N];ll ans=-inf;
vector<pair<int,ll>>tmp;
int n,m,L,R,a[N],rt,Min;
class SGT{
	#define l(i) ((i)<<1)
	#define r(i) ((i)<<1|1)
	private:ll tr[N<<2];
	public:
		SGT(){memset(tr,-0x3f,sizeof tr);}
		void upd(int p,ll v,int x=1,int l=0,int r=n){
			if(l==r) return tr[x]=max(tr[x],v),void();
			int mid=l+r>>1;
			if(p<=mid) upd(p,v,l(x),l,mid);
			else upd(p,v,r(x),mid+1,r);
			tr[x]=max(tr[l(x)],tr[r(x)]);
		}
		void reset(int p,int x=1,int l=0,int r=n){
			if(l==r) return tr[x]=-inf,void();
			int mid=l+r>>1;
			if(p<=mid) reset(p,l(x),l,mid);
			else reset(p,r(x),mid+1,r);
			tr[x]=max(tr[l(x)],tr[r(x)]);
		}
		ll query(int ql,int qr,int x=1,int l=0,int r=n){
			if(ql<=l&&qr>=r) return tr[x];
			int mid=l+r>>1;ll res=-inf;
			if(ql<=mid) res=max(res,query(ql,qr,l(x),l,mid));
			if(qr>mid) res=max(res,query(ql,qr,r(x),mid+1,r));
			return res;
		}
	#undef l
	#undef r
}tr1,tr2;
void Find(int x,int tot,int fa=0){
	int mx=0;siz[x]=1;
	for(auto p:s[x])
		if(p.x^fa&&!vis[p.x])
			Find(p.x,tot,x),
			siz[x]+=siz[p.x],
			mx=max(mx,siz[p.x]);
	mx=max(mx,tot-siz[x]);
	if(mx<Min) Min=mx,rt=x;
}
void updroot(int x,int tot){Min=INT_MAX,Find(x,tot),Find(rt,tot);}
void upd(int x,int fa,int v,int lst,int d=1){
	if(vis[x]) return;tmp.emplace_back(d,v),tr2.upd(d,v);
	for(auto p:s[x]) if(p.x^fa) upd(p.x,x,v+(p.c^lst?a[p.c]:0),p.c,d+1);
}
void calc(int x,int fa,ll v,int c,int lst,int d=1){
	if(vis[x]||d>R) return;
	ans=max({ans,tr1.query(max(L-d,0),R-d)+v,tr2.query(max(L-d,0),R-d)+v-c});
	for(auto p:s[x]) if(p.x^fa) calc(p.x,x,v+(p.c^lst?a[p.c]:0),c,p.c,d+1);
}
void solve(int x){
	vector<int>arr;vector<edge>son;
	int lst=0;vis[x]=true,tmp.clear();
	for(auto p:s[x]) if(!vis[p.x]) 
		son.emplace_back(p);
	sort(son.begin(),son.end());
	for(auto p:son){
		if(p.c^lst){
			for(auto p:tmp)
				tr2.reset(p.first),
				tr1.upd(p.first,p.second),
				arr.emplace_back(p.first);
			tmp.clear(),lst=p.c;
		}
		calc(p.x,x,a[p.c],a[p.c],p.c);
		upd(p.x,x,a[p.c],p.c);
	}
	for(auto p:arr) tr1.reset(p);
	for(auto p:tmp) tr2.reset(p.first);
	for(auto p:s[x]) if(!vis[p.x])
		updroot(p.x,siz[p.x]),solve(rt);
}
int main(){
	tr1.upd(0,0);
	scanf("%d%d%d%d",&n,&m,&L,&R);
	for(int i=1;i<=m;i++) scanf("%d",a+i);
	for(int i=1,u,v,w;i<n;i++)
		scanf("%d%d%d",&u,&v,&w),
		s[u].push_back({v,w}),
		s[v].push_back({u,w});
	updroot(1,n),solve(rt),printf("%lld",ans);
	return 0;
}

P4567 [AHOI2006] 文本编辑器

光标的位置显然好维护,只需要支持区间插入,区间删除,区间翻转,单点查询。

类似文艺平衡树用 FHQ Treap 维护区间,区间翻转与那题相同,交换左右儿子并打懒标记。

区间插入:在操作的位置裂开,按左边+插入的字符串+右边的顺序合并。

区间删除:在区间的左右端点裂开,再将区间的左右两边合并起来。

单点查询:将左右两边裂开获得询问位置,记得合并回去。

#include<bits/stdc++.h>
#define N 2100000
#define l(i) tr[i].lch
#define r(i) tr[i].rch
#define v(i) tr[i].val
#define s(i) tr[i].siz
#define p(i) tr[i].pri
#define f(i) tr[i].flag
using namespace std;
mt19937 Rand(20120412);
struct node{
	bool flag;char val;
	int lch,rch,siz,pri;
	node(){}
	node(int x){
		lch=rch=flag=0;
		val=x,siz=1,pri=Rand();
	}
}tr[N];
string s;
int n,m,rt,idx;
void up(int x){if(x) s(x)=s(l(x))+s(r(x))+1;}
void down(int x){
	if(!f(x)) return;
	f(l(x))^=1,f(r(x))^=1;
	swap(l(x),r(x)),f(x)=false;
}
void split(int x,int &l,int &r,int v){
	if(!x) return l=r=0,void();
	down(x);
	if(v<=s(l(x))) r=x,split(l(x),l,l(r),v);
	else l=x,split(r(x),r(l),r,v-s(l(x))-1);
	up(x);
}
void merge(int &x,int l,int r){
	if(!l||!r) return x=l+r,void();
	if(p(l)<p(r)) down(x=l),merge(r(x),r(l),r);
	else down(x=r),merge(l(x),l,l(r));
	up(x);
}
void rev(int l,int r){
	int x,y,z;
	split(rt,x,y,r);
	split(x,x,z,l-1);
	f(z)^=1;
	merge(x,x,z);
	merge(rt,x,y);
}
void del(int l,int r){
	int x,y,z;
	split(rt,x,y,r);
	split(x,x,z,l-1);
	merge(rt,x,y);
}
void ins(int p,string s){
	int x,y;split(rt,x,y,p);
	for(auto p:s) tr[++idx]=p,merge(x,x,idx);
	merge(rt,x,y);
}
void print(int p){
	int x,y,z;
	split(rt,x,y,p),split(x,x,z,p-1);
	putchar(v(z));v(z)^'\n'?putchar('\n'):0;
	merge(x,x,z),merge(rt,x,y);
}
int main(){
	scanf("%d",&n);
	for(int ptr=0;n--;){
		cin>>s;int k;
		if(s=="Move") scanf("%d",&k),ptr=k;
		if(s=="Insert"){
			string tmp;
			scanf("%d",&k),getchar();
			while(k--) tmp+=getchar();
			ins(ptr,tmp);
		}
		if(s=="Delete") scanf("%d",&k),del(ptr+1,ptr+k);
		if(s=="Rotate") scanf("%d",&k),rev(ptr+1,ptr+k);
		if(s=="Get") ptr==idx?putchar('\n'):(print(ptr+1),0);
		if(s=="Prev") ptr--;
		if(s=="Next") ptr++;
	}
	return 0;
}
posted @ 2026-04-16 10:01  Jokersen  阅读(7)  评论(0)    收藏  举报