2020牛客寒假算法基础集训营1 解题报告 Apare_xzc
2020牛客寒假算法基础集训营1 解题报告 Apare_xzc
比赛链接:2020牛客寒假算法基础集训营1<–
2020-02-04 13:00:00 至 2020-02-04 18:00:00
时长: 5小时
已有3952人报名
官方题解:【题解】牛客寒假集训营第一场 <–
A. honoka和格点三角形
题目链接
分析:
求格点内能连成的面积为1的三角形的个数,要求三角形至少有一条边与x轴或y轴平行。
满足条件的三角形的总数ans = 有一条边与x轴平行的三角形个数 ans1 有一条边与y轴平行的三角形的个数 ans2 - 直角三角形的个数ans3
计算的方法都相同,以平行于x轴的三角形为例。三角形面积为1,一定是底为1高为2,或者底为2,高为1。那么必然有两个点的纵坐标相同,他们在纵坐标一定的时候,横坐标的范围是1到m, 于是底为1的两点有m-1种取法,每种取法可以改变纵坐标,上下移动,可知横坐标确定了的有一边平行于x轴的三角形第三个点的纵坐标有(n-2)*2种,纵坐标确定之后,三角形的高确定,第三个点有m中取法(直角三角形有2种)。
其它的情况都类似,互换m,n即可。具体公式见代码。
我的代码:
#include <bits/stdc .h>
using namespace std;
#define LL long long
LL f(LL m,LL n)
{
return ((m-2)*m%mod*(n-1)%mod*2%mod (m-1)*m%mod*(n-2)%mod*2%mod )%mod;
}
LL calRT(LL m,LL n)
{
return ( (m-2)*2%mod*(n-1)%mod*2%mod (m-1)*2%mod*(n-2)%mod*2%mod )%mod;
}
int main()
{
LL m,n,ans;
while(cin>>m>>n)
{
ans = (f(m,n) f(n,m))%mod;
ans = ((ans - calRT(m,n)) % mod mod)%mod;
cout<<ans<<endl;
}
return 0;
}
B. kotori和bangdream
题目链接
分析:
签到题,期望 = 1.0xa/100 1.0b(100-x)/100*n
代码
#include <bits/stdc .h>
using namespace std;
int main()
{
int n,x,a,b;
cin>>n>>x>>a>>b;
double ans = 1.0*x*a/100 1.0*b*(100-x)/100;
ans *= n;
printf("%.2f\n",ans);
return 0;
}
C. umi和弓道
题目链接 不想写计算几何,这题没写,跳过吧…
D. hanayo和米饭
题目链接
分析:
1到n的数列少了一个,给n-1个,求少的那一个。直接 (1 n)*n/2 - sum(arr) 即可。用n个数的和减去n-1个数的和
#include <bits/stdc .h>
using namespace std;
int main()
{
int n,x;
cin>>n;
LL sum = 1ll*(1 n)*n/2;
for(int i=1;i<n; i)
scanf("%d",&x),sum -= x;
cout<<sum<<endl
return 0;
}
E. rin和快速迭代
题目链接
分析:
也没有找到什么规律或者公式。那就按题意模拟吧。我们只需要进行迭代,每次用f(x)代替x,直到x变为2。求迭代了多少次。如何求x因数的个数呢?我们先对x进行分解质因数,得到
x = P1^C1 * P2^C2 * ... * Pk^Ck
其中P1,P2...Pk均为质数
那么每个质因数Pi的次数可以选{0,1,2…Ci},共有Ci 1种选法,那么根据乘法原理,因数的个数就是Ci 1的乘积。
我们可以先筛出1E6之内的质数,加快分解因式的速度。
我用的是欧拉线性筛,复杂度为O(n), 分解质因数的复杂度大约为根号n
我的代码
#include <bits/stdc .h>
using namespace std;
#define LL long long
bool notPrime[maxn];
int sushu[maxn],cnt;//cnt表示素数的个数
void getPrime(int n); //欧拉筛法线性筛素数
LL getNumFac(LL x); //获得x因子的个数
int main()
{
getPrime(1000000 10);
LL x;
while(cin>>x)
{
int ans = 0;
while(x!=2)
{
x = getNumFac(x);
ans;
}
cout<<ans<<endl;
}
return 0;
}
void getPrime(int n)
{
cnt = 0;
for(int i=2;i<=n; i)
{
if(!notPrime[i]) sushu[cnt ] = i;
for(int j=0;j<cnt&&1ll*i*sushu[j]<=n; j)
{
notPrime[i*sushu[j]] = true;
if(i%sushu[j]==0) break;
}
}
}
LL getNumFac(LL x)
{
LL res = 1;
for(int i=0;i<cnt&&sushu[i]<=x/sushu[i]; i)
{
int p = sushu[i];
if(x%p) continue;
int t = 0;
while(x%p==0) t, x/=p;
res *= (t 1);
}
if(x>1) res *= 2;
return res;
}
F. maki和tree
题目链接
分析:
求树上两点间路径只有一个黑色节点的对数。我们只需要对每一个黑色节点dfs,求出包含该黑色节点的路径数(点的对数)相加即可。
每个黑节点有若干个白色节点与之相连,相互不经过黑色节点可达的白色节点看成是一个连通的整体,我们dfs求出这些连通的整体中白色节点的个数,记录这些个数v0,v1,v2… 可以存在一个vector之中。那么,如果黑色节点是点对两点中的某一点,路径的数目为v0,v1,v2…之和,也就是黑色节点不经过其它黑色节点可达的白色节点的个数之和。如果黑色节点不是点对中的某一点,那么两个点一定在不同的连通整体中,这样的路径数位v0,v1,v2… 这些数两两相乘相加之和。
我的代码:
#include <bits/stdc .h>
using namespace std;
const int maxn = 3e5 100;
#define LL long long
#define Mst(a,b) memset(a,(b),sizeof(a))
char color[maxn];
struct Node{
int to,Next;
}node[maxn*2];
int tot,head[maxn];
void initEdge()
{
tot = 0;
memset(head,-1,sizeof(head));
}
bool vis[maxn];
void addedge(int u,int v)
{
node[tot].to = v;
node[tot].Next = head[u];
head[u] = tot ;
}
LL DFS(int x)
{
if(color[x]=='B') return 0;
vis[x] = true;
LL sum = 1;
for(int i=head[x];i!=-1;i=node[i].Next)
{
int to = node[i].to;
if(color[to]=='B'||vis[to]) continue;
sum = DFS(to);
}
vis[x] = false;
return sum;
}
int main()
{
// freopen("in.txt","r",stdin);
int n,u,v;
scanf("%d%s",&n,color 1);
initEdge();
for(int i=1;i<n; i)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
LL ans = 0;
LL cc = 0;
for(int i=1;i<=n; i)
{
if(color[i]=='B') //
{
vector<LL> v;
for(int t=head[i];t!=-1;t=node[t].Next)
{
int to = node[t].to;
if(color[to]=='W')
{
cc = DFS(node[t].to);
v.push_back(cc);
}
}
for(auto z:v) ans =z;
if((int)v.size()<2) continue;
int len = v.size();
for(int ii=0;ii<len; ii)
{
for(int jj=ii 1;jj<len; jj)
{
ans = v[ii]*v[jj];
}
}
}
}
cout<<ans<<endl;
return 0;
}
G. eli和字符串
题目链接
分析:
我们可以每个小写字母开一个vector,记录该字母在字符串中出现的所有位置,是一个递增的序列。然后我们每个字母从v[i]开始,v[i k-1]结束的这一段含有k个相同的字母,用v[i k-1] - v[i] 1更新答案即可
我的代码:
#include <bits/stdc .h>
using namespace std;
const int maxn = 1e6 100;
char str[maxn];
int main()
{
int n,k;
cin>>n>>k;
scanf("%s",str);
vector<int> v[320];
for(int i=0;i<n; i)
{
v[(int)str[i]].push_back(i);
}
int ans = n 100;
for(int i='a';i<='z'; i)
{
int len = v[i].size();
if(len<k) continue;
for(int j=0;j k-1<len; j)
{
ans = min(ans,v[i][j k-1]-v[i][j] 1);
}
}
if(ans==n 100) ans = -1;
cout<<ans<<endl;
return 0;
}
H. nozomi和字符串
题目链接
分析:
在K次操作之内,求最大的数字相同的子串的长度。数字都相同,无非只有两种情况,一种是全部为0,一种是全部为1。
我们可以二分答案,枚举子串的起点,二分子串的长度。如果是全1串,我们需要求得串中0的个数,来判断是否能在K次操作之内都变为1。我们可以维护一个0的个数的前缀和(1同理) ,于是我们可以O(1)check。起点一定的话,0或1的个数也随着长度的增加单调非递减,满足二分的性质。
我的代码:
#include <bits/stdc .h>
using namespace std;
const int maxn = 1e6 100;
char str[maxn];
int sum0[maxn],sum1[maxn];
int main()
{
int n,k;
cin>>n>>k;
scanf("%s",str 1);
for(int i=1;i<=n; i)
{
sum1[i] = sum1[i-1];
sum0[i] = sum0[i-1];
if(str[i]=='1') sum1[i];
else sum0[i];
}
int ans = 0;
int left,mid,right;
for(int i=1;i ans-1<=n; i)
{
left = i,right = n 1;
while(right-left>1)
{
mid = (left right)>>1;
if(sum0[mid]-sum0[i-1]<=k) left = mid;
else right = mid;
}
ans = max(ans,left-i 1);
}
for(int i=1;i ans-1<=n; i)
{
left = i,right = n 1;
while(right-left>1)
{
mid = (left right)>>1;
if(sum1[mid]-sum1[i-1]<=k) left=mid;
else right = mid;
}
ans = max(ans,left-i 1);
}
cout<<ans<<endl
return 0;
}
I. nico和niconiconi
题目链接
分析:
简单dp,官方题解说的很明白
我的代码:
#include <bits/stdc .h>
using namespace std;
const int maxn = 3e5 100;
#define LL long long
char str[maxn];
LL n,a,b,c;
char A[] = "nico";
char B[] = "niconi";
char C[] = "niconiconi";
bool equalA(int);
bool equalB(int);
bool equalC(int);
LL dp[maxn];
int main()
{
scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
scanf("%s",str 1);
for(int i=1;i<=n; i)
{
dp[i] = dp[i-1];
if(equalA(i-3)) dp[i] = max(dp[i],dp[i-4] a);
if(equalB(i-5)) dp[i] = max(dp[i],dp[i-6] b);
if(equalC(i-9)) dp[i] = max(dp[i],dp[i-10] c);
}
cout<<dp[n]<<endl;
return 0;
}
bool equalA(int st)
{
if(st<1) return false;
for(int i=0;i<4; i)
if(str[st i]!=A[i]) return false;
return true;
}
bool equalB(int st)
{
if(st<1) return false;
for(int i=0;i<6; i)
if(str[st i]!=B[i]) return false;
return true;
}
bool equalC(int st)
{
if(st<1) return false;
for(int i=0;i<10; i)
if(str[st i]!=C[i]) return false;
return true;
}
J. u’s的影响力
题目链接
分析:
如果n<=2,直接输出x或y,如果n>=3,可以发现x和y的指数都是斐波那契数列的某一项,a^b的系数是也是某一项-1
Pab[i] = Pab[i-1] Pab[i-2] 1,一开始我直接退出一个三乘三的系数矩阵
// x[i 2] = x[i 1] x[i] 1
// |x[i 2]| |1 1 1| |x[i 1]| 0 x2 X[n 1]
// |x[i 1]| = |1 0 0| * |x[i 0]| 0 x1 -- n-1次 --> X[n]
// | 1 | |0 0 1| | 1 | 1
后来发现就是Feibonaci(n 1)-1
x的系数为Fei(n-1), y的系数为Fei(n) 如果像下面这样定义的话
// id 1 2 3 4 5 6 7 8 9
//Fei 0 1 1 2 3 5 8 13 21
//x 1 0 1 1 2 3 5 8 13 Fei(n-1)
//y 0 1 1 2 3 5 8 13 21 Fei(n )
//a^(b) 0 0 1 2 4 7 12 20 33 Fei(n 1)-1
我们可以用费马小定理,计算Fei[n]的时候用1000000007的欧拉函数作为模数。由于1E9 7是质数,质数的欧拉函数等于它自己减一,所以求Fei的时候对1E9 6取模即可。
由于x,y,a^b这三个底数可能是1E9 7的倍数,结果一定为0,要注意一下。
如果指数对1000000006取模后变为0,那么x^0 返回1,就会出错,所以建议快速幂这么写:
#define LL long long
LL fast_pow(LL a,LL b,LL mod)
{
if(a==0) return 0; //加一个特判以防万一
LL ans = 1;
while(b)
{
if(b&1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
求斐波那契额数列的第n项可以用矩阵快速幂,系数矩阵如下:
// fei[n ] = |1 1|^n-2 |fei[2]| 1
// fei[n-1] |0 1| * |fei[1]| 0
我的代码:
#include <bits/stdc .h>
#define LL long long
using namespace std;
const int N = 3;
const int mod = 1e9 7;
LL fast_pow(LL a,LL b,LL mod);
void Mul_Matrix(LL a[N][N],LL b[N][N],int n,int mod);
void Matrix_fast_pow(LL a[N][N],LL b,int n,int mod);
LL getFeiboNa(LL n,int mod);
int main()
{
LL a,b,x,y,n;
cin>>n>>x>>y>>a>>b;
a %= mod;
x %= mod;
y %= mod;
if(n==1)
{
cout<<x<<endl;return 0;
}
if(n==2)
{
cout<<y<<endl;return 0;
}
a = fast_pow(a,b,mod);
LL pa,px,py,ans;
pa = (getFeiboNa(n 1,mod-1)-1 mod-1)%(mod-1);
ans = fast_pow(a,pa,mod);
px = getFeiboNa(n-1,mod-1);
py = getFeiboNa(n,mod-1);
ans = ans * fast_pow(x,px,mod) % mod * fast_pow(y,py,mod) % mod;
printf("%lld\n",ans);
return 0;
}
LL fast_pow(LL a,LL b,LL mod)
{
if(a==0) return 0;
LL ans = 1;
while(b)
{
if(b&1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void Mul_Matrix(LL a[N][N],LL b[N][N],int n,int mod)
{
LL c[N][N] = {0};
for(int i=0;i<n; i)
for(int j=0;j<n; j)
for(int k=0;k<n; k)
c[i][j] = (c[i][j] a[i][k]*b[k][j]%mod)%mod;
for(int i=0;i<n; i)
for(int j=0;j<n; j)
a[i][j] = c[i][j];
}
void Matrix_fast_pow(LL a[N][N],LL b,int n,int mod)
{
LL ans[N][N] = {0};
for(int i=0;i<n; i)
ans[i][i] = 1;
while(b)
{
if(b&1) Mul_Matrix(ans,a,n,mod);
Mul_Matrix(a,a,n,mod);
b >>= 1;
}
for(int i=0;i<n; i)
for(int j=0;j<n; j)
a[i][j] = ans[i][j];
}
LL getFeiboNa(LL n,int mod)
{
if(n<=2) return n-1;
LL m2[N][N] = {
1,1,666,
1,0,999,
5,20,1314,
};
Matrix_fast_pow(m2,n-1,2,mod);
return m2[1][0];
}
xzc
2020.2.6 1:08