做题记录3.28

模拟赛

T1 发光虫

经典trick,二分答案,然后2-SAT判断是否合法,可以做到 $ O(n^2\log n) $

考虑优化建图,可以使用分块或可持久化线段树。

这里详细揭秘线段树做法,将原序列按照一个关键字排序,建初始的线段树,对于每一种颜色在原树上删掉,这里要用可持久化,查询时二分左右端点,在线段树上区间查询,把覆盖到的区间用新建节点合并起来就行。复杂度 $ O(n \log V \log n) $

怎么我常数这么大啊,都跑不过分块。

CODE
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+100;
int n,m,tot,q[N],h[N];
int fi[N],gi[N];
struct node{
	int x,y,k;
}ins[N];
vector<int>sub[N];
struct wxr{
	int id,val;
}f[N],g[N];
bool cmp(wxr fir,wxr sec){
	return fir.val<sec.val;
}
#define fir(x) ((x<<1)-1)
#define sec(x) (x<<1)
struct graph{
	int cnt,head[N*400],to[N*1600],nxt[N*1600];
	void add(int u,int v){
		assert(cnt<N*1600);
		cnt++;
		to[cnt]=v;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	void Clear(){
		memset(head,0,sizeof(head));
		for(int i=1;i<=cnt;i++)to[i]=nxt[i]=0;
		cnt=0;
	}
}e;
int dfn[N*400],low[N*400],scc[N*400],now,s;
bool vis[N*400];
int st[N*400],tp;
void Tarjan(int u){
	dfn[u]=low[u]=++now;
	vis[u]=1;st[++tp]=u;
	for(int i=e.head[u];i;i=e.nxt[i]){
		int v=e.to[i];
		if(!dfn[v]){
			Tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])low[u]=min(low[u],low[v]);
	}
	if(low[u]>=dfn[u]){
		int k=0;s++;
		do{
			k=st[tp];tp--;
			vis[k]=0;
			scc[k]=s;
		}while(k!=u);
	}
}
bool check(){
	memset(dfn,0,sizeof(int)*(now+1));
	memset(low,0,sizeof(int)*(now+1));
	memset(scc,0,sizeof(int)*(now+1));
	now=s=0;
	for(int i=1;i<=tot*2;i++){
		if(!dfn[i])Tarjan(i);
	}
	for(int i=1;i<=tot;i++){
		if(scc[fir(i)]==scc[sec(i)])return 0;
	}
	return 1;
}
int root[2][N];
struct seg{
	int cnt,ls[N*100],rs[N*100],id[N*100];
	void Clear(){
		memset(ls,0,sizeof(int)*(cnt+1));
		memset(rs,0,sizeof(int)*(cnt+1));
		memset(id,0,sizeof(int)*(cnt+1));
		cnt=0;
	}
	inline void up(int rt){
		if(ls[rt]){
			e.add(fir(id[rt]),fir(id[ls[rt]]));e.add(sec(id[rt]),sec(id[ls[rt]]));
		}
		if(rs[rt]){
			e.add(fir(id[rt]),fir(id[rs[rt]]));e.add(sec(id[rt]),sec(id[rs[rt]]));
		}
	}
	void build(int &rt,int l,int r,bool flag){
		rt=++cnt;
		if(l==r){
			if(flag)id[rt]=h[g[l].id];
			else id[rt]=q[f[l].id];
			return ;
		}
		id[rt]=++tot;
		int mid=(l+r)>>1;
		build(ls[rt],l,mid,flag);
		build(rs[rt],mid+1,r,flag);
		up(rt);
	}
	void updata(int &rt,int l,int r,int x){
		if(l==r)return rt=0,void();
		assert(cnt<N*200);
		++cnt;ls[cnt]=ls[rt];rs[cnt]=rs[rt];
		id[rt=cnt]=++tot;
		int mid=(l+r)>>1;
		if(mid>=x)updata(ls[rt],l,mid,x);
		else updata(rs[rt],mid+1,r,x);
		up(rt);
	}
	int query(int rt,int l,int r,int L,int R){
		if(L>R)return 0;
		if(!rt)return 0;
		if(L<=l&&r<=R)return rt;
		int res=++cnt,mid=(l+r)>>1;id[cnt]=++tot;
		if(mid>=L)ls[res]=query(ls[rt],l,mid,L,R);
		if(mid+1<=R)rs[res]=query(rs[rt],mid+1,r,L,R);
		up(res);
		return res;
	}
}t1,t2;
int bs1(wxr *x,int y){
	int l=1,r=n+1;
	while(l<r){
		int mid=(l+r)>>1;
		if(x[mid].val>y)r=mid;
		else l=mid+1;
	}
	return l;
}
int bs2(wxr *x,int y){
	int l=0,r=n;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(x[mid].val<y)l=mid;
		else r=mid-1;
	}
	return l;
}
bool solve(int maxn){
	e.Clear();tot=n*2;
	for(int i=1;i<=n;i++){
		e.add(fir(q[i]),sec(h[i]));e.add(sec(h[i]),fir(q[i]));
		e.add(sec(q[i]),fir(h[i]));e.add(fir(h[i]),sec(q[i]));
	}
	t1.Clear();t2.Clear();
	t1.build(root[0][0],1,n,0);t2.build(root[1][0],1,n,1);
	for(int i=1;i<=n;i++){
		if(sub[i].empty())continue;
		root[0][i]=root[0][0];root[1][i]=root[1][0];
		for(int j:sub[i]){
			t1.updata(root[0][i],1,n,fi[j]);
			t2.updata(root[1][i],1,n,gi[j]);
		}
		for(int j:sub[i]){
			int k=t1.query(root[0][i],1,n,bs1(f,ins[j].x-maxn),bs2(f,ins[j].x+maxn));
			if(t1.id[k])e.add(fir(q[j]),sec(t1.id[k]));
			k=t2.query(root[1][i],1,n,bs1(g,ins[j].x-maxn),bs2(g,ins[j].x+maxn));
			if(t2.id[k])e.add(fir(q[j]),sec(t2.id[k]));
			k=t1.query(root[0][i],1,n,bs1(f,ins[j].y-maxn),bs2(f,ins[j].y+maxn));
			if(t1.id[k])e.add(fir(h[j]),sec(t1.id[k]));
			k=t2.query(root[1][i],1,n,bs1(g,ins[j].y-maxn),bs2(g,ins[j].y+maxn));
			if(t2.id[k])e.add(fir(h[j]),sec(t2.id[k]));
		}
	}
	return check();
}
signed main()
{
	freopen("light.in","r",stdin);
	freopen("light.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&ins[i].x,&ins[i].y,&ins[i].k);
		f[i]={i,ins[i].x};
		g[i]={i,ins[i].y};
		q[i]=++tot;h[i]=++tot;
		sub[ins[i].k].push_back(i);
	}
	sort(f+1,f+n+1,cmp);sort(g+1,g+n+1,cmp);
	for(int i=1;i<=n;i++)fi[f[i].id]=i,gi[g[i].id]=i;
	int l=0,r=2000000000;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(solve(mid))l=mid;
		else r=mid-1;
	}
	cout<<l;
}

