CSP-S 2022 题解

CSP-S 2022 题解

「CSP-S 2022」假期计划

1abcd1a,b,c,d4 个不同的景点是突破点,数据范围允许枚举其中的两个。便很自然想到枚举中间的 b,c,并用合法且最优的 a,d 对答案进行统计。

可以预处理出 1ab 合法且权值最大的 a,以及 cd1 合法且权值最大的 d,但是 a,b,c,d 可能会有重合就会发生错误。

这里有个套路,有的问题中想要求出除去一种颜色以外的点的权值最大是多少,那么维护最大权值,以及和最大权值颜色不同的次大权值,那么答案一定出自其中两个之一。

于是可以维护前 t 大的 a,以及前 t 大的 d,然后暴力枚举这些 a,d 看如果合法就更新答案。这里 t 是一个常数。我考场上没有多想 t 就取的 5,毕竟多维护几个答案不会变劣。

预处理的时候如果排序会多个 log,如果用 nth_element 取前 t 大那么时间复杂度是 O(nm+n2)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#define dbg(x) cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << '\n';
#define fi first
#define se second
#define mp make_pair
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vpii;
template<typename T>inline void cmax(T &x,T y){x=x>y?x:y;}
template<typename T>inline void cmin(T &x,T y){x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+ch-'0',ch=getchar();
	return r=w?-r:r;
}
const int N=2510;
const int inf=0x7fffffff;
int n,m,k;
ll s[N],ans;
int vis[N];
int dis[N][N];
vi eg[N],vec[N];
void bfs(int S){
	for(int i=1;i<=n;i++)vis[i]=0,dis[S][i]=inf;
	queue<int>q;q.push(S);vis[S]=1;
	dis[S][S]=0;
	while(!q.empty()){
		int x=q.front();q.pop();
		for(auto v:eg[x])if(!vis[v]){
			vis[v]=1;
			dis[S][v]=dis[S][x]+1;
			q.push(v);
		}
	}
}
signed main(){
	freopen("holiday.in","r",stdin);
	freopen("holiday.out","w",stdout);
	read(n);read(m);read(k);++k;
	for(int i=2;i<=n;i++)read(s[i]);
	for(int i=1;i<=m;i++){
		int u,v;
		read(u);read(v);
		eg[u].pb(v);eg[v].pb(u);
	}
	for(int i=1;i<=n;i++)bfs(i);
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++)if(i!=j){
			if(dis[1][j]<=k && dis[j][i] <=k)
				vec[i].pb(j);
		}
		sort(vec[i].begin(),vec[i].end(),[](const int &x,const int &y){
			return s[x]>s[y];
		});
		vec[i].resize(5);
	}
	for(int b=2;b<=n;b++)
		for(int c=b+1;c<=n;c++)if(dis[b][c]<=k){
			for(auto a:vec[b])
				if(a!=c && a)
					for(auto d:vec[c]){
						if(a!=d && b!=d && d){
							cmax(ans,s[a]+s[b]+s[c]+s[d]);
						}
					}
		}
	cout << ans << '\n';
	return 0;
}

「CSP-S 2022」策略游戏

可以猜到结论是,只会挑选正负数最大最小值,以及 0.因为如果挑选了其它数,总可以调整为这些值来得到更优的答案。

0 可以直接用前缀和维护,正负数最大最小值需要用 ST 表。

实现技巧是,ST 表可以通过传数组指针的方式来写,这样就不用每个 ST 表都单独写个预处理写个查询。

查询的时候不要分类讨论,只需要把这些值拉出来跑个暴力即可。很好写,大部分代码都是直接复制粘贴。

