CSPS2022 题解

T1

容易想到枚举 \(B,C\),然后 \(A,D\) 可以预处理,即对于 \(i\) 处理存在路径 \(1\rightarrow j\rightarrow i\)\(j\) 的权值最大的,那么只需枚举 \(B,C\) 然后分别取最大的 \(A,D\)。但是因为此时最大的 \(A\) 可能与 \(C,D\) 重复,所以需要保存前三大的 \(j\) 才能保证找到最大的合法路径。为了避免分讨,可以枚举前三大的组合,判断是否合法并更新 \(\max\) 即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))p=p*10+c-48,c=getchar();
	return p*f;
}
const int N=2505,M=10005,inf=1000000000000000000; 
int n,m,k;
int val[N];
struct edge{int v,nxt;}e[M<<1];
int head[N],en;
inline void insert(int u,int v){e[++en]={v,head[u]},head[u]=en;}
int dis[N][N],vis[N];
priority_queue<pii>q;
inline void dij(int s){
	for(int i=1;i<=n;i++)dis[s][i]=1000000000,vis[i]=0;
	dis[s][s]=0;q.push(mkp(0,s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;vis[u]=1;
		FOR(u)if(dis[s][v]>dis[s][u]+1)
			dis[s][v]=dis[s][u]+1,q.push({-dis[s][v],v});
	}
}
struct node{
	int d,f;
	friend bool operator>=(node a,node b){
		return a.d>=b.d;
	}
}mx[3][N];

int ans;
signed main(){
	n=in,m=in,k=in;
	for(int i=2;i<=n;i++)val[i]=in;
	for(int i=1,u,v;i<=m;i++)u=in,v=in,insert(u,v),insert(v,u);
	for(int i=1;i<=n;i++)dij(i),mx[0][i].d=mx[1][i].d=mx[2][i].d=-inf;
	for(int i=2;i<=n;i++)if(dis[1][i]<=k+1){
		node tmp={val[i],i};
		for(int j=2;j<=n;j++)if(i^j&&dis[i][j]<=k+1){
			if(tmp>=mx[0][j])mx[2][j]=mx[1][j],mx[1][j]=mx[0][j],mx[0][j]=tmp;
			else if(tmp>=mx[1][j])mx[2][j]=mx[1][j],mx[1][j]=tmp;
			else if(tmp>=mx[2][j])mx[2][j]=tmp;
		}
	}
	for(int i=2,sum;i<=n;i++){
		for(int j=2;j<=n;j++)if(i^j&&dis[i][j]<=k+1){
			sum=val[i]+val[j];
			for(int l=0;l<3;l++){
				for(int r=0;r<3;r++){
					if(mx[l][i].f==0||mx[r][j].f==0)continue;
					if(mx[l][i].f!=mx[r][j].f&&mx[l][i].f!=j&&i!=mx[r][j].f)
						ans=max(ans,sum+mx[l][i].d+mx[r][j].d);
				}
			}
		}
	}cout<<ans;
	return 0;
}

T2

注意到两人选择的只可能是正负的 \(\max,\min\) 或是 \(0\),为了避免分讨,直接对每个数组开五个 \(ST\) 然后暴力找到这些数,然后 \(5*5\) 的枚举选的方法即可,实现比较优美,开一个 \(ST\) 的结构体,再开一个数组的结构体。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))p=p*10+c-48,c=getchar();
	return p*f;
}
const int NM=100005,inf=1000000000000000000;
int n,m,q,lg2[NM];
inline void init(){
	for(int i=2;i<=100000;i++)lg2[i]=lg2[i/2]+1;
}
struct ST{
	int len,maxn[20][NM],minn[20][NM];
	ST(){for(int i=1;i<=100000;i++)maxn[0][i]=-inf,minn[0][i]=inf;}
	inline void pro(){
		for(int i=1;i<=19;i++)
			for(int j=1;j+(1<<(i-1))<=n;j++)
				maxn[i][j]=max(maxn[i-1][j],maxn[i-1][j+(1<<(i-1))]),
				minn[i][j]=min(minn[i-1][j],minn[i-1][j+(1<<(i-1))]);
	}
	inline int queryMax(int l,int r){
		int t=lg2[r-l+1];
		return max(maxn[t][l],maxn[t][r-(1<<t)+1]);
	}
	inline int queryMin(int l,int r){
		int t=lg2[r-l+1];
		return min(minn[t][l],minn[t][r-(1<<t)+1]);
	}
};
struct ar{
	int len,a[NM];
	ST P,N,Z;
	inline void init(){
		for(int i=1;i<=len;i++){
			if(a[i]>0)P.maxn[0][i]=P.minn[0][i]=a[i];
			else if(a[i]<0)N.maxn[0][i]=N.minn[0][i]=a[i];
			else Z.maxn[0][i]=Z.minn[0][i]=a[i];
		}
		P.pro(),N.pro(),Z.pro();
	}
}A,B;
inline int solve(int x,int al,int ar){
	if(x==0)return 0;
	int tmp,ans=inf;
	tmp=B.N.queryMax(al,ar);
	if(tmp!=-inf)ans=min(ans,x*tmp);
	tmp=B.N.queryMin(al,ar);
	if(tmp!=inf)ans=min(ans,x*tmp);
	tmp=B.P.queryMax(al,ar);
	if(tmp!=-inf)ans=min(ans,x*tmp);
	tmp=B.P.queryMin(al,ar);
	if(tmp!=inf)ans=min(ans,x*tmp);
	tmp=B.Z.queryMax(al,ar);
	if(tmp!=-inf)ans=min(ans,x*tmp);
	return ans;
}
signed main(){
	n=in,m=in,q=in,init();
	A.len=n,B.len=m;
	for(int i=1;i<=n;i++)A.a[i]=in;
	for(int i=1;i<=m;i++)B.a[i]=in;
	A.init(),B.init();
	for(int i=1,al,ar,bl,br,tmp,ans;i<=q;i++){
		al=in,ar=in,bl=in,br=in,ans=-inf;
		tmp=A.N.queryMax(al,ar);
		if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
		tmp=A.N.queryMin(al,ar);
		if(tmp!=inf)ans=max(ans,solve(tmp,bl,br));
		tmp=A.P.queryMax(al,ar);
		if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
		tmp=A.P.queryMin(al,ar);
		if(tmp!=inf)ans=max(ans,solve(tmp,bl,br));
		tmp=A.Z.queryMax(al,ar);
		if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
		cout<<ans<<'\n';
	}
	return 0;
}

