【超好懂的比赛题解】第20届上海大学程序设计联赛春季赛题解

57 篇文章 0 订阅
38 篇文章 0 订阅

第20届上海大学程序设计联赛春季赛题解

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

通过记录:8/9

总结:比赛结束剩下E和F,F本来可以做出来的但是前面A卡太久了,可惜。题目偏基础但是做起来很爽。

A.如何才能穿过传送门

题意

给一个数轴问能否从0走到n,可以向左向右走,遇到传送门必须传送到相应位置然后再往前走一格,不能走到墙上去。

题解

一开始这题数据错了,然后因为读错题(以为经过传送门可以不用的)用了并查集去做结果wa了。不过有点麻烦后面就跑dfs把他过了。因为路径是唯一的所以乱搞也可以过。建图然后从0点搜到n点,遇到墙马上返回就行了。

//#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 fa[N],bel[N],n,m,q,x[N],y[N],a[N];

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

void Solve(){
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>x[i]>>y[i];
	}
	for(int i=1;i<=q;i++){
		cin>>a[i];
	}
	for(int i=1;i<=q+1;i++) fa[i]=i; 
	for(int i=0,j=1;i<=n;i++){  //bel:点所在的块编号,fa:块的祖先 
		bel[i]=j;
		if(i==a[j]) j++;
	}
	for(int i=1;i<=m;i++){
		int fx=find(bel[x[i]]),fy=find(bel[y[i]]);
		if(fx!=fy) fa[fx]=fy; 
	}
	//for(int i=0;i<=n;i++) cout<<i<<" "<<fa[i]<<" "<<find(i)<<"!!\n";
	if(find(bel[0])!=find(bel[n])) cout<<"NO\n";
	else 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;
	while(T--){
		Solve();
	}
	return 0;
}

B.逃离魔爪

题意

给一个矩形支持如下操作:

①选定一个矩形,将里面每个格子的0变为1,1变为0。

②查询一个矩形中1的个数是奇数还是偶数。

题解

支持二维区间修改和区间查询,搬一个二维树状数组的板子,然后将查询结果模2就行了。

#include<stdio.h>
#define lb(x) (x&-x)
using namespace std;
typedef long long ll;
const int N=1007;

ll n,m,q,op,x1,y1,x2,y2;
ll t1[N][N],t2[N][N],t3[N][N],t4[N][N];

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');}

inline void add(ll x,ll y,ll z){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
			t1[i][j]+=z;
			t2[i][j]+=z*x;
			t3[i][j]+=z*y;
			t4[i][j]+=z*x*y;
		}
	}
}

inline void range_upd(ll xa,ll ya,ll xb,ll yb,ll z){
	add(xa,ya,z);
	add(xa,yb+1,-z);
	add(xb+1,ya,-z);
	add(xb+1,yb+1,z);
}

inline ll ask(ll x,ll y){
	ll res=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			res+=(x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
		}
	}
	return res;
}

inline ll range_ask(ll xa,ll ya,ll xb,ll yb){
	return ask(xb,yb)-ask(xb,ya-1)-ask(xa-1,yb)+ask(xa-1,ya-1);
}

int main(){
	n=read();m=read();q=read();
	for(int i=1;i<=q;i++){ 
		op=read();x1=read();y1=read();x2=read();y2=read();
		if(op==1){
			range_upd(x1,y1,x2,y2,1);
		}else{
			printf("%lld\n",range_ask(x1,y1,x2,y2)&1ll);
		}
	}
	return 0;
}

C. 古老的恩尼格玛机

题意

给定26个字母两两对应转换关系,然后将每个字符串按这个关系转化,输出转化结果。

题解

签到题,直接模拟即可。

//#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 k;
string str;
map<char,char>mp;
char x,y;