时间复杂度 O(nlogn+mlogm+q)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#define dbg(x) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << '\n';}while(0)
#define dpi(x,y) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << " the " << #y << " = " << y << '\n';}while(0)
#define fi first
#define se second
#define mp make_pair
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vpii;
template<typename T>inline void cmax(T &x,T y){x=x>y?x:y;}
template<typename T>inline void cmin(T &x,T y){x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+ch-'0',ch=getchar();
	return r=w?-r:r;
}
const int N=100010;
const ll INF=0x7fffffffffffffff;
int lg[N];
void Premax(ll st[17][N],int n){
	for(int i=1;i<17;i++)
		for(int j=1;j+(1<<i)-1<=n;j++)
			st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
void Premin(ll st[17][N],int n){
	for(int i=1;i<17;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))]);
}
ll qmax(ll st[17][N],int l,int r){
	int k=lg[r-l+1];
	return max(st[k][l],st[k][r-(1<<k)+1]);
}
ll qmin(ll st[17][N],int l,int r){
	int k=lg[r-l+1];
	return min(st[k][l],st[k][r-(1<<k)+1]);
}
int n,m,q;
int al[N],bl[N];
ll a[N],b[N];
ll azmx[17][N],azmn[17][N],afmx[17][N],afmn[17][N];
ll bzmx[17][N],bzmn[17][N],bfmx[17][N],bfmn[17][N];
ll solve(ll x,int l,int r){
	ll mn=INF;
	for(int i=l;i<=r;i++)
		cmin(mn,x*b[i]);
	return mn;
}
signed main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	read(n);read(m);read(q);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=m;i++)read(b[i]);
	{
		int t=max(n,m);
		for(int i=2;i<=t;i++)lg[i]=lg[i>>1]+1;
	}
	for(int i=1;i<=n;i++){
		al[i]=al[i-1]+(a[i]==0);
		if(a[i]>0)
			azmx[0][i]=azmn[0][i]=a[i];
		else
			azmx[0][i]=-INF,azmn[0][i]=INF;
		if(a[i]<0)
			afmx[0][i]=afmn[0][i]=a[i];
		else
			afmx[0][i]=-INF,afmn[0][i]=INF;
	}
	Premax(azmx,n);
	Premax(afmx,n);
	Premin(azmn,n);
	Premin(afmn,n);
	//
	for(int i=1;i<=m;i++){
		bl[i]=bl[i-1]+(b[i]==0);
		if(b[i]>0)
			bzmx[0][i]=bzmn[0][i]=b[i];
		else
			bzmx[0][i]=-INF,bzmn[0][i]=INF;
		if(b[i]<0)
			bfmx[0][i]=bfmn[0][i]=b[i];
		else
			bfmx[0][i]=-INF,bfmn[0][i]=INF;
	}
	Premax(bzmx,m);
	Premax(bfmx,m);
	Premin(bzmn,m);
	Premin(bfmn,m);
	for(int o=1;o<=q;o++){
		int l1,r1,l2,r2;
		ll t;
		read(l1);read(r1);read(l2);read(r2);
		vector<ll>av,bv;
		//----------------
		if(al[r1]-al[l1-1])av.pb(0);
		t=qmax(azmx,l1,r1);
		if(t!=-INF)av.pb(t);
		t=qmax(afmx,l1,r1);
		if(t!=-INF)av.pb(t);
		//
		t=qmin(azmn,l1,r1);
		if(t!=INF)av.pb(t);
		t=qmin(afmn,l1,r1);
		if(t!=INF)av.pb(t);
		//----------------
		if(bl[r2]-bl[l2-1])bv.pb(0);
		t=qmax(bzmx,l2,r2);
		if(t!=-INF)bv.pb(t);
		t=qmax(bfmx,l2,r2);
		if(t!=-INF)bv.pb(t);
		//
		t=qmin(bzmn,l2,r2);
		if(t!=INF)bv.pb(t);
		t=qmin(bfmn,l2,r2);
		if(t!=INF)bv.pb(t);
		//----------------
		ll ans=-INF;
		for(auto x:av){
			ll mn=INF;
			for(auto y:bv)
				cmin(mn,x*y);
			cmax(ans,mn);
		}
		cout << ans << '\n';
	}
	return 0;
}

「CSP-S 2022」星战

一个点能实现反击当且仅当从它能无限走下去,也就是从它出发能走到一个环。

一个点能实现连续穿梭当且仅当从它出发的边仅有一条,也就是它的出度为 1

如果所有点都能够实现反击连续穿梭,那么这个时刻是一次反攻

综上,可以得出能进行一次反攻当且仅当图是一个内向基环森林,其等价于每个点的出度都为 1

于是 O(n2) 暴力非常容易实现。可以得到 40 分。

9, 10 两个点的做法是,暴力删边的同时维护出每个点的出度,这样每次不需要再 O(n) check 所有出度,可以直接判断出度为 1 的点的个数是否是 n

11, 12 两个点的做法是,考虑将每个点的未被摧毁的入边个数总和当作势能,1, 3 操作都只会将势能减小,而 2 操作只会将势能 +1,所以拿一个 set 暴力删边即可。

仔细思考一下,如果只有 2,4 操作相当于每次将一个集合涂一层色,或者将一个集合的颜色去除掉一层,然后询问被涂的次数是否为 n 且每个点都被涂色。注意到被涂的次数(也就是边数)是很好维护出的,但是颜色数这个东西,它是个理想莫队信息,所以不能考虑直接将这个维护出来。