T3

删加一条边或某个点的所有入边,判断当前图是否是内向基环树森林。

只需要判断每个点的出度是否都是 \(1\),那么由于每个点的出度都是 \(1\),就有一种随机化 hash 的做法。

具体来说,每个点有一个随机权值 \(a\),每个点在当前图中的权值 \(b\) 是所有入边的点的 \(a\) 之和。那么如果所有点 \(b\) 的和等于所有点 \(a\) 的和,就有较大概率当前图满足条件。

可以在 OI-wiki 学习随机化技巧。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(i,u,G) for(int i=G.head[u],v=G.e[i].v;i;i=G.e[i].nxt,v=G.e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))p=p*10+c-48,c=getchar();
	return p*f;
}
const int N=500005;
int n,m,Q;
struct edge{int u,v,nxt;};
struct Gra{
	edge e[N];
	int head[N],en;
	inline void insert(int u,int v){e[++en]={u,v,head[u]},head[u]=en;}
}rG;
int a[N],b[N],suma[N],sum,now;
int t[N],tu[N],tv[N],ans[N];
signed main(){
	srand(time(0));
	n=in,m=in;
	for(int i=1,u,v;i<=m;i++)
		u=in,v=in,rG.insert(v,u);
	Q=in;
	for(int i=1;i<=Q;i++){
		t[i]=in,tu[i]=in;
		if(t[i]==1||t[i]==3)tv[i]=in;
	}
	for(int i=1;i<=Q;i++) ans[i]=1;
	for(int T=1;T<=10;T++){
		sum=now=0;
		for(int i=1;i<=n;i++) suma[i]=0,a[i]=rand(),sum+=a[i];
		for(int u=1;u<=n;u++){
			FOR(i,u,rG)suma[u]+=a[v];
			b[u]=suma[u],now+=b[u];
		}
		for(int i=1,u,v;i<=Q;i++){
			u=tu[i],v=tv[i];
			if(t[i]==1)b[v]-=a[u],now-=a[u];
			else if(t[i]==2)now-=b[u],b[u]=0;
			else if(t[i]==3)b[v]+=a[u],now+=a[u];
			else now+=suma[u]-b[u],b[u]=suma[u];
			if(now!=sum)ans[i]=0; 
		}
	}
	for(int i=1;i<=Q;i++){
		if(ans[i])cout<<"YES\n";
		else cout<<"NO\n";
	}
	return 0;
}

