CSP-S 2022 个人题解

因为疫情没能参加。vp 了一下

假期计划

先做 \(n\) 遍 BFS 算出来 \(\text{dis}(x,y)\) 表示 \(x,y\) 的最短路长度。

然后考虑对每个 \(i\) 算出来 \(1,i\) 均可在 \(k\) 步内到达的点集 \(S\) 中点权最大的值,记为 \(v_i\),接下来只需要枚举满足 \(\text{dis}(b,c)\le k\) 的所有 \(b,c\),然后算出 \(v_b+v_c+s_b+s_c\) 更新答案即可。但是这样是错的,因为可能重复。

仔细思考一下发现至多重复两次,维护前三大的值即可。复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

#define int long long

using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
	for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
	return x*f;
}

const int N=2515;
int n,m,k;
int dis[N][N],val[N];
vector<int>G[N],T[N];

#define fi first
#define se second
#define mk make_pair

pair<int,int>mx[N][3];
bool vis[N][N];

queue<int>q;
void bfs(int s){
	q.push(s),dis[s][s]=0,vis[s][s]=1;
	while(q.size()){
		int x=q.front();q.pop();
		for(int v:G[x]){
			if(vis[s][v])continue;
			dis[s][v]=dis[s][x]+1,vis[s][v]=1,q.push(v);
		}
	}
}

bool can[N];//can can need
const int INF=4e18+114514;//show show way

signed main(void){

	freopen("holiday.in","r",stdin);
	freopen("holiday.out","w",stdout);

	memset(dis,63,sizeof(dis));
	n=read(),m=read(),k=read()+1;
	for(int i=2;i<=n;i++)val[i]=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		G[u].push_back(v),G[v].push_back(u);
	}
	for(int i=1;i<=n;i++)bfs(i);
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++)if(dis[i][j]<=k)T[i].push_back(j),T[j].push_back(i);
	}

	for(int v:T[1])can[v]=1;
	for(int i=2;i<=n;i++)mx[i][0]=mx[i][1]=mx[i][2]=mk(-INF,-1);
	for(int i=2;i<=n;i++){
		for(int v:T[i]){
			if(!can[v])continue;
			if(val[v]>mx[i][0].fi)mx[i][2]=mx[i][1],mx[i][1]=mx[i][0],mx[i][0]=mk(val[v],v);
			else if(val[v]>mx[i][1].fi)mx[i][2]=mx[i][1],mx[i][1]=mk(val[v],v);
			else if(val[v]>mx[i][2].fi)mx[i][2]=mk(val[v],v);
		}
	}

	int ans=0;
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			if(dis[i][j]>k||i==j)continue;int x=0,y=0;
			if(mx[i][x].se==j)x++;
			if(mx[j][y].se==i)y++;
			if(mx[i][x].se!=mx[j][y].se)ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
			else{
				int now=x;x++;
				if(mx[i][x].se==j)x++;
				ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
				x=now;now=y;y++;
				if(mx[j][y].se==i)y++;
				ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
			}
		}
	}
	cout<<ans<<'\n';

	return 0;
}

策略游戏

分别求出 \(A[l_1\cdots r_1],B[l_2\cdots r_2]\) 中正数最大值、正数最小值、负数最大值、负数最小值,那么两人选的数要么是以上四种之一,要么是 \(0\)。暴力枚举两人分别选哪个就行了,使用线段树维护,复杂度 \(O(n+q\log n)\)

#include<bits/stdc++.h>

#define int long long

using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
	for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
	return x*f;
}

const int N=1e5+5;
struct Node{int mx,mn,nx,nn;bool f;};

#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
const int INF=1e9+1;
int val[2][N],n,m;

struct SegTree{
	Node d[N<<2];

	inline Node Merge(Node ls,Node rs){
		Node res;res.f=(ls.f|rs.f);
		res.mx=max(ls.mx,rs.mx),res.mn=min(ls.mn,rs.mn);
		res.nx=max(ls.nx,rs.nx),res.nn=min(ls.nn,rs.nn);
		return res;
	}