T2 跑步

赛事觉得这不虚树板子吗,怒调3h喜提5pts。

建虚树是显然的,对于虚树上的每个关键点,可以求出距离它最近的打卡点。

在原树上记录以下几个数据:

$ siz[u] $ :以 \(u\) 为根的子树权值和
$ sum[u] $ :以 \(u\) 为根的子树所有节点到达节点 \(u\) 所需要的代价和
$ h[i][u] $ :与 $ sum $ 类似,用ST表维护以 \(u\) 为底,长度为 $ 2^i $ 的一条链

对于每次查询建出新的虚树,对 $ sum,siz $ 进行一次树上差分,这样贡献分为两部分:关键点和非关键点。

前者是容易的,先让不在关键边上的非关键点跑到当前关键点 \(u\) ,贡献为差分后的 $ sum[u] $ ,然后再跑到最近的打卡点,贡献为 $ Dis \times siz[u] $ (差分后的)。

后者相对困难一些。考虑有关建边相连的两个关键点 $ x,y $ ,统计它们之间的非关键点的贡献。记录 $ fx,fy为距离x,y最近的打卡点 $ ,暂时假设 $ fx!=fy $ ,在这条链上从 \(v\) 倍增往上跳父亲,设 $ k $ 为链上第一个满足 $ Dis(k,fx) <= Dis(k,fy) $ 的非关键点,考虑先让 \(k\) 子树的所有节点跑到 \(k\) ,再从 \(k\) 跑到打卡点,贡献为 $ sum[k] + Dis(k,fy) \times siz[k] $ ,这样额外的贡献有两部分:1. 以 \(y\) 为根的子树不需要参与此过程;2. 这条链上的节点本来可以直接跑到打卡点,这里多计算了二倍到 \(k\) 的贡献,倍增时利用 \(h\) 数组容斥掉就可以了。从 \(k\)\(x\) 类似。注意具体的实现细节和处理 $ fx=fy $ 的情况。

