20250926模拟赛 T2 题解

题目描述

ZLOIers\text{ZLOIers}ZLOIers 喜欢 Play LOL\text{Play LOL}Play LOL

一次放假,nnnZLOIers\text{ZLOIers}ZLOIers 准备 Play\text{Play}Play

每个 ZLOIer\text{ZLOIer}ZLOIer 的空闲时间可以用一段区间表示[ai,bi)[a_i,b_i)[ai,bi)

现在 nnn 个人准备分成kkkPlay\text{Play}Play

每个人恰好加入一组,每组至少一个人 。

每组能够 Play\text{Play}Play 的时间取决于每个人时间的交。

并且要求每组的人都能够一起的时间大于 000

求最大化所有组 Play 的总时间。

输入格式

第一行包括两个整数 nnnkkk

接下来 nnn 行每行包括两个整数 aia_iaibib_ibi

输出格式

输出最大总时间,每组只算进答案一次。

如果无解,输出 000

样例 #1

样例输入 #1

4 2
1 3
1 5
4 6
2 7

样例输出 #1

4

提示

20%20\%20% 数据,n≤15,k≤15n \leq 15, k \leq 15n15,k15

40%40\%40% 的数据,n≤100,k≤100n\leq 100,k\leq 100n100,k100

60%60\%60% 的数据,n≤1000,k≤1000n\leq 1000,k\leq 1000n1000,k1000

80%80\%80% 的数据,n≤3000,k≤3000n\leq 3000,k\leq 3000n3000,k3000

100%100\%100% 的数据,n≤8000,k≤8000,1<a<b<105n\leq 8000,k\leq 8000,1\lt a\lt b\lt 10^5n8000,k8000,1<a<b<105

解题思路

考虑使用树形 dp 统计答案。

注意到,对于每条路径 (x,y)(x,y)(x,y),其关键节点为 lca(x,y)lca(x,y)lca(x,y)。所以我们在此处更新答案。

fxf_xfx 表示 xxx 为根的子树的答案,gxg_xgx 表示以 xxx 的所有儿子为根的子树的答案的和,ppp 为与该路径有交点的已经选取的路径的总数,aia_iaibib_ibi 为路径 iii 的两端点,EEE 为两端点最近公共祖先为 xxx 的路径的集合,显然有转移方程 fx=max⁡(gx,max⁡ii∈Egx−p+1)f_x=\max(g_x,\max_{i}^{i∈E} g_x-p+1)fx=max(gx,maxiiEgxp+1)

容易发现,对 ppp 的统计是时间上的瓶颈所在。此处可以记录以每个点是否被作为已选取的路径的最近公共祖先,如是则其点权为 111,则对 ppp 的计算转化为对 aia_iaibib_ibi 的路径上的点权求和,然后就可以用树链剖分和线段树维护了。注意更新 fxf_xfx 时注意记录 fxf_xfx 是否、被哪条路径更新,更新完后先对路径上所有节点权值赋值为 000,然后再对 xxx 的点权 +1+1+1。时间复杂度 O(nlog⁡2n)O(n \log^2 n)O(nlog2n)

参考代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define ls(p) p<<1
#define rs(p) p<<1|1
const int N=2e5+10;

namespace IO{
	inline int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; }
	inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); }
}using namespace IO;

namespace code{
	int n,m,head[N],tot=1,fa[N],son[N],top[N],deep[N],size[N],dfn[N],cnt,f[N],g[N];
	
	struct node{ int x,y; };
	stack<node> st[N];
	
	struct edge{ int ver,next; }t[N<<1];
	void add(int x,int y){ t[++tot].ver=y,t[tot].next=head[x],head[x]=tot; }
	
	void dfs1(int x){
		size[x]=1;
		int maxson=-1;
		for(int i=head[x];i;i=t[i].next){
			int y=t[i].ver;
			if(y==fa[x]) continue;
			fa[y]=x,deep[y]=deep[x]+1,dfs1(y),size[x]+=size[y];
			if(size[y]>maxson) son[x]=y,maxson=size[y];
		}
	}
	
	void dfs2(int x,int fr){
		top[x]=fr,dfn[x]=++cnt;
		if(son[x]) dfs2(son[x],fr);
		for(int i=head[x];i;i=t[i].next){
			int y=t[i].ver;
			if(y==fa[x]||y==son[x]) continue;
			dfs2(y,y);
		}
	}
	
