2022牛客寒假算法基础集训营1
A.九小时九个人九扇门
分析:
-
要先发现一个特点:对于多个数的合并,先求和再求根,与先分别求根 再求根的和 的根,效果是一样的,
举个栗子:17+23+79=119=2,8+5+7=20=2
因此,先分别将n个数求取根,就变成n个1~9的数了
-
然后就是类似01背包的动态转移
d p [ i ] [ c h a n g e ( x + j ) ] + = d p [ i − 1 ] [ j ] dp[i][change(x+j)]+=dp[i-1][j] dp[i][change(x+j)]+=dp[i−1][j]
-
转换也可以不用递归来写,模9取余也可以,注意一点,为9的倍数时,是余0的就行
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5, mo=998244353;
int change(int x)
{
while(x>9)
{
int t=x,s=0;
while(t)
{
s+=t%10;
t/=10;
}
x=s;
}
return x;
}
int dp[N][11];
void solve()
{
int n;
cin>>n;
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
for(int j=0;j<=9;j++)
{
dp[i][j]=(dp[i][j]+dp[i-1][j])%mo;
int k=change(x+j); // =(x+j)%9;
dp[i][k]=(dp[i][k]+dp[i-1][j])%mo;
}
}
for(int i=1;i<=9;i++) cout<<dp[n][i]<<' ';
cout<<endl;
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
}
B.炸鸡块君与FIFA22
分析:(待补充)
C.Baby’s first attempt on CPU
分析:
- 将两句有关联的语句隔开(即使两者之间的语句数量至少为3)
- 模拟,d[i]表示当前语句是第几条(加上空白的),与d[i-j]+3比较取大值
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int d[N];
void solve()
{
int n;
cin>>n;
int ans=0;
for(int i=1;i<=n;i++)
{
d[i]=d[i-1];
for(int j=1;j<=3;j++)
{
int x; cin>>x;
if(x) d[i]=max(d[i],d[i-j]+3);
}
d[i]++;
}
cout<<d[n]-n<<endl;
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
}
D.牛牛做数论
分析:
-
欧拉函数: φ ( n ) = n × ∏ i = 1 s p i − 1 p i \varphi(n) = n \times \prod_{i = 1}^s{\dfrac{p_i - 1}{p_i}} φ(n)=n×∏i=1spipi−1
故 H ( x ) = ∏ i = 1 s p i − 1 p i H(x)=\prod_{i=1}^s\frac{p_i-1}{p_i} H(x)=∏i=1spipi−1
-
后者即求范围内最大的质数
-
前者即求不同质因子数量最多的数,且要求最小(即 2 ∗ 3 ∗ 5 ∗ 7 ∗ . . . 2*3*5*7*... 2∗3∗5∗7∗...)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int a[N],b[N],s[N];
void solve()
{
int n;
cin>>n;
int r=n,fg=1;
while(fg)
{
fg=0;
for(int i=2;i<=sqrt(r);i++)
{
if(r%i==0)
{
fg=1;
r--;
break;
}
}
}
int tot=0;
for(int i=2;i<=1000;i++)
{
int fg=1;
for(int j=2;j<=sqrt(i);j++)
{
if(i%j==0)
{
fg=0;
break;
}
}
if(fg) b[++tot]=i;
}
int ans=1;
for(int i=1;i<=tot;i++)
{
if(ans*b[i]<=n) ans*=b[i];
else break;
}
if(n==1) cout<<-1<<endl;
else cout<<ans<<' '<<r<<endl;
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}
E.炸鸡块君的高中回忆
- 签到题
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char s[N];
void solve()
{
int n,m;
cin>>n>>m;
if(m==1)
{
if(n==1) cout<<1<<endl;
else cout<<-1<<endl;
}
else
{
int ans=1+(n-m)/(m-1)+((n-m)%(m-1)!=0);
cout<<2*ans-1<<endl;
}
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}
F.中位数切分
分析:
-
emmm… 这题我是读假题了,然后一眼看出来(还刚好和正确结论撞上了)
我所理解的题意:将序列划分成若干段(可以不连续),那不就是将所有的"<m的数"全部放在一个序列里,再用">=m"的去配成一个合法的,然后剩下的">m"的全部单独拎出来即可、
-
以下是错误理解,但是 A 了的代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N];
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
int p=lower_bound(a+1,a+1+n,m)-a;
if(n-p+1<=p-1) cout<<-1<<endl;
else
{
cout<<1+(n-p-p+1)<<endl;
}
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}
正解
-
(假设"<m"的个数为a,">=m"的个数为b)
-
对于某一段连续的序列,要使该序列合法,则 b > = a + 1 b>=a+1 b>=a+1
根据这个结论,去划分,使得每个单独序列的 b ‘ − a ‘ = 1 b^{`}-a^{`}=1 b‘−a‘=1
这样, a n s = b − a ans=b-a ans=b−a
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N];
void solve()
{
int n,m;
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(x<m) cnt++;
}
int ans=n-2*cnt;
if(ans>0) cout<<ans<<endl;
else cout<<-1<<endl;
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}
G.ACM is all you need
分析:
-
map 遍历,分类讨论
-
若对 a [ i ] ( 1 < i < n ) a[i](1<i<n) a[i](1<i<n) 展开讨论,会有四种情况
- a [ i ] < a [ i + 1 ] a n d a [ i ] < a [ i − 1 ] a[i]<a[i+1] \ and\ a[i]<a[i-1] a[i]<a[i+1] and a[i]<a[i−1]:会对 ans 贡献1,当b比某个值还要大时,会对 ans 贡献-1
- a [ i ] > a [ i + 1 ] a n d a [ i ] > a [ i − 1 ] a[i]>a[i+1] \ and\ a[i]>a[i-1] a[i]>a[i+1] and a[i]>a[i−1]:当b大于某个值时,会对 ans 贡献1
- a [ i ] > a [ i + 1 ] a n d a [ i ] < a [ i − 1 ] a[i]>a[i+1] \ and\ a[i]<a[i-1] a[i]>a[i+1] and a[i]<a[i−1]:当b大于某个值会对 ans 贡献1,再大一些,大于第二个值时,会贡献-1
- a [ i ] < a [ i + 1 ] a n d a [ i ] > a [ i − 1 ] a[i]<a[i+1] \ and\ a[i]>a[i-1] a[i]<a[i+1] and a[i]>a[i−1]:同(3)
-
最后的贡献,就是“某个值”从小到大,贡献值的前缀和
-
每种情况的“某个值”,可以通过画图或举例来求出具体如何表示
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N];
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ans=0;
map <int,int> mp;
for(int i=2;i<n;i++)
{
if(a[i]<a[i+1] && a[i]<a[i-1])
{
ans++;
int mn=min(a[i-1],a[i+1]);
mp[(a[i]+mn+1)/2]++;
}
else if(a[i]>a[i+1] && a[i]>a[i-1])
{
int mx=max(a[i+1],a[i-1]);
mp[(a[i]+mx)/2+1]--;
}
else if(a[i]>a[i+1] && a[i]<a[i-1])
{
mp[(a[i]+a[i+1])/2+1]--;
mp[(a[i]+a[i-1]+1)/2]++;
}
else if(a[i]>a[i-1] && a[i]<a[i+1])
{
mp[(a[i]+a[i-1])/2+1]--;
mp[(a[i]+a[i+1]+1)/2]++;
}
}
int now=0,mx=0;
for(auto it : mp)//遍历
{
now+=it.second;
mx=max(now,mx);
}
cout<<ans-mx<<endl;
}
signed main()
{
int t=1;
cin>>t;
while(t--) solve();
}
H.牛牛看云
分析:
-
二分,前缀和,绝对值运算
-
直接计算 O ( n 2 ) O(n^2) O(n2),炸了,考虑优化
-
首先,是去绝对值,同时考虑 a i , a j a_i,a_j ai,aj 太头疼,可以通过一步变换,将两者分离
分两种情况:
- a i + a j − 1000 > = 0 a_i+a_j-1000>=0 ai+aj−1000>=0: a n s + = a j − ( 1000 − a i ) ans+=a_j-(1000-a_i) ans+=aj−(1000−ai)
- a i + a j − 1000 < 0 a_i+a_j-1000<0 ai+aj−1000<0: a n s + = ( 1000 − a i ) − a j ans+=(1000-a_i)-a_j ans+=(1000−ai)−aj
令 1000 − a i = d 1000-a_i=d 1000−ai=d , 那么对于这组序列,若从小到大排序(排序之后显然不会影响最后结果),并二分找到第一个">d" 的数 a [ p ] a[p] a[p],根据上述两种情况,便可得到对ans的贡献:
a n s + = ( p − i ) ∗ d − ( s [ p − 1 ] − s [ i − 1 ] ) + s [ n ] − s [ p − 1 ] − ( n − p + 1 ) ∗ d ; ans+=(p-i)*d-(s[p-1]-s[i-1])+s[n]-s[p-1]-(n-p+1)*d; ans+=(p−i)∗d−(s[p−1]−s[i−1])+s[n]−s[p−1]−(n−p+1)∗d;
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int a[N],b[N],s[N];
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
int ans=0;
for(int i=1;i<=n;i++)
{
int d=1000-a[i];
int p=upper_bound(a+i,a+1+n,d)-a;
ans+=(p-i)*d-(s[p-1]-s[i-1])+s[n]-s[p-1]-(n-p+1)*d;
}
cout<<ans<<endl;
}
signed main()
{
int T;
//cin>>T;
T=1;
while(T--) solve();
}
暴力求解
- 赛后才发现 0 = < a i < = 1000 0=<a_i<=1000 0=<ai<=1000, 那就可以直接记录个数,直接暴力算贡献
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int b[N];
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int x; cin>>x;
b[x]++;
}
int ans=0;
for(int i=0;i<=1000;i++)
{
for(int j=i;j<=1000;j++)
{
if(i==j) ans+=b[i]*(b[i]+1)/2*abs(i+j-1000);
else ans+=b[i]*b[j]*abs(i+j-1000);
}
}
cout<<ans<<endl;
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
}
I.B站与各唱各的
分析:
-
一句歌词失败的概率为 2 2 n \frac{2}{2^n} 2n2
故期望为 m × ( 1 − 1 2 n − 1 ) m\times(1-\frac{1}{2^{n-1}}) m×(1−2n−11)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005,mo=1e9+7;
int ksm(int a,int b,int p)
{
int ans=1;
while(b)
{
if(b&1) ans=ans*a%p;
a=a*a%p; b>>=1;
}
return ans;
}
void solve()
{
int n,m;
cin>>n>>m;
int d=ksm(2,n-1,mo);
int ans=m*(1+mo-ksm(d,mo-2,mo))%mo;
cout<<ans<<endl;
}
signed main()
{
int T=1;
cin>>T;
while(T--) solve();
}
J.小朋友做游戏
- 签到题
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],b[N];
void solve()
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
sort(a+1,a+1+n);
sort(b+1,b+1+m);
int d=(k+1)/2;
if(n<d )
{
cout<<-1<<endl;
}
else
{
int ans=0,r=n;
while(r>n-d) ans+=a[r], r--;
int l=m,cnt=0;
while(cnt++<k-d)
{
if(a[r]>b[l]) ans+=a[r], r--;
else ans+=b[l], l--;
}
cout<<ans<<endl;
}
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}
K. 冒险公社
题意:
- 根据预测情况,求前 n 个岛最多的绿岛数
分析:
-
没有思路的情况下,先想了一下暴力, O ( 3 n ) O(3^n) O(3n) 显然超时
暴力枚举所有情况,每次都会考虑n个岛的具体颜色
但实际上,对于每次预判我们只需要考虑当前岛和其前两个岛的颜色即可
所以,考虑线性DP,枚举每三个岛的情况,记录状态,再状态转移即可
-
d p [ i ] [ y ] [ z ] dp[i][y][z] dp[i][y][z] 表示前 i 个岛,第i个岛颜色为z,第i-1个岛颜色为y的情况下,最多绿岛数,那么状态转移方程为,
dp[i][y][z]=max(dp[y][z],dp[x][y]+(z==1));
然后根据当前预测值分为三类情况考虑:(g表示三个岛绿岛数,r表示红岛数)
-
s[i]==‘R’:r>g 时才会进行状态转移
-
s[i]==‘G’:g>r 时,~~
-
s[i]‘B’:gr 时,~~
-
-
坑点:最后答案为0时,也是合法情况,因此dp初值要赋为无穷小才能得到非法情况"-1"
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char s[N];
int dp[N][5][5];
void solve()
{
int n;
cin>>n>>s+1;
memset(dp,-0x3f,sizeof(dp));
// 预处理
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
int g=(i==1)+(j==1), r=(i==2)+(j==2);
dp[2][i][j]=g;
}
}
for(int i=3;i<=n;i++)
{
for(int x=1;x<=3;x++)
for(int y=1;y<=3;y++)
for(int z=1;z<=3;z++)
{
int g=(x==1)+(y==1)+(z==1), r=(x==2)+(y==2)+(z==2);
if((g==r && s[i]=='B') || (g>r && s[i]=='G') || (g<r && s[i]=='R'))
{
dp[i][y][z]=max(dp[i][y][z],dp[i-1][x][y]+(z==1));
}
}
}
int ans=-0x3f3f3f3f;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
ans=max(ans,dp[n][i][j]);
}
}
if(ans>=0) cout<<ans<<endl;
else cout<<-1<<endl;
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
}
L.牛牛学走路
- 签到题
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char s[N];
void solve()
{
int n;
cin>>n;
cin>>s+1;
double x=0,y=0,ans=0;
for(int i=1;i<=n;i++)
{
if(s[i]=='U') y++;
if(s[i]=='D') y--;
if(s[i]=='R') x++;
if(s[i]=='L') x--;
ans=max(ans,sqrt(x*x+y*y));
}
printf("%.12lf\n",ans);
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}