T4

给出一棵树,一个点一次可以跳到距离小于等于 \(k\) 的所有点,一条路径权值是跳的所有点点权之和,求树上两点间路径的最小权值。

逐步递进,\(k=1\) 时,就是树上两点距离,\(k=2\) 时,注意到假如跳出了两点间的链,那么跳回来的点在跳出去之前一定可以跳到,所以不会跳出链,\(k=3\) 时,同上,不会跳出去两个点,也就是如果跳出链,只会跳到链周围一圈,而对于一个点,它周围一圈没有区别,所以可以找到一个点周围的 \(\min\) 记作 \(ex[u]\)

那么假设把这条链找出来了,设 \(dp[u][0/1/2]\) 表示上个跳到的点与 \(u\) 的距离为 \(0/1/2\),然后你发现就可以用 \(dp[u-1]\) 更新 \(dp[u]\) 了。

  • \(dp[u][0]=\min\limits_{j=0}^{k-1}(dp[u-1][j]+a[u])\)
  • \(dp[u][1]=\min(dp[u-1][0],[k=3](dp[u-1][1]+ex[u]))\)
  • \(dp[u][2]=[k\ge2]dp[u-1][1]\)

发现是线性转移,而 \(+\)\(\min\) 有分配律所以可以用矩阵优化转移,可以用倍增维护一个向上和向下的矩阵乘,也可以树剖。

