do_while_true

一言(ヒトコト)

CSP-S 2022 题解

「CSP-S 2022」假期计划

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

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

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

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

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

#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 表都单独写个预处理写个查询。

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

时间复杂度 \(\mathcal{O}(n\log n+m\log m+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\)

于是 \(\mathcal{O}(n^2)\) 暴力非常容易实现。可以得到 40 分。

9, 10 两个点的做法是,暴力删边的同时维护出每个点的出度,这样每次不需要再 \(\mathcal{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」数据传输

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

不难发现 \(f_{v}\to f_{x}\) 的过程,实际上是以 \((\min,+)\) 乘上一个固定的系数矩阵 \(A_x\).由于没有修改可以不用动态 dp,直接倍增预处理出 \(fu_{x,i}\) 表示从 \(x\)\(x\)\(2^i\) 级祖先(不包含 \(x\)\(2^i\) 级祖先)这一段路径上的矩阵乘积,\(fd_{x,i}\) 同理,只不过顺序是从祖先乘到儿子。

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

时间复杂度 \(\mathcal{O}((n+Q)\log nk^3)\)

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

#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 @ 2022-11-02 08:22  do_while_true  阅读(537)  评论(0编辑  收藏  举报