Codeforces Round #775 (Div. 1)

D. Serious Business

题目描述

有一个 \(3\times n\) 的矩阵,我们要从 \((1,1)\) 走到 \((3,n)\),每经过一个格子就会获得对应的权值。初始时第二行时不能经过的,有 \(m\) 个活动,第 \(i\) 个活动可以花费 \(k_i\) 的代价把 \([L_i,R_i]\) 的格子解封,你可以选择多个活动,问最后获得的最大权值。

\(n,q\leq 5\cdot 10^5\)

解法

首先考虑只开放一个活动的情况,设 \(sl[i]\) 表示在 \(i\) 点进入第 \(2\) 行的权值,\(sr[i]\) 表示在 \(i\) 点退出第 \(2\) 行的权值,我们想让 \(sl[l]+sr[r]\) 表示经过第 \(2\) 行的 \([l,r]\) 的总经过点权,这个很容易通过前缀和算出来。

那么现在的问题是把左右端点限制在了一段区间内,即 \(L\leq l\leq r\leq R\),这个可以平凡地用线段树维护,上传的时候用右子树的右端点和左子树的左端点匹配即可。

考虑多个活动的情况,我们枚举 \(R_i\) 最大的那个活动,那么右端点的范围一定在 \([L_i,R_i]\) 中,我们确定左端点即可。那么剩下活动的功能就是拓展左端点的选取范围,我们把所有活动按照 \(R_i\) 排序,对于每个活动我们找出 \(\max_{L_i\leq x\leq R_i} sl[x]\),然后用它来更新 \(sl[R_i+1]\) 即可(但是有 \(k_i\) 的花费),我们求出新的 \(sl\) 之后就用一个活动的方法做即可。

时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 500005;
#define int long long
const int inf = 1e18;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[3][M],s[M][3],sl[M],sr[M],id[M];
namespace A
{
	int mx[M<<2];
	void build(int i,int l,int r)
	{
		if(l==r) {mx[i]=sl[l];return ;}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
		mx[i]=max(mx[i<<1],mx[i<<1|1]);
	}
	void ins(int i,int l,int r,int id)
	{
		if(l==r) {mx[i]=sl[l];return ;}
		int mid=(l+r)>>1;
		if(mid>=id) ins(i<<1,l,mid,id);
		else ins(i<<1|1,mid+1,r,id);
		mx[i]=max(mx[i<<1],mx[i<<1|1]);
	}
	int ask(int i,int l,int r,int L,int R)
	{
		if(L>r || l>R) return -inf;
		if(L<=l && r<=R) return mx[i];
		int mid=(l+r)>>1;
		return max(ask(i<<1,l,mid,L,R),
		ask(i<<1|1,mid+1,r,L,R));
	}
}
namespace B
{
	struct node
	{
		int lx,rx,mx;
		node operator + (const node &b) const
		{
			node r;
			r.lx=max(lx,b.lx);
			r.rx=max(rx,b.rx);
			r.mx=max(lx+b.rx,max(mx,b.mx));
			return r;
		}
	}t[M<<2];
	void build(int i,int l,int r)
	{
		if(l==r)
		{
			t[i]={sl[l],sr[l],sl[l]+sr[l]};
			return ; 
		}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
		t[i]=t[i<<1]+t[i<<1|1];
	}
	node ask(int i,int l,int r,int L,int R)
	{
		if(L<=l && r<=R) return t[i];
		int mid=(l+r)>>1;
		if(mid>=R) return ask(i<<1,l,mid,L,R);
		if(mid<L) return ask(i<<1|1,mid+1,r,L,R);
		return ask(i<<1,l,mid,L,R)+
		ask(i<<1|1,mid+1,r,L,R);
	}
}
int cmp(int x,int y) {return s[x][1]<s[y][1];}
signed main()
{
	n=read();m=read();ans=-inf;
	for(int i=0;i<3;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=read()+a[i][j-1];
	for(int i=1;i<=n;i++)
	{
		sl[i]=a[0][i]-a[1][i-1];
		sr[i]=a[2][n]-a[2][i-1]+a[1][i];
	}
	A::build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		id[i]=i;
		for(int j=0;j<3;j++)
			s[i][j]=read();
	}
	sort(id+1,id+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		int u=id[i],to=s[u][1]+1;
		int tmp=A::ask(1,1,n,s[u][0],s[u][1])-s[u][2];
		sl[to]=max(sl[to],tmp);
		if(to<=n) A::ins(1,1,n,to);
	}
	B::build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int tmp=B::ask(1,1,n,s[i][0],s[i][1]).mx;
		ans=max(ans,tmp-s[i][2]);
	}
	printf("%lld\n",ans);
}

E. Air Reform

题目描述

点此看题

解法

记录一种略显复杂,但是个人感觉比较自然的做法,代码难度较高

考虑搞出补图的最小生成树,这样处理询问就是有手就行了。使用创造奇迹的 \(\tt Boruvka\) 算法,核心问题是对于每个点 \(u\),找到不在同一个连通块内,且边权最小的邻接点 \(v\)

考虑建立 \(\tt kruskal\) 重构树,那么点 \(v\) 需要满足:

  • \(u\)\(\tt lca\) 最深。
  • 在原图中和 \(u\) 不直接有边相连。
  • \(u\) 不在同一个连通块中。

有一种想法是二分 \(\tt lca\),然后找合法的 \(u\),但其实有更快了做法。