复杂度 $ O(n \log n) $ 。

为什么我的常数又这么大,用 $ O(1) $ 求LCA才能过,还跑不过两只 $ \log $

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+100;
const int inf=1e9;
int T,n,m,s[N],num,t[N];
int fa[20][N],dis[N],dep[N],st[20][N],lg[N];
ll siz[N],h[20][N];
ll ans;
ll sum[N],wi[N];
int dfn[N],tot,out[N];
bool vis[N];
struct graph{
	int cnt,head[N],to[N<<1],nxt[N<<1],val[N<<1];
	void add(int u,int v,int w){
		cnt++;
		to[cnt]=v;
		nxt[cnt]=head[u];
		val[cnt]=w;
		head[u]=cnt;
	}
	void Clear(){
		for(int i=1;i<=cnt;i++)head[to[i]]=0;
		cnt=0;
	}
}g,w;
void dfs(int u,int pa){
	siz[u]=s[u];
	dfn[u]=++tot;st[0][tot]=u;
	for(int i=g.head[u];i;i=g.nxt[i]){
		int v=g.to[i];
		if(v==pa)continue;
		dis[v]=dis[u]+g.val[i];dep[v]=dep[u]+1;
		fa[0][v]=u;wi[v]=g.val[i];
		dfs(v,u);
		siz[u]+=siz[v];
		sum[u]+=sum[v]+1ll*siz[v]*g.val[i];
		h[0][v]=1ll*siz[v]*g.val[i];
	}
	out[u]=tot;
}
inline int Min(int x,int y){
	if(dep[x]<dep[y])return x;
	else return y;
}
inline int LCA(int x,int y){
	if(x==y)return x;
	x=dfn[x],y=dfn[y];
	if(x>y)swap(x,y);
	int llg=lg[y-x];
	return fa[0][Min(st[llg][x+1],st[llg][y-(1<<llg)+1])];
}
inline int Dis(int x,int y){
	if(x==0||y==0)return inf;
	return dis[x]+dis[y]-dis[LCA(x,y)]*2;
}
bool cmp(int fir,int sec){
	return dfn[fir]<dfn[sec];
}
void build(){
	w.Clear();
	sort(t+1,t+num+1,cmp);
	int las=num;
	for(int i=2;i<=num;i++){
		int k=LCA(t[i],t[i-1]);
		if(k!=t[i-1]){
			if(t[las]!=k)t[++las]=k;
		}
	}
	sort(t+1,t+las+1,cmp);
	las=unique(t+1,t+las+1)-t-1;
	for(int i=2;i<=las;i++){
		int k=LCA(t[i-1],t[i]);
		w.add(t[i],k,dis[t[i]]-dis[k]);
		w.add(k,t[i],dis[t[i]]-dis[k]);
	}
}
inline int get(int x,int y){
	for(int i=18;i>=0;i--){
		if(dep[fa[i][y]]>dep[x])y=fa[i][y];
	}
	return y;
}
int minn[N];
void init(int u,int f){
	if(vis[u])minn[u]=u;
	for(int i=w.head[u];i;i=w.nxt[i]){
		int v=w.to[i];
		if(v==f)continue;
		int k=get(u,v);
		sum[u]-=(sum[k]+1ll*siz[k]*wi[k]);
		siz[u]-=siz[k];
		init(v,u);
		if(Dis(minn[v],u)<Dis(minn[u],u))minn[u]=minn[v];
	}
}
void init2(int u,int f){
	if(vis[u])minn[u]=u;
	for(int i=w.head[u];i;i=w.nxt[i]){
		int v=w.to[i];
		if(v==f)continue;
		if(Dis(minn[v],v)>Dis(minn[u],v))minn[v]=minn[u];
		init2(v,u);
	}
}
void solve1(int u,int pa){
	ans+=sum[u]+1ll*Dis(u,minn[u])*siz[u];
	for(int i=w.head[u];i;i=w.nxt[i]){
		int v=w.to[i];
		if(v==pa)continue;
		solve1(v,u);
	}
}
void Back(int u,int pa){
	for(int i=w.head[u];i;i=w.nxt[i]){
		int v=w.to[i];
		if(v==pa)continue;
		Back(v,u);
		int k=get(u,v);
		siz[u]+=siz[k];
		sum[u]+=(sum[k]+1ll*siz[k]*wi[k]);
	}
}
ll query(int x,int y,int fx,int fy){
	int bx=x,by=y,bz=y;
	ll dx=Dis(fx,x),dy=Dis(fy,y),res=0;
	y=fa[0][y];
	if(y==x)return 0;
	if(dfn[y]<=dfn[fy]&&out[fy]<=out[y]&&Dis(y,fy)<=Dis(y,fx)){
		for(int i=18;i>=0;i--){
			if(dep[fa[i][y]]>dep[bx]){
				if(dx+dis[fa[i][y]]-dis[bx]>=dy+dis[by]-dis[fa[i][y]]){
					res-=h[i][y]*2;
					y=fa[i][y];
				}
			}
		}
		res+=1ll*siz[by]*(dis[fa[0][by]]-dis[y])*2;
		res+=sum[y]-sum[by]-1ll*siz[by]*(dis[by]-dis[y])+1ll*(siz[y]-siz[by])*Dis(fy,y);
		bz=y;
		y=fa[0][y];
	}
	for(int i=18;i>=0;i--){
		if(dep[fa[i][y]]>dep[bx]){
			y=fa[i][y];
		}
	}
	if(dep[y]>dep[x])
		res+=sum[y]-sum[bz]-1ll*siz[bz]*(dis[bz]-dis[y])+1ll*(siz[y]-siz[bz])*Dis(y,fx);
	return res;
}
void solve2(int u,int pa){
	for(int i=w.head[u];i;i=w.nxt[i]){
		int v=w.to[i];
		if(v==pa)continue;
		solve2(v,u);
		ll k=query(u,v,minn[u],minn[v]);
		ans+=k;
	}
}
signed main()
{
	freopen("runplus.in","r",stdin);
	freopen("runplus.out","w",stdout);
	scanf("%d",&T);
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		g.add(a,b,c);g.add(b,a,c);
	}
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	dis[1]=1;dep[1]=1;
	dfs(1,0);
	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=lg[n];i++){
		for(int j=1;j+(1<<i)-1<=n;j++){
			st[i][j]=Min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
		}
	}
	for(int i=1;i<=18;i++){
		for(int j=1;j<=n;j++){
			fa[i][j]=fa[i-1][fa[i-1][j]];
			h[i][j]=h[i-1][j]+h[i-1][fa[i-1][j]];
		}
	}
	while(m--){
		scanf("%d",&num);
		if(num==0)continue;
		bool flag=0;
		ans%=n;
		for(int i=1;i<=num;i++){
			scanf("%d",&t[i]);
			t[i]^=ans;
			if(t[i]==1)flag=1;
			vis[t[i]]=1;
		}
		if(!flag)t[++num]=1;
		ans=0;
		build();
		init(1,0);init2(1,0);
		solve1(1,0);
		Back(1,0);
		solve2(1,0);
		printf("%lld\n",ans);
		for(int i=1;t[i];i++)minn[t[i]]=vis[t[i]]=0,t[i]=0;
	}
}

T3 梦

长链剖分优化DP,这辈子有了。

设 $ f[u][i] $ 为 \(u\) 的子树中,与 \(u\) 相对深度为 \(i\) 的节点的 \(b\) 的最大值,同理,设 $ g[u][i] $ 为 \(u\) 的子树中,与 \(u\) 相对深度为 \(i\) 的节点的 \(c\) 的最大值。

然后是一个类似于dsu on tree的过程统计答案。

现在问题在于怎么快速求f,g。引入长链剖分,与重剖类似,只是重儿子是深度最大的那个,重儿子继承,轻儿子合并,注意到每条长链只会统计一次贡献,这部分均摊 $ O(n) $ ,精细化实现可以做到 $ O(nk) $ 。其实还可以更优秀,现在合并是 $ O(n) $ 的,瓶颈在于统计答案,考虑平衡二者复杂度,因为统计答案是区间查最大值,用一个类似于ST表的东西动态维护即可,复杂度 $ O(n \log k) $ 。好像单调队列可以直接 $ O(n) $ ?

posted @ 2025-03-27 22:12  Abnormal123  阅读(74)  评论(5)    收藏  举报