void Solve(){
	for(int i=1;i<=13;i++){
		cin>>x>>y;
		mp[x]=y;
		mp[y]=x;
	}
	cin>>k;
	for(int i=1;i<=k;i++){
		cin>>str;
		for(int j=0;j<str.length();j++){
			cout<<mp[str[j]];
		}
		cout<<" ";
	}
}

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;
	while(T--){
		Solve();
	}
	return 0;
}

C.并不智能的卡牌 AI

题意

给定m张牌,每次最多翻n张,问要多少次把牌翻过来。

题解

签到题,但是有坑。要特判n和m是否等于0。

//#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;

void Solve(){
	cin>>m>>n;
	if(n==0){
		if(m==0) cout<<"0\n";
		else cout<<"-1\n";
	}else{
		cout<<(m+n-1)/n<<"\n";		
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	//cin>>T;
	while(T--){
		Solve();
	}
	return 0;
}

E.林荫小径

没补题,给一份我们机房的代码参考。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x; i<=y; ++i)
#define repd(i,x,y) for(int i=x; i>=y; --i)
using namespace std;
typedef long long LL;
const int N=1000005,mod=998244353;
LL n,a,b,cnt[N],p[N];

LL fpow(LL a,LL b){
	LL res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

LL get(LL i){
	return max(1ll,i/b);
}

signed main(){
	scanf("%lld%lld%lld",&n,&a,&b);
	int P=0;
	if(n<=1000000){
		for(int i=2;i<=n;i++) p[i]=get(i*a);
		for(int i=2;i<=n;i++) 
			if(i==2||p[i]!=p[i-1]) cnt[++P]=1;
			else ++cnt[P];
	}else{
		LL nw=b+b-1;
		while(nw<n*a){
			p[++P]=nw;
			nw+=b;
		}
		p[++P]=nw;
		rep(i,1,P) cnt[i]=min(n,p[i]/a)-p[i-1]/a;
		rep(i,1,P) if(p[i]>=a) {--cnt[i]; break;}
	}
	LL tot=1,ans=1;
	for(int i=1;i<=P;i++) if(cnt[i]){
		ans=ans*fpow((cnt[i]+tot)%mod,cnt[i]-1)%mod*tot%mod;
		tot=(tot+cnt[i])%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

F.到底是多少分啊

题意

给定序列a和操作数t,每次操作可以让其中一个元素+1,定义一种结果为操作完后a中所有数的乘积,问期望结果。(直接看题应该比我讲的清楚)

题解

第一反应爆搜……铁定T。考虑dp,显然有
d p [ i ] [ j ] = ∑ k = 0 j d p [ i − 1 ] [ j − k ] ∗ ( a [ i ] + k ) , 其 中 d p [ i ] [ j ] 表 示 前 i 个 元 素 使 用 了 j 次 操 作 的 期 望 结 果 dp[i][j]=\sum_{k=0}^j dp[i-1][j-k]*(a[i]+k),其中dp[i][j]表示前i个元素使用了j次操作的期望结果 dp[i][j]=k=0jdp[i1][jk](a[i]+k)dp[i][j]i使j
但这个转移方程是 O ( n ∗ t 2 ) O(n*t^2) O(nt2)的,还是t,那我们考虑优化掉第三维。

将方程拆开可以得到
d p [ i ] [ j ] = ∑ k = 0 j d p [ i − 1 ] [ k ] ∗ ( a [ i ] + j ) − ∑ k = 0 j d p [ i − 1 ] [ k ] ∗ k 预 处 理 后 可 得 : d p [ i ] [ j ] = p r e [ j ] ∗ ( a [ i ] + j ) − s u m [ j ] dp[i][j]=\sum_{k=0}^j dp[i-1][k]*(a[i]+j)-\sum_{k=0}^j dp[i-1][k]*k\\ 预处理后可得:dp[i][j]=pre[j]*(a[i]+j)-sum[j] dp[i][j]=k=0jdp[i1][k](a[i]+j)k=0jdp[i1][k]k:dp[i][j]=pre[j](a[i]+j)sum[j]
复杂度 O ( n t ) O(nt) O(nt),然后对分母是一个组合数 C ( n + m − 1 , n ) C(n+m-1,n) C(n+m1,n)取逆元,做完了。

G.多吃蘑菇

题意

给一颗树,每个结点有权值w和颜色c,问根节点到每个点时吃到的蘑菇的最大值,要求每种蘑菇只吃一个。

题解

显然遍历一遍树就可以跑出来,维护每种颜色的蘑菇最大值,遍历完当前结点之后把最大值回溯回去即可。(机房有个用可持久化线段树做的,被秀了

//#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;
int mx[N],w[N],c[N],ans[N];
vector<int>G[N];

void dfs(int x,int f){
	int tmp=mx[c[x]];  //先记录这个颜色的蘑菇最多是多少 
	mx[c[x]]=max(mx[c[x]],w[x]);
	ans[x]=ans[f]-tmp+mx[c[x]];
	for(auto to:G[x]){
		if(f==to) continue;
		dfs(to,x);
	} 
	mx[c[x]]=min(mx[c[x]],tmp);//回退这个最大值 
}

void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<=n;i++) cin>>c[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	} 
	dfs(1,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<"\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;
	while(T--){
		Solve();
	}
	return 0;
}

H.差不多得了

题解

给定不过序列a,取出子序列b使得b的总和为a总和-1,问方案数

题解

签到题。显然b是在a的基础上取出一个1,注意如果取出的1位置连续,那么子序列是一样的,所以对答案的贡献只有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=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,a[N];
void Solve(){
	cin>>n;
	int ans=0,flag=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		if(!flag&&a[i]==1){
			flag=1;
			ans++;
		}
		if(flag&&a[i]!=1) flag=0;
	}
	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;
	while(T--){
		Solve();
	}
	return 0;
}

I.数学题真难啊

题意

给定序列a,按奇偶位分成两个序列a_odd和a_even,每个位置可以放0~9,然后问 ∑ a o d d 为 3 的 倍 数 并 且 ∑ a e v e n 为 9 的 倍 数 的 方 案 数 \sum a_{odd}为3的倍数并且\sum a_{even}为9的倍数的方案数 aodd3aeven9

题解

显然两者是乘积关系,我们考虑对于一个序列a,如何判定求和是否为k的倍数,这其实就是一个简单的数位dp, d p [ i ] [ j ] 表 示 前 i 个 数 求 和 对 p 取 模 为 j 的 方 案 数 dp[i][j]表示前i个数求和对p取模为j的方案数 dp[i][j]ipj,那么显然转移方程就是 k ∈ [ 0 , 9 ] , d p [ i ] [ ( j + k ) m o d    p ] + = d p [ i − 1 ] [ j ] k\in [0,9],dp[i][(j+k)\mod p]+=dp[i-1][j] k[0,9],dp[i][(j+k)modp]+=dp[i1][j]

//#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=998244353;

//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,f[N][10],g[N][10];

void Solve(){
	cin>>n;
	int len1=(n+1)/2,len2=n/2;
	f[0][0]=g[0][0]=1;
	for(int i=1;i<=len1;i++){ //奇数数组 
		for(int k=0;k<=9;k++){
			f[i][(0+k)%3]+=f[i-1][0];
			f[i][(1+k)%3]+=f[i-1][1];
			f[i][(2+k)%3]+=f[i-1][2];
			f[i][0]%=mod;f[i][1]%=mod;f[i][2]%=mod;
		}
	}
	for(int i=1;i<=len2;i++){
		for(int k=0;k<=9;k++){
			for(int l=0;l<=9;l++)	g[i][(l+k)%9]+=g[i-1][l];
			for(int l=0;l<=9;l++)	g[i][l]%=mod;
		}
	}
	cout<<f[len1][0]*g[len2][0]%mod;
}

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;
	while(T--){
		Solve();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RWLinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值