我们可以对于每个点维护在重构树上 \(\tt dfs\) 序向前\(/\)向后的指针,这样指针指向的位置就是最优的点。如果 \(u\) 和指针指向的点直接相邻,那么移动指针。如果 \(u\) 和指针在同一个连通块中,那么跳过连续一段的点。

只有第二个条件会导致指针多次移动,而总体上指针只会移动 \(O(\sum deg)=O(m)\) 次,所以时间复杂度是对的。每次做 \(\tt Boruvka\) 的时候重构连通块的连续段,这样就可以直接跳指针了。

时间复杂度 \(O(n\log n)\),需要 \(\tt st\) 表求 \(\tt lca\),再次警告代码难度较高。

总结

在点分树 \(/\) 重构树上去 \(\log\),可以考虑把某个东西换成指针。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 400005;
const int inf = 0x3f3f3f3f;
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,k,fa[M],val[M],dfn[M],rev[M],dep[M];
int A[M],B[M],Ind,cnt,id[M],dp[M<<1][20],lg[M<<1];
struct edge{int x,y,c;}e[M],E[M];
int lp[M],rp[M],fp[M],bp[M],mn[M],mx[M],my[M];
vector<int> g[M],G[M];
vector<int>::iterator fi[M],bi[M];
int Min(int x,int y) {return dep[x]<dep[y]?x:y;}
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
int lca(int x,int y)
{
	int l=id[x],r=id[y];if(l>r) swap(l,r);
	int k=lg[r-l+1];
	return Min(dp[l][k],dp[r-(1<<k)+1][k]);
}
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;
	if(u<=n) rev[dfn[u]=++Ind]=u;
	dp[++cnt][0]=u;id[u]=cnt;
	for(int v:G[u]) dfs(v,u),dp[++cnt][0]=u;
}
void kruskal(edge *a,int m)
{
	sort(a+1,a+1+m,[&](edge x,edge y){return x.c<y.c;});
	for(int i=1;i<=2*n;i++) fa[i]=i;
	int k=n;
	for(int i=1;i<=m;i++)
	{
		int x=find(a[i].x),y=find(a[i].y),z=0;
		if(x==y) continue;
		val[z=++k]=a[i].c;
		G[z].pb(x);G[z].pb(y);
		fa[x]=fa[y]=fa[z]=z;
	}
	Ind=cnt=0;dfs(k,0);
	for(int i=2;i<=cnt;i++) lg[i]=lg[i>>1]+1;
	for(int j=1;(1<<j)<=cnt;j++)
		for(int i=1;i+(1<<j)-1<=cnt;i++)
			dp[i][j]=Min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
	for(int i=1;i<=k;i++) G[i].clear();
}
void solve()
{
	for(int l=1,r;l<=n;l=r)
	{
		for(r=l;r<=n && find(rev[l])==find(rev[r]);r++);
		for(int i=l;i<r;i++) lp[i]=l,rp[i]=r-1;
	}
	for(int i=1;i<=n;i++) mn[i]=inf,mx[i]=my[i]=0;
	for(int i=1;i<=n;i++)
	{
		while(fp[i])
		{
			if(find(rev[fp[i]])==find(i))
				{fp[i]=lp[fp[i]]-1;continue;}
			while(fi[i]!=g[i].begin() && dfn[*fi[i]]>fp[i])
				fi[i]--;
			if(dfn[*fi[i]]==fp[i]) fp[i]--;
			else break;
		}
		while(bp[i]<=n)
		{
			if(find(rev[bp[i]])==find(i))
				{bp[i]=rp[bp[i]]+1;continue;}
			while(bi[i]!=g[i].end() && dfn[*bi[i]]<bp[i])
				bi[i]++;
			if(bi[i]!=g[i].end() && dfn[*bi[i]]==bp[i]) bp[i]++;
			else break;
		}
		int x=find(i),z=inf,y=0,t=0;
		if(fp[i] && (t=val[lca(rev[fp[i]],i)])<z)
			z=t,y=rev[fp[i]];
		if(bp[i]<=n && (t=val[lca(rev[bp[i]],i)])<z)
			z=t,y=rev[bp[i]];
		if(z<mn[x]) mn[x]=z,mx[x]=i,my[x]=y;
	}
	for(int i=1;i<=n;i++) if(fa[i]==i)
	{
		int j=find(my[i]);
		if(i==j) continue;
		fa[i]=j;E[++k]={mx[i],my[i],mn[i]};
	}
}
int cmp(int x,int y) {return dfn[x]<dfn[y];}
void work()
{
	n=read();m=read();k=0;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),c=read();
		A[i]=u;B[i]=v;
		e[i]={u,v,c};g[u].pb(v);g[v].pb(u);
	}
	kruskal(e,m);
	for(int i=1;i<=n;i++)
	{
		sort(g[i].begin(),g[i].end(),cmp);
		fi[i]=bi[i]=lower_bound(g[i].begin(),g[i].end(),i,cmp);
		if(fi[i]!=g[i].begin()) fi[i]--;
		fp[i]=bp[i]=dfn[i];fa[i]=i;
	}
	while(k<n-1) solve();
	kruskal(E,k);
	for(int i=1;i<=m;i++)
		printf("%d ",val[lca(A[i],B[i])]);
	puts("");
	for(int i=1;i<=n;i++) g[i].clear();
}
signed main()
{
	T=read();
	while(T--) work();
}
posted @ 2022-03-08 15:43  C202044zxy  阅读(103)  评论(0编辑  收藏  举报