	inline void pushup(int p){d[p]=Merge(d[ls(p)],d[rs(p)]);}

	inline void build(int l,int r,int p,int c){
		if(l==r){
			if(val[c][l]>0)d[p].mx=d[p].mn=val[c][l],d[p].nx=-INF,d[p].nn=INF,d[p].f=0;
			if(val[c][l]==0)d[p].f=1,d[p].mx=d[p].nx=-INF,d[p].mn=d[p].nn=INF;
			if(val[c][l]<0)d[p].mx=-INF,d[p].mn=INF,d[p].nx=d[p].nn=val[c][l],d[p].f=0;
			return ;
		}
		int mid=(l+r)>>1;
		build(l,mid,ls(p),c),build(mid+1,r,rs(p),c);pushup(p);
	}

	inline Node get(int l,int r,int ql,int qr,int p){
		if(l<=ql&&qr<=r)return d[p];
		int mid=(ql+qr)>>1;
		if(l>mid)return get(l,r,mid+1,qr,rs(p));
		if(r<=mid)return get(l,r,ql,mid,ls(p));
		return Merge(get(l,r,ql,mid,ls(p)),get(l,r,mid+1,qr,rs(p)));
	}
}T[2];

vector<int>f,g;

signed main(void){

	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);

	n=read(),m=read();int q=read();
	for(int i=1;i<=n;i++)val[0][i]=read();
	for(int i=1;i<=m;i++)val[1][i]=read();
	T[0].build(1,n,1,0),T[1].build(1,m,1,1);

	while(q--){
		int l=read(),r=read(),x=read(),y=read(),ans=-INF*INF;
		Node s=T[0].get(l,r,1,n,1),t=T[1].get(x,y,1,m,1);
		f.clear();f.push_back(s.mx),f.push_back(s.nx),f.push_back(s.mn),f.push_back(s.nn);
		g.clear();g.push_back(t.mx),g.push_back(t.nx),g.push_back(t.mn),g.push_back(t.nn);
		for(int v:f){
			if(v==INF||v==-INF)continue;
			int res=INF*INF;
			for(int w:g)if(w!=INF&&w!=-INF)res=min(res,w*v);
			ans=max(ans,res);
		}
		if(s.f)ans=max(ans,0ll);if(t.f)ans=min(ans,0ll);
		cout<<ans<<'\n';
	}

	return 0;
}

星战

题目等价于判断这张图是不是基环树森林,也就是是否每个点的出度均为 \(1\)

考虑给每个点 \(i\) 随机一个点权 \(x_i\),然后对于一张图,设 \(\text{deg}_i\) 为点 \(i\) 的出度,我们记它的特征值为

\[S=\sum_{i=1}^n \text{deg}_i\times x_i \]

然后我们发现 \(S\) 在四种操作后的变化都很好维护,可以多维护几个 \(S\),然后每次将 \(S\)\(\sum x_i\) 比较即可。

同理还可以维护 \(\text{xor}\) 和此时图中的边数。正确率我不会算,时间复杂度是线性的。

#include<bits/stdc++.h>

#define int long long

using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
	for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
	return x*f;
}

const int N=5e5+5;
const int V=(1ll<<60)-1;
const int W=1e9;
int Xor[N][4],val[N][4],n,tot[4],m,q,deg[N],D[N],Yor[N][4],res[4];
int Sum[N][4],Tum[N][4],Val[N][4],Res[4],Tot[4];
vector<int>G[N];

