第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=0∑jdp[i−1][j−k]∗(a[i]+k),其中dp[i][j]表示前i个元素使用了j次操作的期望结果
但这个转移方程是
O
(
n
∗
t
2
)
O(n*t^2)
O(n∗t2)的,还是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=0∑jdp[i−1][k]∗(a[i]+j)−k=0∑jdp[i−1][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+m−1,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的倍数的方案数 ∑aodd为3的倍数并且∑aeven为9的倍数的方案数
题解
显然两者是乘积关系,我们考虑对于一个序列a,如何判定求和是否为k的倍数,这其实就是一个简单的数位dp, d p [ i ] [ j ] 表 示 前 i 个 数 求 和 对 p 取 模 为 j 的 方 案 数 dp[i][j]表示前i个数求和对p取模为j的方案数 dp[i][j]表示前i个数求和对p取模为j的方案数,那么显然转移方程就是 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[i−1][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;
}