回到原问题,直接维护出每个点的出度是很难做的,问题是在于如何判定有出度为 1 的点数是否为 n

这里有一个(很经典吗?)的套路是哈希,对于每个点随机赋一个 Hash 值,如果一个点有一个出边那么将这个点的 Hash 值加到一个总和 sum 里,如果 sum 与所有点 Hash 值总和 all 相等,则可以断言出度为 1 的点的个数有一定概率是 n.多随几组 Hash 值即可。操作 1,2,3,4 都形如对 sum 修改,或者是对一个点的入点的 Hash 值的和进行修改,对于单独一组 Hash 容易做到线性。

(我认为)比较好写的做法是随机赋 Hash 值,然后算 XOR 和,这样 XOR 和就是出度为奇数的点的 XOR 和,如果其为所有点 Hash 值的 XOR 并且边数是 n,那么出度为 1 的点的个数有一定概率是 n

仔细思考一下,如果当前出度为奇数的点构成的集合是 S,当且仅当 S 的补集的 XOR 是 0 才会判断错误,多随几组权值这个概率就很小了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<ctime>
#include<random>
#include<set>
#define dbg(x) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << '\n';}while(0)
#define dpi(x,y) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << " the " << #y << " = " << y << '\n';}while(0)
#define fi first
#define se second
#define mp make_pair
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vpii;
template<typename T>inline void cmax(T &x,T y){x=x>y?x:y;}
template<typename T>inline void cmin(T &x,T y){x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+ch-'0',ch=getchar();
	return r=w?-r:r;
}
mt19937_64 rnd(time(NULL)^(ull)(new char));
inline ll rd(ll a,ll b){
	return a+rnd()%(b-a+1);
}
const int N=500010;
struct Node{
	static const int T=3;
	ull a[T];
	Node operator+(const Node &y){
		Node z;
		for(int i=0;i<T;i++)z.a[i]=a[i]^y.a[i];
		return z;
	}
	bool operator==(const Node &y){
		for(int i=0;i<T;i++)
			if(a[i]!=y.a[i])
				return 0;
		return 1;
	}
	void reset(){
		for(int i=0;i<T;i++)a[i]=rnd();
	}
}a[N],ok,all,ot[N],now[N],emp;
int n,m,q,d[N],deg[N];
int ct;
signed main(){
	freopen("galaxy.in","r",stdin);
	freopen("galaxy.out","w",stdout);
	read(n);ct=read(m);
	for(int i=1;i<=n;i++)a[i].reset(),ok=ok+a[i];
	for(int i=1;i<=m;i++){
		int u,v;
		read(u);read(v);
		all=all+a[u];
		now[v]=now[v]+a[u];
		++d[v];++deg[v];
	}
	for(int i=1;i<=n;i++)ot[i]=now[i];
	read(q);
	for(int i=1;i<=q;i++){
		int op,u,v;read(op);
		if(op==1||op==3){
			int w=op==1?-1:1;
			read(u);read(v);
			d[v]+=w;
			ct+=w;
			all=all+a[u];
			now[v]=now[v]+a[u];
		}
		if(op==2){
			read(u);
			all=all+now[u];
			now[u]=emp;
			ct-=d[u];
			d[u]=0;
		}
		if(op==4){
			read(u);
			all=all+now[u]+ot[u];
			now[u]=ot[u];
			ct+=deg[u]-d[u];
			d[u]=deg[u];
		}
		if(ct==n && all==ok)puts("YES");
		else puts("NO");
	}
	return 0;
}

「CSP-S 2022」数据传输

先考虑暴力怎么做,把 st 的链拉出来,然后在上面跑 dp,即 fx,0/1/2 表示走到 x,上一次标记点和 x 的距离是 0/1/2,最小的时间是多少。通过手玩可以发现 k=3 的时候最多会拐出路径,到距离路径为 1 的点上,所以需要预处理出 gx 表示距离 x1 的点权最小值,这样就可以从 flast 转移到 fnow

不难发现 fvfx 的过程,实际上是以 (min,+) 乘上一个固定的系数矩阵 Ax.由于没有修改可以不用动态 dp,直接倍增预处理出 fux,i 表示从 xx2i 级祖先(不包含 x2i 级祖先)这一段路径上的矩阵乘积,fdx,i 同理,只不过顺序是从祖先乘到儿子。

询问的时候跳 LCA 的过程中把矩阵乘起来即可得到答案。

时间复杂度 O((n+Q)lognk3)