signed main(void){

	freopen("galaxy.in","r",stdin);
	freopen("galaxy.out","w",stdout);

	srand(19260817);
	n=read(),m=read();int now=m;
	for(int i=1;i<=n;i++){
		for(int j=0;j<4;j++)val[i][j]=((rand()*rand())&V),res[j]^=val[i][j];
		for(int j=0;j<4;j++)Val[i][j]=(rand()*rand()%W),Res[j]+=Val[i][j];
	}
	for(int i=1;i<=m;i++){
		int u=read(),v=read();G[u].push_back(v);deg[v]++,D[v]++;
		for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],Yor[v][j]^=val[u][j],tot[j]^=val[u][j];
		for(int j=0;j<4;j++)Sum[v][j]+=Val[u][j],Tum[v][j]+=Val[u][j],Tot[j]+=Val[u][j];
	}
	q=read();
	while(q--){
		int op=read();
		if(op==1){
			int u=read(),v=read();deg[v]--,now--;
			for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],tot[j]^=val[u][j];
			for(int j=0;j<4;j++)Sum[v][j]-=Val[u][j],Tot[j]-=Val[u][j];
		}
		if(op==2){
			int u=read();now-=deg[u],deg[u]=0;
			for(int j=0;j<4;j++)tot[j]^=Xor[u][j],Xor[u][j]=0;
			for(int j=0;j<4;j++)Tot[j]-=Sum[u][j],Sum[u][j]=0;
		}
		if(op==3){
			int u=read(),v=read();deg[v]++,now++;
			for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],tot[j]^=val[u][j];
			for(int j=0;j<4;j++)Sum[v][j]+=Val[u][j],Tot[j]+=Val[u][j];
		}
		if(op==4){
			int u=read();now+=D[u]-deg[u],deg[u]=D[u];
			for(int j=0;j<4;j++)tot[j]^=(Xor[u][j]^Yor[u][j]),Xor[u][j]=Yor[u][j];
			for(int j=0;j<4;j++)Tot[j]+=(Tum[u][j]-Sum[u][j]),Sum[u][j]=Tum[u][j];
		}
		bool ans=(now==n);
		for(int j=0;j<4;j++)ans=(ans&(tot[j]==res[j]));
		for(int j=0;j<4;j++)ans=(ans&(Tot[j]==Res[j]));
		puts(ans?"YES":"NO");
	}
	return 0;
}

数据传输

\(k\le 2\) 的时候,最优情况下一定不会跳出 \(u\to v\) 的这条链。

因此相当于把 \(u\to v\) 路径上的点都拉出来形成一条链,在这条链上选出若干点满足:

  • 头尾必须选
  • 每相邻 \(k\) 个点就至少要选一个点

要最小化选出的点权和。显然可以暴力 DP 做到 \(O(q\times nk)\) 之类的。

怎么优化呢,显然可以倍增维护矩阵(或者树剖也可以),这里说一个点分做法

我们建出点分树,然后对 \(u,v\) 考虑二者在点分树上的 \(\text{LCA}\),每次在 \(\text{LCA}\) 处统计跨越它的询问。

对于当前的 \(\text{LCA}=\text{rt}\),我们对每个点算出来 \(f_{i,j}\) 表示从 \(\text{rt}\)\(i\) 这条链上,\(\text{rt}\) 开头的第 \(j\) 个点开始,选到 \(i\),满足限制的情况下,选出点权和的最小值。这里 \(j\in [0,k]\)

然后就可以愉快地在 \(\text{LCA}\) 处算一个 \(\max+\) 卷积合并两个点了,总的时间复杂度为 \(O(nk^2\log n)\)

然而这个方法不兼容 \(k=3\) 的做法,因为 \(k=3\) 时可能会跳出这条链,然后再跳回来。

这个怎么处理呢,我暂时还不知道,先咕着

下面是 vp 的时候写的点分代码,貌似还写挂了,没拿到 \(k=1,2\) 的所有分数,只有 \(40\)

UPD:貌似在 \(n,q=2\times 10^5\) 的时候 TLE 了,但是能过 \(5\times 10^4\),我暂且蒙古

#include<bits/stdc++.h>

#define int long long

