2018 安徽大学校赛网络赛解题报告
本次网络赛发挥奇差,被各种姿势卡范围、精度、智商。这样下去现场赛堪忧啊QAQ。
下面开始正文,由于B题的标称貌似出现了一些问题,以及H题的考点是计算几何,我对这方面了解为0。所以就先不补了~
A.a*b problem
本题需要注意的就是数据范围,两个2^32-1的数相乘单纯的long long 是存不下的,
但也用不着用大数乘法,一个unsigned long long 就可以搞定。签到题
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ll; int main() { int t; ll a,b; cin>>t; while(t--) { cin>>a>>b; cout<<a*b<<endl; } return 0; }
C.Fibonacci卷积
本题标准解法应该是一个FFT或者NTT,但聪明的蕊蕊想到了一个非常骚的方法。可以使复杂度从nlogn 降到 n ,可以说是非常优秀了。
下面来介绍一个这个解法。
首先我们应该知道朴素的解法是怎样的:
1 for(int i=0;i<=n;++i) 2 for(int j=0;j<=n;++j) 3 c[i+j]=(ans[i+j]+1LL*f[i]*f[j]%mod)%mod;
对于每一个a[i] 都需要计算 n 次来维护c数组。有没有办法简化这个操作呢?
方法当然是有的。举个栗子:
假如说 a[i]=1,n=5
朴素的解法是将c数组更新为
0,1,1,2,3,5
而我们现在的解法是将数组变成(操作一):
0,1,0,0,0,-8,-5
然后对于从右到左对每一位进行一个操作:
c[i] = c[i-1] + c [i-2] (操作二)
这样操作一遍之后就会发现 c 数组又变回了本来的样子。
还有一个重要的性质,就是经过多次操作一之后,再进行操作二,还是保证正确性的。
(此处ORZ蕊蕊,能想出这样的方法%%%%%%)
所以O(n)的做法就有了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5; const int mod=998244353; int f[maxn+7],c[maxn*2+7]; void init() { f[0]=0,f[1]=1; for(int i=2;i<maxn+7;++i) f[i]=(f[i-1]+f[i-2])%mod; } void add(int &a,int b) { a=(a+b)%mod; } int main() { ios::sync_with_stdio(false); int t,n; init(); cin>>t; while(t--) { memset(c,0,sizeof(c)); cin>>n; for(int i=0;i<=n;++i){ add(c[i+1],f[i]);//c[i]项是0,操作无意义 add(c[n+1+i],1LL*f[i]*(mod-f[n+1])%mod); add(c[n+2+i],1LL*f[i]*(mod-f[n])%mod); } for(int i=2;i<=2*n;++i) add(c[i],(c[i-1]+c[i-2])%mod); for(int i=0;i<2*n;++i) cout<<c[i]<<" "; cout<<c[2*n]<<endl; } return 0; }
D.瓶子数列
光看题意感觉很难用代码实现,所以我们把题意转化一下:
从0到第i个数的最长不下降子序列的长度加上第i个数到最后一个数的最长不下降子序列的长度之和最大为多少。
如果这个长度只和加起来等于N,说明这个序列是一个瓶子序列。如果不等于N,则答案要加上一个1来满足题意
#include<bits/stdc++.h> using namespace std; const int maxn=1010; int a[maxn]; int l[maxn],r[maxn]; int main() { ios::sync_with_stdio(false); // freopen("in.txt","r",stdin); int t,n; cin>>t; while(t--) { memset(l,0,sizeof(l)); memset(r,0,sizeof(r)); cin>>n; for(int i=0;i<n;++i) cin>>a[i]; l[0]=r[n-1]=1; for(int i=1;i<n;++i){ l[i]=1; for(int j=0;j<i;++j) if(a[i]<=a[j]) l[i]=max(l[i],l[j]+1); } for(int i=n-2;i>=0;--i){ r[i]=1; for(int j=n-1;j>i;--j) if(a[i]<=a[j]) r[i]=max(r[i],r[j]+1); } int ans=0; for(int i=0;i<n;++i) ans=max(ans,l[i]+r[i]-1); if(ans!=n) ans+=1; cout<<ans<<endl; } return 0; }
E.分组
本场比赛第二个签到题。贪心的去想就好了。就是把总人数除以3
#include<bits/stdc++.h> using namespace std; int main() { int t,n; cin>>t; while(t--) { cin>>n; cout<<n/3<<endl; } return 0; }
F.Operation II
这个题我本来打算用bfs去做,但应该是超时的。所以就没有试了,下面介绍一个非常巧妙的办法:
首先特判一下k=1的情况,这样的话肯定答案只能是(n-1)*a
然后我们先不管a,b。只考虑最短需要多少步能达到目标。
1 int len=-1; 2 while(n) 3 { 4 c[++len]=n%k; 5 n/=k; 6 }
就像这样。c[i] 表示的是第i次除法操作之前需要的减法操作的次数 , len的值是除法操作的次数
然后对每一次除法操作分别判断如果不进行除法操作而进行减法操作所需要的花费
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int INF=1<<30; int c[110]; int main() { ios::sync_with_stdio(false); //freopen("in.txt","r",stdin); int t; ll n,k,a,b; cin>>t; while(t--) { ll ans=INF; cin>>n>>k>>a>>b; if(k==1){ ans=1ll*(n-1)*a; cout<<ans<<endl; continue; } memset(c,0,sizeof(c)); int len=-1; while(n) { c[++len]=n%k; n/=k; } ans=1ll*b*len; for(int i=0;i<=len;++i) ans+=a*c[i]; //ans为步骤最少的花费 for(int i=len;i>=1;--i){ ll tmp=ans; tmp-=b; tmp-=a*c[i]; tmp+=k*a*c[i]; if(tmp<ans) ans=tmp; c[i-1]+=k*c[i];//如果上一步不进行除法,那么需要对c数组进行更新 } cout<<ans-a<<endl; } return 0; }
这就是全部的过程了
G.演唱会
这个题我是被范围卡到死,总是以为自己组合数取错了,结果是爆int了···就很僵硬
题目很简单,就是把C,H,E,Y的人数相乘再乘一个C(n,2) n是L的人数。注意范围,结果有可能爆int
#include<bits/stdc++.h> using namespace std; typedef long long ll; int cnt[10]; int main() { //freopen("in.txt","r",stdin); ios::sync_with_stdio(false); int t,n; char name[25]; cin>>t; while(t--) { memset(cnt,0,sizeof(cnt)); cin>>n; for(int i=0;i<n;++i){ cin>>name; if(name[0]=='C') cnt[0]++; else if(name[0]=='H') cnt[1]++; else if(name[0]=='E') cnt[2]++; else if(name[0]=='L') cnt[3]++; else if(name[0]=='Y') cnt[4]++; } ll ans=1; for(int i=0;i<5;++i){ if(i==3) continue; ans=ans*cnt[i]; } if(cnt[3]<2) ans=0; else if(cnt[3]>2&&ans!=0) ans*=(cnt[3]*(cnt[3]-1)/2); cout<<ans<<endl; } return 0; }