代码中的 g 实际上预处理到了距离 x2 的点权最小值,实际上并没有必要懒得再改了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
#define dbg(x) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << '\n';}while(0)
#define dpi(x,y) do{cerr << "In the Line " << __LINE__ << " the " << #x << " = " << x << " the " << #y << " = " << y << '\n';}while(0)
#define fi first
#define se second
#define mp make_pair
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vpii;
template<typename T>inline void cmax(T &x,T y){x=x>y?x:y;}
template<typename T>inline void cmin(T &x,T y){x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+ch-'0',ch=getchar();
	return r=w?-r:r;
}
const int N=200010;
const ll INF=0x7fffffffffffffff;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,Q,k;
int fa[N][18],dep[N],L[N],R[N],dft;
vi eg[N];
ll w[N],g[N][3];
ll f[N][3];
struct Matrix{
	static const int T=3;
	ll a[T][T];
	Matrix(){for(int i=0;i<T;i++)for(int j=0;j<T;j++)a[i][j]=inf;}
	Matrix operator*(const Matrix &y)const{
		Matrix z;
		for(int i=0;i<T;i++)
			for(int j=0;j<T;j++)
				for(int k=0;k<T;k++)
					cmin(z.a[i][j],a[i][k]+y.a[k][j]);
		return z;
	}
}fu[N][18],fd[N][18],e;
void dfs1(int x,int pa){
	g[x][0]=w[x];g[x][1]=g[x][2]=inf;
	fa[x][0]=pa;dep[x]=dep[pa]+1;
	L[x]=++dft; 
	for(int i=1;i<18;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(auto v:eg[x])if(v!=pa){
		dfs1(v,x);
		cmin(g[x][1],g[v][0]);
		cmin(g[x][2],g[v][1]);
	}
	R[x]=dft;
}
void dfs2(int x,int pa){
	for(auto v:eg[x])if(v!=pa){
		cmin(g[v][1],g[x][0]);
		cmin(g[v][2],g[x][1]);
		dfs2(v,x);
	}
}
ll query(int x,int y){
	vpii vecl,vecr;
	if(L[x]<=L[y]&&L[y]<=R[x]){
		for(int i=17;~i;--i)
			if(dep[fa[y][i]]>=dep[x])
				vecr.pb(mp(y,i)),y=fa[y][i];
	}
	else{
		x=fa[x][0];
		for(int i=17;~i;--i)
			if(dep[fa[x][i]]>=dep[y]){
				vecl.pb(mp(x,i));
				x=fa[x][i];
			}
		for(int i=17;~i;--i)
			if(dep[fa[y][i]]>=dep[x]){
				vecr.pb(mp(y,i));
				y=fa[y][i];
			}
		int lca=x;
		if(x!=y){
			for(int i=17;~i;--i)
				if(fa[x][i]!=fa[y][i]){
					vecl.pb(mp(x,i));vecr.pb(mp(y,i)); 
					x=fa[x][i];y=fa[y][i];
				}
			vecl.pb(mp(x,0));
			vecr.pb(mp(y,0));
			lca=fa[x][0];
		}
		vecl.pb(mp(lca,0));
	}
	reverse(vecr.begin(),vecr.end());
	Matrix A=e;
	for(auto i:vecl)
		A=A*fu[i.fi][i.se];
	for(auto i:vecr)
		A=A*fd[i.fi][i.se];
	return A.a[0][0];
}
signed main(){
	freopen("transmit.in","r",stdin);
	freopen("transmit.out","w",stdout);
	for(int i=0;i<e.T;i++)e.a[i][i]=0;
	read(n);read(Q);read(k);
	for(int i=1;i<=n;i++)read(w[i]);
	for(int i=1;i<n;i++){
		int u,v;read(u);read(v);
		eg[u].pb(v);eg[v].pb(u);
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int x=1;x<=n;x++){
		for(int i=0;i<k;i++){
			for(int j=0;j<k&&i+j+1<=k;j++){
				fu[x][0].a[i][j]=g[x][j];
			}
		}
		for(int i=1;i<k;i++)cmin(fu[x][0].a[i-1][i],0ll);
		fd[x][0]=fu[x][0];
	}
	for(int i=1;i<18;i++)
		for(int x=1;x<=n;x++){
			fu[x][i]=fu[x][i-1]*fu[fa[x][i-1]][i-1];
			fd[x][i]=fd[fa[x][i-1]][i-1]*fd[x][i-1];
		}
	while(Q--){
		int u,v;read(u);read(v);
		cout << w[u]+query(u,v) << '\n';
	}
	return 0;
}
posted @   do_while_true  阅读(591)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?

This blog has running: 1845 days 1 hours 34 minutes 2 seconds

点击右上角即可分享
微信分享提示