给出一份树剖的实现。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))p=p*10+c-48,c=getchar();
	return p*f;
}
const int N=200005,inf=200000000000000;
struct edge{int v,nxt;}e[N<<1];
int head[N],en=1;
inline void insert(int u,int v){e[++en]={v,head[u]},head[u]=en;}
int n,Q,k,val[N],ex[N];
inline void ckmn(int &x,int y){if(y<x)x=y;}
struct mat{
	int a[3][3];
	mat(){for(int i=0;i<3;i++)for(int j=0;j<3;j++)a[i][j]=inf;}
	void I(){for(int i=0;i<3;i++)a[i][i]=0;}
	mat operator*(mat b){
		mat c;
		for(int i=0;i<3;i++)
			for(int k=0,tmp;k<3;k++)
				tmp=a[i][k],
				ckmn(c.a[i][0],tmp+b.a[k][0]),
				ckmn(c.a[i][1],tmp+b.a[k][1]),
				ckmn(c.a[i][2],tmp+b.a[k][2]);
		return c;
	}
}up[N<<2],down[N<<2];
int dep[N],siz[N],maxp[N],fa[N],top[N],ttol[N],ltot[N],tot;
inline void dfsFi(int u,int f){
	dep[u]=dep[f]+1,siz[u]=1,maxp[u]=0,fa[u]=f;
	FOR(u)if(v^f) dfsFi(v,u),siz[u]+=siz[v],maxp[u]=siz[v]>siz[maxp[u]]?v:maxp[u];
}
inline void dfsSe(int u,int f,int tp){
	top[u]=tp,ttol[u]=++tot,ltot[tot]=u;
	if(maxp[u]) dfsSe(maxp[u],u,tp);
	FOR(u)if(v^f&&v^maxp[u]) dfsSe(v,u,v);
}
inline int lca(int x,int y){
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}if(dep[x]<dep[y])swap(x,y); return y;
}
inline void built(int l,int r,int p){
	if(l==r){
		int x=ltot[l];
		if(k==1) up[p].a[0][0]=val[x];
		if(k==2) up[p].a[0][0]=up[p].a[0][1]=val[x],up[p].a[1][0]=0;
		if(k==3) up[p].a[0][0]=up[p].a[0][1]=up[p].a[0][2]=val[x],up[p].a[1][0]=up[p].a[2][1]=0,up[p].a[1][1]=ex[x];
		down[p]=up[p];
		return ;
	}
	int mid=(l+r)>>1;
	built(l,mid,p<<1),built(mid+1,r,p<<1|1);
	up[p]=up[p<<1|1]*up[p<<1];
	down[p]=down[p<<1]*down[p<<1|1];
}
inline mat queryUp(int l,int r,int p,int ql,int qr){
	if(ql<=l&&r<=qr)return up[p];
	int mid=(l+r)>>1;
	if(qr<=mid)return queryUp(l,mid,p<<1,ql,qr);
	if(ql>mid)return queryUp(mid+1,r,p<<1|1,ql,qr);
	return queryUp(mid+1,r,p<<1|1,ql,qr)*queryUp(l,mid,p<<1,ql,qr);
}
inline mat queryDown(int l,int r,int p,int ql,int qr){
	if(ql<=l&&r<=qr)return down[p];
	int mid=(l+r)>>1;
	if(qr<=mid)return queryDown(l,mid,p<<1,ql,qr);
	if(ql>mid)return queryDown(mid+1,r,p<<1|1,ql,qr);
	return queryDown(l,mid,p<<1,ql,qr)*queryDown(mid+1,r,p<<1|1,ql,qr);
}
inline mat queryDown(int x,int y){
	mat ans;ans.I();
	while(top[x]!=top[y]){
		ans=queryDown(1,n,1,ttol[top[x]],ttol[x])*ans;
		x=fa[top[x]];
	}return queryDown(1,n,1,ttol[y],ttol[x])*ans;
}
mat q[N];int qn;
inline mat queryUp(int x,int y){
	mat ans;ans.I();qn=0;
	while(top[x]!=top[y]){
		q[++qn]=queryUp(1,n,1,ttol[top[x]],ttol[x]);
		x=fa[top[x]];
	}
	if(y!=x) q[++qn]=queryUp(1,n,1,ttol[y]+1,ttol[x]);
	while(qn)ans=q[qn--]*ans;
	return ans;
}
inline mat query(int x,int y){
	int t=lca(x,y);
	if(x==t)return queryUp(y,x);
	return queryUp(y,t)*queryDown(fa[x],t);
}
signed main(){
	n=in,Q=in,k=in;
	for(int i=1;i<=n;i++) val[i]=in,ex[i]=inf;
	for(int i=1,u,v;i<n;i++) u=in,v=in,insert(u,v),insert(v,u),ckmn(ex[u],val[v]),ckmn(ex[v],val[u]);
	dfsFi(1,0),dfsSe(1,0,1),built(1,n,1);
	for(int i=1,x,y;i<=Q;i++){
		x=in,y=in;
		mat ans=query(x,y);
		cout<<val[x]+ans.a[0][0]<<'\n';
	}
	return 0;
}
posted @ 2022-10-31 11:36  llmmkk  阅读(164)  评论(0编辑  收藏  举报