Bootstrap

【超好懂的比赛题解】2022年桂林电子科技大学程序设计竞赛(同步赛)


title : 2022年桂林电子科技大学程序设计竞赛
tags : ACM,练习记录
date : 2022-5-29
author : Linno


2022年桂林电子科技大学程序设计竞赛(同步赛)

题目链接:https://ac.nowcoder.com/acm/contest/34773

小结:题都比较简单,比赛时大概做出10道大概是没问题的。

A-Overcooked!

支持①两个单点合并;②一个区间合并;③询问两个点是否在同一集合。

并查集搞一下就行了,第二个操作不需要用到数据结构,直接暴力跳祖先即可。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
const int N=1e6+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}

int n,m,FA,fa[N],nxt[N],tr[N<<2];

inline int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}

void Solve(){
	cin>>n>>m;
	for(int i=1;i<=n;++i) fa[i]=i,nxt[i]=i+1; //nxt指向下一个没被融合的区域 
	for(int i=1,op,l,r,fx,fy;i<=m;++i){
		cin>>op>>l>>r;
		if(op==1){
			fx=find(l);
			fy=find(r);
			fa[fx]=fy;
		}else if(op==2){
			fy=find(r);
			for(int j=l;j<=r;){
				fx=find(j);
				fa[fx]=fy;
				int tmp=j;
				j=nxt[j];
				nxt[tmp]=r+1;
			}
		}else{
			if(find(l)==find(r)) cout<<"YES\n";
			else cout<<"NO\n";
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

B-It takes two

判断给定矩形是否由若干个边不重合的矩形A构建而成。

跑每一个连通块,记录上下左右范围,然后对这个范围的矩形判断是否点都在这个连通块内。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
const int N=1007;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
const int xx[]={0,0,1,-1},yy[]={1,-1,0,0};

int n,m,vis[N][N],U,D,R,L,idx=0;
char mp[N][N];

inline void dfs(int x,int y,int id){
	if(x<1||x>n||y<1||y>m||mp[x][y]=='O') return;
	vis[x][y]=id;
	U=min(U,x);
	D=max(D,x);
	L=min(L,y); 
	R=max(R,y);
	for(int d=0;d<4;++d){
		int nx=x+xx[d],ny=y+yy[d];
		if(!vis[nx][ny]) dfs(nx,ny,id);
	}
}

void Solve(){
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cin>>mp[i][j];
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(mp[i][j]=='A'&&!vis[i][j]){
				U=L=inf;R=D=0;
				dfs(i,j,++idx);
				//cout<<U<<" "<<D<<" "<<L<<" "<<R<<"!!\n";
				for(int k=U;k<=D;++k){
					for(int l=L;l<=R;++l){
						if(vis[k][l]!=idx){
							cout<<"NO\n";
							return;
						}
					}
				}
			}
		}
	}
	cout<<"YES\n";
}

signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

C-Don’t Starve

问能否将一棵树分成点权值和相等的三个部分。

从一个叶子出发遍历整棵树,找到权值为sum/3的就可以把这颗子树裁掉,一开始被这道题坑了,注意会存在满足的子树大于3个的情况。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e6+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
int n,sum,rt,flag,S[N],val[N],deg[N];
vector<int>G[N];

inline void dfs(int x,int f){
	S[x]=val[x];
	for(auto to:G[x]){
		if(to==f) continue;
		dfs(to,x);
		S[x]+=S[to];
	}
	if(S[x]==sum){ //找到了一个子树满足条件 
		++flag; 
		S[x]=0; //清空子树 
	}
}

void Solve(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>val[i],sum+=val[i]; 
	for(int i=1,u,v;i<n;++i){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u); 
		++deg[v];++deg[u];
	}
	if(sum%3){
		cout<<"NO\n";
		return;
	}
	sum/=3;
	for(int i=1;i<=n;++i){
		if(deg[i]==1){
			rt=i;
			break;
		}
	}
	dfs(rt,0);
	if(flag>=3) cout<<"YES\n";
	else cout<<"NO\n";
}

signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

D- Sid Meier’s Civilization

将序列划分成k段,每段子序列的价值是数字的种类数,问最大权值和。

O ( k ∗ n 2 ) O(k*n^2) O(kn2)相信大家都会,关键在于如何把状态转移的前驱结点裁一下。我们假设从j到i由一个开拓者看守,那么j就只由前一段中的所有数字第一次出现位决定。开个vector存一下有可能的前一段开拓者位置即可。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;
const int mod=1e9+7;

int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
int n,K,ans=0,dp[N][55],a[N],cnt[N],stk[N],stq[N],top=0;
vector<int>vt;

