浙江广厦大学第七届程序设计竞赛-题解
A 种植所有植物所需时间
累加 n 个植物所需的阳光总和除以 50 向上取整即是需要获得的阳光次数
每次获取阳光需要 5 秒,乘以 5 即是答案,时间复杂度 O(n)
注意输入较多,请使用较快读入方式
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; const int N=3e5+10; ll t,sum; int main() { IOS cin>>t; while(t--) { ll x; cin>>x; sum+=x; } cout<<((sum-1)/50+1)*5; return 0; }
B 小马喝水
以 x 轴为轴作马厩的对称点,两点之间直线最短
算出小马和对称点的两点之间距离,向下取整即是答案
因为距离较大,可以用__int128来存,以及用二分答案来逼近开根后的值,时间复杂度 O( log w )
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef long long ll; typedef __int128 i8; const int N=3e5+10; i8 dis(i8 x1,i8 y1,i8 x2,i8 y2) { return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2); } int main() { ll x1,y1,x2,y2,ans; cin>>x1>>y1>>x2>>y2; y2*=-1; i8 ds=dis(x1,y1,x2,y2); i8 l=0,r=1e18; while(l<r) { i8 mid=(l+r+1)>>1; if(mid*mid<=ds) l=mid; else r=mid-1; } cout<<(ans=r); return 0; }
C 菠萝蜜多斩
对于一个区间,求区间内出现次数为奇数次的数的异或和就是这个区间的异或和,设为 x
所以我们可以通过求出区间内所有不同的数的异或和,设为 y,x 异或 y 即是答案
x 可以简单地通过前缀数组的形式求出,求出 y 的一种方法是用离线排序+树状数组,时间复杂度 O( m×log n )
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef long long ll; typedef pair<ll,ll> pii; typedef vector<ll> vi; typedef vector<pii> vii; typedef vector<string> vs; typedef vector<char> vc; const int N=1e6+10; int n,m,k=1e6; int p[N],f[N],xo; int ans[N],pos[N]; struct l { int l,r,id; }; vector<l> g; int c[N]; int lowbit(int x) { return x&(-x); } void add(int x,int d) { for(int i=x;i<=k;i+=lowbit(i)) c[i]^=d; } int qry(int x) { int res=0; for(int i=x;i>=1;i-=lowbit(i)) res^=c[i]; return res; } map<int,int> mp; bool cmp(l &a,l &b) { return a.l<b.l; } int main() { IOS cin>>n>>m; for(int i=1;i<=n;i++) { cin>>p[i]; f[i]=f[i-1]^p[i]; } for(int i=1;i<=m;i++) { int l,r; cin>>l>>r; ans[i]=f[r]^f[l-1]; g.push_back({l,r,i}); } sort(g.begin(),g.end(),cmp); for(int i=1;i<=n;i++) { if(!mp[p[i]]++) add(i,p[i]); } for(int i=1;i<=n;i++) mp[p[i]]=n+1; for(int i=n;i>=1;i--) { pos[i]=mp[p[i]]; mp[p[i]]=i; } int left=1; for(auto i:g) { while(left<i.l) add(pos[left],p[left]),left++; ans[i.id]^=(qry(i.r)^qry(i.l-1)); } for(int i=1;i<=m;i++) cout<<ans[i]<<'\n'; return 0; }
D 扫雷游戏
签到题,根据条件分类讨论,按题意统计模拟即可,时间复杂度 O(n×m×8)
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; const int inf=0x3f; const int N=1e3+10; int n,m; int p[N][N],ans[N][N]; int xx[8]={0,0,1,-1,1,-1,1,-1}; int yy[8]={1,-1,0,0,1,-1,-1,1}; int main() { IOS cin>>n>>m; for(int i=1;i<=n;i++) { string s; cin>>s; for(int j=1;j<=m;j++) { if(s[j-1]=='*') p[i][j]=1; } } memset(ans,-1,sizeof(ans)); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(p[i][j]) continue; for(int k=0;k<8;k++) { int dx=i+xx[k]; int dy=j+yy[k]; if(dx<1||dx>n||dy<1||dy>m) continue; if(p[dx][dy]==0) continue; ans[i][j]++; } ans[i][j]++; } } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { cout<<ans[i][j]<<' '; } cout<<endl; } return 0; }
E 始皇一问
在题目限制条件下,从 (1,1) 走到 (n,m) 一共会走 n+m-2 步
题目所求方案数可以看作从 n+m-2 步中选 m-1 步往右走,所以答案即是 C(n+m-2,m-1)
可以预处理组合数来计算,时间复杂度 O(n)
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; const int inf=0x3f; const int mod=998244353; const int N=2e6+10; ll q[N]; ll inv[N]; ll pow_mod(ll a,ll b) { ll res=1; while(b) { if(b&1) res=(res*a)%mod; b>>=1; a=(a*a)%mod; } return res; } void init() { q[0]=1; for(int i=1;i<N;i++) q[i]=q[i-1]*i%mod; inv[N-1]=pow_mod(q[N-1],mod-2); for(int i=N-1;i>=1;i--) inv[i-1]=inv[i]*i%mod; } ll C(ll n,ll m) { if(n<m||n<0||m<0) return 0; if(n==m||m==0) return 1; return q[n]*inv[m]%mod*inv[n-m]%mod; } int main() { int t; cin>>t; init(); while(t--) { ll n,m; cin>>n>>m; cout<<C(n+m-2,m-1)<<endl; } return 0; }
F 压缩文章
签到题,遍历+判断字符即可,时间复杂度 O(n)
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef long long ll; int n,now=-1,cnt; string s; int main() { cin>>s; s+='#'; string ans; for(auto i:s) { if(i==now) cnt++; else if(now<0) now=i,cnt=1; else { ans+=to_string(cnt); ans+=(char)now; now=i,cnt=1; } } cout<<ans; return 0; }
G 原神启动 (hard版本)
二分答案 x,考虑如何 check ,因为原石在积攒了超过一次抽奖的价格后,再攒下去无疑是不优的,所以直接贪心,攒够了原石就抽掉,不难想到贪心的一个实现方法是直接遍历 n 次活动,攒够就抽,但这样会超时
考虑优化,积攒原石的的过程显然并不重要,我们只需要在每一次抽完后知道下一次攒够原石的位置即可,用前缀和+二分来得到这个位置,一旦抽的次数超过 k 说明二分的值有效,时间复杂度 O( k × log w × log n)
注意输入较多,请使用较快读入方式,以及特判 n < k 的情况
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; typedef pair<ll,ll> pii; typedef vector<pii> vii; typedef vector<ll> vi; typedef vector<string> vs; typedef vector<char> vc; const int inf=0x3f; const int N=1e7+5; ll n,k,p[N],q[N]; bool check(ll x) { ll now=0,cnt=0; while(now<=n) { auto it=lower_bound(q+1,q+n+1,q[now]+x)-q; if(it<=n) now=it,cnt++; else return 0; if(cnt>=k) return 1; } return cnt>=k; } int main() { IOS cin>>n>>k; if(n<k) { cout<<-1; return 0; } for(int i=1;i<=n;i++) { cin>>p[i]; q[i]=q[i-1]+p[i]; } ll l=1,r=q[n]; while(l<r) { ll mid=(l+r+1)>>1; if(check(mid)) l=mid; else r=mid-1; } cout<<l; return 0; }
H 原神启动 (easy版本)
会发现原石可以累积到 n 次活动结束后再一起抽
统计原石总数 sum,x 即为 sum 除以 k 向下取整,注意特判 n < k 的情况,时间复杂度 O(n)
注意输入较多,请使用较快读入方式
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; const int inf=0x3f; const int N=1e7+10; ll n,k,sum; int main() { IOS cin>>n>>k; for(int i=1;i<=n;i++) { ll x; cin>>x; sum+=x; } ll ans=sum/k; if(ans==0) ans=-1; cout<<ans; return 0; }
I 古神话
可以遍历每一个格子,求出所有以该格子为右下角的所有全 0 矩形的个数,统计后即答案
对于每一个空格子,可以在线性复杂度下预处理出其上方有几个空格子,这个值设为 g(i,j)
对于每一个作为右下角的格子 (x,y),g(x,y) 即以 (x,y) 为右下角,底长为 1 的矩形的最大高度
会发现,随着矩形的左边往左扩散,底长会单调递增,最大高度会单调不升,直到底边无法往左扩散
会发现底长的增长是规律的,而最大高度从最大的 y' 满足 y' < y 且 g(x,y') < g(x,y) 的时候才会开始减少
我们可以用单调栈找到最大的 y' ,并用前缀和配合统计贡献,时间复杂度 O(n×m)。
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; typedef pair<ll,ll> pii; typedef vector<pii> vii; typedef vector<ll> vi; typedef vector<string> vs; typedef vector<char> vc; const int N=5000+5; int n,m; short p[N][N]; short h[N][N]; short l[N][N]; ll s[N]; int main() { IOS cin>>n>>m; for(ll i=1;i<=n;i++) { for(ll j=1;j<=m;j++) { cin>>p[i][j]; p[i][j]^=1; if(p[i][j]) h[i][j]=h[i-1][j]+1; } } ll res=0; for(ll i=1;i<=n;i++) { memset(s,0,sizeof(s)); for(ll j=1;j<=m;j++) { if(p[i][j]==0) continue; l[i][j]=j-1; while(l[i][j]>0&&h[i][l[i][j]]>=h[i][j]) l[i][j]=l[i][l[i][j]]; s[j]=s[l[i][j]]+h[i][j]*(j-l[i][j]); res+=s[j]; } } cout<<res%(10000000007)<<endl; return 0; }
J 青铜门下
对于一个后缀,其母序列的长度是固定的,所以我们对每个后缀,考虑其权值不为 0 的母序列个数
设当前后缀的长度为 k ,后缀中 0 的个数为 c0 ,1 的个数为 c1,那么母序列还有 k-1 个位置可以填,那么如果要使母序列的第一个数是序列众数,假设第一个数是 0,则在 k-1 个位置中至少还要填 c1 个 0,在这个限制下,母序列的个数是: F(k,0)=C(k-1,c1)+C(k-1,c1+1)+...+C(k-1,k-1),直接遍历算会 TLE
考虑优化,注意到在顺序遍历每个后缀的情况下,每一次的后缀可以看作是上一次的后缀接上一个数,所以我们可以维护两个数 F0 和 F1,代表 F(k,0) 和 F(k,1),k 每次会加上一,c0 和 c1 会实时更新,分类讨论新接上的数是 0 或 1 的情况,维护 F0 和 F1,在更新 F0 的情况下,第一种情况是后缀新接上的数是 0,F0=C(k-1,c1)+...+C(k-1,k-1) 变成 F0'=C(k,c1)+... + C(k,k),可以通过组合递推式得出 F0'=F0×2+C(k-1,c1-1),类似地可得 F1'=F1×2+C(k-1,c0-2)-C(k,c0-1),新接上的数是 1 同理,时间复杂度 O(n)
#include<bits/stdc++.h> #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; typedef double db; typedef long long ll; typedef pair<ll,ll> pii; typedef vector<pii> vii; typedef vector<ll> vi; typedef vector<string> vs; typedef vector<char> vc; const int inf=0x3f; const int N=1e6+10; const int mod=100000007; ll pow_mod(ll a,ll b) { ll res=1; while(b) { if(b&1) res=(res*a)%mod; b>>=1; a=(a*a)%mod; } return res%mod; } ll q[N]; ll inv[N]; void init() { q[0]=1; for(int i=1;i<N;i++) q[i]=q[i-1]*i%mod; inv[N-1]=pow_mod(q[N-1],mod-2); for(int i=N-1;i>=1;i--) inv[i-1]=inv[i]*i%mod; } ll C(ll n,ll m) { if(n<m) return 0; if(n==m||m==0) return 1; if(n<0||m<0) return 0; return q[n]*inv[m]%mod*inv[n-m]%mod; } ll n,m,p[N],c[2],d[2],res; int main() { IOS init(); cin>>n; for(int i=1;i<=n;i++) cin>>p[i]; reverse(p+1,p+n+1); for(ll i=0;i<n;i++) { ll x=p[i+1],sum=0; c[x]++; if(x) { d[1]=C(i-1,c[0]-1)+d[1]+d[1]; d[1]=(d[1]%mod+mod)%mod; d[0]=C(i-1,c[1]-2)+d[0]+d[0]-C(i,c[1]-1); d[0]=(d[0]%mod+mod)%mod; sum=(sum+d[1])%mod; } else { d[0]=C(i-1,c[1]-1)+d[0]+d[0]; d[0]=(d[0]%mod+mod)%mod; d[1]=C(i-1,c[0]-2)+d[1]+d[1]-C(i,c[0]-1); d[1]=(d[1]%mod+mod)%mod; sum=(sum+d[0])%mod; } res=(res+sum*(i<<1|1))%mod; } cout<<(res%mod+mod)%mod; return 0; }