using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
	for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
	return x*f;
}

const int N=2e5+5;
vector<int>G[N];
int val[N],f[N][5],m,n,k,sz[N],mx[N],rt=0,all,col[N],ans[N];
bool vis[N];
vector<pair<int,int> >q[N];
vector<pair<int,pair<int,int> > >S;

#define fi first
#define se second
#define mk make_pair

void getroot(int u,int fa){
	sz[u]=1,mx[u]=0;
	for(int v:G[u]){
		if(v==fa||vis[v])continue;
		getroot(v,u),sz[u]+=sz[v],mx[u]=max(mx[u],sz[v]);
	}
	mx[u]=max(mx[u],all-sz[u]);
	if(mx[u]<mx[rt]||rt==0)rt=u;
}

void getc(int u,int fa,int c){
	col[u]=c;
	for(int v:G[u])if(v!=fa&&(!vis[v]))getc(v,u,c);
}

void getquery(int u,int fa){
	for(auto t:q[u])if(col[t.fi]!=col[u]&&col[t.fi])S.push_back(mk(t.se,mk(t.fi,u)));
	for(int v:G[u])if(v!=fa&&(!vis[v]))getquery(v,u);
}

const int INF=3e14;
int stk[N],top=0;
void getf(int u,int fa){
	for(int i=0;i<=k;i++)f[u][i]=INF;
	for(int i=0;i<=k;i++){
		if(top==i)f[u][i]=min(f[u][i],val[u]);
		for(int j=0;j<k;j++)if(top-j>i)f[u][i]=min(f[u][i],f[stk[top-j]][i]+val[u]);
	}
	stk[++top]=u;for(int v:G[u])if(v!=fa&&(!vis[v]))getf(v,u);top--;
}

void clear(int u,int fa){
	for(int i=0;i<=k;i++)f[u][i]=INF;col[u]=0;
	for(int v:G[u])if(v!=fa&&(!vis[v]))clear(v,u);
}

void calc(int u){
	S.clear();int cnt=0;col[u]=++cnt;
	for(int v:G[u])if(!vis[v])getc(v,u,++cnt);
	for(int v:G[u])if(!vis[v])getquery(v,u);
	for(auto t:q[u])if(col[t.fi])S.push_back(mk(t.se,mk(t.fi,u)));
	f[u][0]=val[u];for(int i=1;i<=k;i++)f[u][i]=INF;
	stk[++top]=u;for(int v:G[u])if(!vis[v])getf(v,u);top--;

	for(auto t:S){
		int x=t.se.fi,y=t.se.se,id=t.fi;ans[id]=INF;
		for(int i=1;i<k;i++){
			for(int j=1;i+j<=k;j++)ans[id]=min(ans[id],f[x][i]+f[y][j]);
		}
		for(int j=1;j<=k;j++)ans[id]=min(ans[id],f[x][0]+f[y][j]);
		for(int i=1;i<=k;i++)ans[id]=min(ans[id],f[x][i]+f[y][0]);
	}

	for(int v:G[u])if(!vis[v])clear(v,u);col[u]=0;
}

void build(int u){
	vis[u]=1;calc(u);
	for(int v:G[u]){
		if(vis[v])continue;
		all=sz[v],rt=0,getroot(v,u),getroot(rt,u),build(v);
	}
}

signed main(void){

	freopen("transmit.in","r",stdin);
	freopen("transmit.out","w",stdout);

	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++)val[i]=read();
	for(int i=1;i<=n-1;i++){int u=read(),v=read();G[u].push_back(v),G[v].push_back(u);}
	for(int i=1;i<=m;i++){int u=read(),v=read();q[u].push_back(mk(v,i));}

	all=n,rt=0;getroot(1,0),getroot(rt,0),build(rt);
	for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';

	return 0;
}
posted @ 2022-10-30 20:07  云浅知处  阅读(228)  评论(1编辑  收藏  举报