	struct Segment_Tree{
		struct t_node{
			int sum,lz_tag;
		}t[N<<2];
		
		void push_up(int p){
			t[p].sum=t[ls(p)].sum+t[rs(p)].sum;
		}
		
		void push_down(int p,int l,int r){
			if(t[p].lz_tag==-1) return;
			int mid=(l+r)>>1;
			t[ls(p)].lz_tag=t[rs(p)].lz_tag=t[p].lz_tag,t[ls(p)].sum=t[p].lz_tag*(l-mid+1),t[rs(p)].sum=t[p].lz_tag*(r-mid);
			t[p].lz_tag=-1;
		}
		
		void build(int p,int l,int r){
			t[p].lz_tag=-1;
			if(l==r) return;
			int mid=(l+r)>>1;
			build(ls(p),l,mid),build(rs(p),mid+1,r);
		}
		
		void update(int p,int l,int r,int ul,int ur,int k){
			if(ul<=l&&r<=ur) return t[p].lz_tag=k,t[p].sum=k*(r-l+1),void();
			push_down(p,l,r);
			int mid=(l+r)>>1;
			if(ul<=mid) update(ls(p),l,mid,ul,ur,k);
			if(ur>mid) update(rs(p),mid+1,r,ul,ur,k);
			push_up(p);
		}
		
		void add(int p,int l,int r,int pos,int k){
			if(l==r) return t[p].sum+=k,void();
			push_down(p,l,r);
			int mid=(l+r)>>1;
			if(pos<=mid) add(ls(p),l,mid,pos,k);
			else add(rs(p),mid+1,r,pos,k);
			push_up(p);
		}
		
		int query(int p,int l,int r,int ql,int qr){
			if(ql<=l&&r<=qr) return t[p].sum;
			push_down(p,l,r);
			int mid=(l+r)>>1,ret=0;
			if(ql<=mid) ret=query(ls(p),l,mid,ql,qr);
			if(qr>mid) ret+=query(rs(p),mid+1,r,ql,qr);
			return ret;
		}
	}tr;
	
	int lca(int x,int y){
		int bx=0,by=0;
		while(top[x]!=top[y]){
			if(deep[top[x]]<deep[top[y]]) swap(x,y),swap(bx,by);
			bx=top[x],x=fa[top[x]];
		}
		if(deep[x]>deep[y]) swap(x,y);
		return x;
	}
	
	void qupd(int x,int y){
		while(top[x]!=top[y]){
			if(deep[top[x]]<deep[top[y]]) swap(x,y);
			tr.update(1,1,n,dfn[top[x]],dfn[x],0),x=fa[top[x]];
		}
		if(deep[x]>deep[y]) swap(x,y);
		tr.update(1,1,n,dfn[x],dfn[y],0);
	}
	
	int qsum(int x,int y){
		int ret=0;
		while(top[x]!=top[y]){
			if(deep[top[x]]<deep[top[y]]) swap(x,y);
			ret+=tr.query(1,1,n,dfn[top[x]],dfn[x]),x=fa[top[x]];
		}
		if(deep[x]>deep[y]) swap(x,y);
		return ret+tr.query(1,1,n,dfn[x],dfn[y]);
	}
	
	void dp(int x){
		for(int i=head[x];i;i=t[i].next){
			int y=t[i].ver;
			if(y==fa[x]) continue;
			dp(y),g[x]+=f[y];
		}
		f[x]=g[x];
		int nx=0,ny=0;
		while(!st[x].empty()){
			int y=st[x].top().x,z=st[x].top().y,k=qsum(y,z);
			st[x].pop();
			if(f[x]<g[x]-k+1) nx=y,ny=z,f[x]=g[x]-k+1;
		}
		if(nx) qupd(nx,ny),tr.add(1,1,n,dfn[x],1);
	}

	void solve(){
		n=read(),m=read();
		for(int i=1,x,y;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
		dfs1(1),dfs2(1,1),tr.build(1,1,n);
		for(int i=1,x,y,z;i<=m;++i) x=read(),y=read(),z=lca(x,y),st[z].push((node){x,y});
		dp(1),write(f[1]);
	}
}

signed main(){
	freopen("paths.in","r",stdin);
	freopen("paths.out","w",stdout);
	code::solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值