void Solve(){
	memset(cnt,0,sizeof(cnt));
	n=read();K=read();
	for(int i=1;i<=n;++i) a[i]=read();
	vt.emplace_back(0);
	for(int k=1,tmp=0;k<=K;++k){
		for(int p=0,j;p<vt.size();++p){
			j=vt[p];
			for(int i=j+1;i<=n;++i){
				if(cnt[a[i]]) stk[++top]=i-1,dp[i][k]=max(dp[i][k],dp[j][k-1]+tmp);
				else cnt[a[i]]=1,stq[++tmp]=a[i],dp[i][k]=max(dp[i][k],dp[j][k-1]+tmp); 
			}
			dp[n][k]=max(dp[n][k],dp[j][k-1]+tmp);
			while(tmp) cnt[stq[tmp--]]=0;
		}
		vt.clear();
		while(top) vt.emplace_back(stk[top--]);
		stable_sort(vt.begin(),vt.end());
		vt.erase(unique(vt.begin(),vt.end()),vt.end());
	}
	for(int j=1;j<=K;++j) ans=max(ans,dp[n][j]);
	write(ans);
}

signed main(){
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

E-Ori and the Blind Forest

给定n个不相交区间的范围和m个跳跃距离,问能否从区间1跳到区间n。

将n个区间转化为n-1个需要的技能范围 [ l i , r i ] [l_i,r_i] [li,ri],然后我们按r从小到大排序,对跳跃距离从小到大排序,枚举需要的技能范围,将满足小于右端点的技能放入set离,二分得到第一个满足的技能匹配上即可。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int __int128
//#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}

struct node{int l,r;}s[N];
bool cmp(node A,node B){return A.r<B.r;}
multiset<int>st;
int n,m,a[N],l[N],r[N],flag=0;

void Solve(){
	n=read();m=read();
	//cin>>n>>m;
	for(int i=1;i<=m;++i) a[i]=read();//cin>>a[i];
	for(int i=1;i<=n;++i){
		//cin>>l[i]>>r[i];
		l[i]=read();r[i]=read();
	}
	for(int i=1;i<n;++i){ 
		s[i].l=l[i+1]-r[i];
		s[i].r=r[i+1]-l[i];
	}
	sort(a+1,a+1+m);
	sort(s+1,s+n,cmp);
	int idx=1;
	for(int i=1;i<n;++i){ //是否每个区间能匹配一个技能
		while(idx<=m&&a[idx]<=s[i].r) st.insert(a[idx++]);
		auto it=st.lower_bound(s[i].l);
		if(it==st.end()){
			flag=1;
			break;
		}
		//cout<<(*it)<<" "<<i<<"!!\n";
		st.erase(it);	
	}
	if(flag) puts("NO");//cout<<"NO\n";
	else puts("YES");//cout<<"YES\n"; 
}

signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

F-XCOM

签到题,不中的概率乘起来即可。

void Solve(){
    int n;
    double ans=1.0,a;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a;
        ans*=(1.0-a);
    }
    printf("%.6lf",ans);
    //cout<<ans<<"\n";
}

G-Devil May Cry

压轴题,给定序列a,求n-k+1个答案,每次求[i,i+k-1]中最长的严格上升子序列,并且要求选了 a i a_i ai之后必须选择第一个在区间内的比 a i a_i ai大的数。

每个点的下一个要选的数都是确定的,往后面连边,整个序列就变成了一棵树的形状。(这个过程可以用单调栈连边)。现在对每个点往后的LIS就是他在树上的深度了。

如何将范围限制在k之中?我们可以在树上求dfn序进行线段树操作,对于加入一个结点和删除一个结点,就是对其子树进行+1-1操作,最后求和树上可用结点即可。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
const int N=2e6+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
vector<int>G[N];
inline void addedge(int u,int v){G[u].push_back(v);} 
int n,k,val[N],top,stk[N],L[N],R[N],vis[N],idx=0;

inline void dfs(int x){
	if(vis[x]) return;
	vis[x]=1;
	L[x]=++idx;
	for(auto to:G[x]) dfs(to);
	R[x]=idx;
}

#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
int tr[N<<2],tg[N<<2];

inline void pushdown(int p,int l,int r){
	if(l==r) return;
	if(tg[p]){
		tg[ls]+=tg[p];
		tg[rs]+=tg[p];
		tr[ls]+=tg[p];
		tr[rs]+=tg[p];
		tg[p]=0;
	}
}

inline void upd(int p,int l,int r,int ql,int qr,int val){
	if(ql<=l&&r<=qr){
		tr[p]+=val;
		tg[p]+=val;
		return;
	}
	pushdown(p,l,r);
	if(ql<=mid) upd(ls,l,mid,ql,qr,val);
	if(qr>mid) upd(rs,mid+1,r,ql,qr,val);
	tr[p]=max(tr[ls],tr[rs]);
}

void Solve(){
	scanf("%d%d",&n,&k);
	top=0;
	for(int i=1;i<=n;++i){
		scanf("%d",&val[i]);
		while(top&&val[stk[top]]<val[i]){
			addedge(i,stk[top]);
			top--; 
		}
		stk[++top]=i;
	}
	for(int i=n;i>=1;--i) if(!vis[i]) dfs(i);
	for(int i=1;i<=k;++i) upd(1,1,n,L[i],R[i],1);
	printf("%d ",tr[1]);
	for(int i=k+1;i<=n;++i){
		upd(1,1,n,L[i],R[i],1);
		upd(1,1,n,L[i-k],R[i-k],-1);
		printf("%d ",tr[1]);
	}
}

signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

H-Mental Omega

求以某个点为根最大的子树重量和。

显然以每个点开始求重量和累加是肯定是对的,但是会超。考虑钦定某个点为根,考虑不换根,有结点x朝儿子y的转移,其实就是 a n s [ y ] = a n s [ x ] + n − 2 ∗ s z [ t o ] ans[y]=ans[x]+n-2*sz[to] ans[y]=ans[x]+n2sz[to]

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}

int n,m,rt,ans=0,dp[N],sz[N],fa[N],son[N],g[N];
vector<int>G[N];

inline void dfs1(int x,int f){
	sz[x]=1; 
	for(auto to:G[x]){
		if(to==f) continue;
//		fa[to]=x;
		dfs1(to,x);
		sz[x]+=sz[to];
		dp[x]+=dp[to];
	}
	dp[x]+=sz[x];
}

inline void dfs2(int x,int f){
	for(auto to:G[x]){
		if(to==f) continue;
		g[to]=g[x]-dp[to]-2*sz[to]+n+dp[to];
	}
	for(auto to:G[x]){
		if(to==f) continue;
		dfs2(to,x);
	}
}

void Solve(){
	cin>>n;
	for(int i=1,u,v;i<n;++i){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
//		++deg[u];++deg[v]; 
	}
	dfs1(1,0);
	g[1]=dp[1];
	dfs2(1,0);
	for(int i=1;i<=n;++i){
//		cout<<i<<" "<<dp[i]<<" "<<sz[i]<<" "<<g[i]<<"!!\n";
		ans=max(ans,g[i]);
	}
	cout<<ans<<"\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

I-Shadow Tactics

给定每个敌人的侦察范围和行走路径,问给定点会不会被敌人发现。

将每个敌人能侦察的范围变成一个矩形,然后判断点是否在矩形内即可。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
//void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}

int sx,sy;

bool check(int x1,int y1,int x2,int y2){
	if(x1<=sx&&sx<=x2&&y1<=sy&&sy<=y2) return true;
	return false;
}

void Solve(){
	int n,R,x,y,w,x1,x2,y1,y2;
	char c;
	cin>>n>>R;
	cin>>sx>>sy;
	for(int i=1;i<=n;++i){
		cin>>c>>x>>y>>w;
		if(c=='U'){ 
			x1=x-R,x2=x+R;y1=y-R;y2=y+w+R;
		}else if(c=='D'){
			x1=x-R,x2=x+R;y1=y-w-R;y2=y+R;
		}else if(c=='L'){
			x1=x-R-w,x2=x+R;y1=y-R;y2=y+R;
		}else{
			x1=x-R,x2=x+R+w;y1=y-R;y2=y+R;
		}
		if(check(x1,y1,x2,y2)){
			cout<<"YES\n";
			return;
		}
	}
	cout<<"NO\n";
}

signed main(){
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);
//  freopen("out.cpp","w",stdout);
	int T=1;
//	cin>>T;
//	clock_t start,finish;
//	start=clock();
	while(T--){
		Solve();
	}
//	finish=clock();
//	cerr<<((double)finish-start)/CLOCKS_PER_SEC<<endl;	return 0;
}

J-Sekiro:Shadows Die Twice

签到题,将字母替换然后输入输出即可。

void Solve(){
    string str;
    cin>>str;
    mp['A']="RRRR";
    mp['B']="RS";
    mp['C']="RP";
    mp['D']="L";
    mp['E']="RRRRRRSL";
    mp['F']="S";
    mp['G']="PL";
    for(int i=0;i<str.length();++i){
        cout<<mp[str[i]];
    }
}

K-Prototype

签到题,对一个数唯一分解,然后答案就是每一种质数的乘积。

int w,ans,np[N],pri[N],cnt=0;
void init(){
    np[1]=1;
    for(int i=2;i<N;++i){
        if(!np[i]) pri[++cnt]=i;
        for(int j=1;j<=cnt&&pri[j]*i<N;++j){
            np[pri[j]*i]=1;
            if(i%pri[j]==0) break;
        }
    }
}
 
void Solve(){
    init();
    cin>>w;
    int ans=1;
    for(int i=1;i<=cnt;++i){
        if(w%pri[i]==0){
            ans*=pri[i];
            while(w%pri[i]==0) w/=pri[i];
        }
    }
    if(w>1) ans*=w;
    cout<<ans<<"\n";
}

L-Stardew Valley

签到题,找到比钱一个数小的,答案加上那个差值即可(等价于统一给后面的数加上那个差值)。

void Solve(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=2;i<=n;++i){
        if(a[i]<a[i-1]){
            ans+=a[i-1]-a[i];
        }
    }
    cout<<ans<<"\n";
}
;