文章目录
概述
整体难度还行思维性不是很高,好在寒假刷题的时候开阔了一些眼界所以大致上还算顺利(可能前几天被牛客的集训杀爆了吧hh)
最后一个多小时挣扎了一下A题感觉搞不出来就先到这里写一下题解(当然不是最优解啦,大概讲讲解法,emm部分题目如果有需要的话可以这篇博客评论区里留个言,我可以专门写博客来分析
比赛链接
知识点
题目 | 知识点 |
---|---|
A-SET | gcd |
B-密码解锁 | 快速幂,打表 |
C-夕日坂 | 双指针,尺取法 |
D-小林的AC | 模拟 |
E-小林刷题 | 贪心 |
F-小林移石子 | 中位数,排序 |
G-语汐玩游戏 | 逆序对,dfs,打表 |
H-字符串之谜 | 字符串,dp |
I-小林取数 | 贪心 |
题解
A-SET
分析
选取a和b,进行2a-b操作
先模拟一波,集合中只有两个数,我们假定a>b
那么会出来……2b-a,b,a,2a-b……发现貌似是一个公差为a-b的等差数列
也就是说,我们选两个数,就可以衍生出公差为两数之差的等差数列
现在我们加入c,那么想想一个轴先被a-b划分为等间距的,然后a-b会被划分成c-b和a-c两段,其中大的那段又会被小的那段划分,一直进行下去直到所有段相等,停止划分。也就是说最后会变成一个等差数列,那么他们的公差是多少呢?答案是起始所有相邻段长度的最大公约数(因为你想啊,c-b啊,a-c啊这种每一段都要能被某一长度均分,那不就是公约数码,而且他一旦被均分就停止了,那就取最大呗)
所以我们排个序,把a1作为起点,然后求一下所有相邻段长度的最大公约数就能用下面这个式子把所有能产生的数表示出来了
x
=
a
[
1
]
+
m
∗
g
c
d
(
a
[
2
]
−
a
[
1
]
,
a
[
3
]
−
a
[
2
]
…
…
)
x=a[1]+m*gcd(a[2]-a[1],a[3]-a[2]……)
x=a[1]+m∗gcd(a[2]−a[1],a[3]−a[2]……)
我们令g为最大公约数那么k需要能表示为
k
=
a
[
1
]
+
m
g
k=a[1]+mg
k=a[1]+mg
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX=2e5+11;
int n;
LL a[MAX];
LL gcd(LL a,LL b){
if(b==0)return a;
return gcd(b,a%b);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
LL k;
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
LL g=0;
for(int i=2;i<=n;++i){
g=gcd(g,a[i]-a[i-1]);
}
if((a[1]-k)%g==0){
printf("YES\n");
}
else{
printf("NO\n");
}
}
return 0;
}
标称Hack数据(感谢ph巨佬)
所有数都一样gcd会变成0,无法取模
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX=2e5+11;
int n;
LL a[MAX];
LL gcd(LL a,LL b){
if(b==0)return a;
return gcd(b,a%b);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
LL k;
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
LL g=0;
for(int i=2;i<=n;++i){
g=gcd(g,a[i]-a[i-1]);
}
if(a[1]==k)puts("YES");
else
{
if(g&&(a[1]-k)%g==0)puts("YES");
else puts("NO");
}
}
return 0;
}
B-密码解锁
分析
快速幂+打表
一般这种题目吧,要么数学推导,要么打表找规律
我这种蒟蒻当然是打表找规律啦
数学推导的话(应该可以用欧拉降幂吧,不过我不咋会就打表了)
打表发现基本都是前面一段不循环,后面一段循环的
写的时候标记开始循环的点和循环节的长度即可利用取模运算减小复杂度啦
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long//long long 缩写
const int N=1e5+10;
ll read()//快速读入,你们直接cin或者scanf即可
{
ll x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
return f?-x:x;
}
ll fp(ll a,ll b,ll mod)//快速幂
{
ll res=1;a=a%mod;
while(b)
{
if(b&1)res=(res*a)%mod;
a=(a*a)%mod;b>>=1;
}
return res;
}
ll T,Q,n,m,k,cnt,tmp;//常用变量组
ll ans[N];
bool tj[N];
int main()
{
int a=read();k=read();
while(!tj[a])
{
tj[a]=1;
ans[++cnt]=a;
a=fp(a,a,1000);
}
int st;//标记开始循环位置
for(int i=1;i<=cnt;i++)
if(a==ans[i])st=i;
int t=(k-st)%(cnt-st+1);
cout<<ans[st+t];
return 0;
}
C-夕日坂
分析
连续乘积不超过m
双指针呗,还好我写过博客,先前发群里了如果看了的话应该可以A出这题吧
就是博客里第1题反过来写即可,不过貌似不能前缀积因为太大了,用tmp模拟即可(如果可以的话给俺点个赞?)
双指针尺取法小结
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
ll read()
{
ll x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
return f?-x:x;
}
ll T,Q,n,m,k,cnt,tmp;
ll a[N],ans[N];
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)a[n-i+1]=read();
tmp=1;
for(int i=1,j=1;i<=n;i++)
{
tmp*=a[i];
while(j<=i&&tmp>m)
{
tmp/=a[j];
j++;
}
ans[i]=i-j+1;
}
for(int i=n;i>=1;i--)
{
if(i==n)printf("%lld",ans[n]);
else printf(" %lld",ans[i]);
}
return 0;
}
D-小林的AC
分析
如果你刷林大oj的话应该记得一道西红柿炒鸡蛋吧,短板效应,统计最少的即可,e和c有两个要特殊处理
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF=0x3f3f3f3f;
ll T,Q,n,m,k,p,ans,cnt,sum,tmp;
unordered_map<char,int>mp;
string s;
int main()
{
while(cin>>s)
{
int minx=INF;
mp.clear();
for(int i=0;s[i];i++)mp[s[i]]++;
for(auto t:mp)
{
char x=t.first;
int y=t.second;
if(x=='c'||x=='e')minx=min(minx,y/2);
else if(x=='a'||x=='p'||x=='t'||x=='d')minx=min(minx,y);
}
printf("%lld\n",minx);
}
return 0;
}
E-小林刷题
分析
比较基础的贪心,按a[i]/b[i]排序即可下附证明
把交换前交换后相邻两项写一下
交换相邻两项的话值影响这两项的值,其他的是不影响的
下面公式里S[i]数后缀和,数组b[i]从第i个到最后的和
做一下简单的化简即可,划线的可以约去
比较
a
[
i
]
∗
b
[
i
+
1
]
?
a
[
i
+
1
]
∗
b
[
i
]
a[i]*b[i+1]?a[i+1]*b[i]
a[i]∗b[i+1]?a[i+1]∗b[i]即可,最后移项就得到按比值排序
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10,INF=0x3f3f3f3f;
ll T,Q,n,m,k,ans,cnt,sum,tmp;
struct problem
{
double a,b,t;
}p[N];
ll s[N];
bool cmp(problem x,problem y)
{
if(x.t!=y.t)return x.t<y.t;
return 0;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
double a,b,t;
scanf("%lf%lf",&a,&b);
t=a/b;
p[i]={a,b,t};
}
sort(p+1,p+1+n,cmp);
for(int i=n;i>=1;i--)s[i]=s[i+1]+p[i].b;
for(int i=1;i<n;i++)ans+=p[i].a*s[i+1];
cout<<ans;
return 0;
}
F-小林移石子
分析
看到石子脑子里想到堆和区间DP,瞬间紧张。but,这题和这两个没啥关系,货舱选址问题取中位数即可
∑
i
=
1
n
∣
a
[
i
]
−
x
∣
\sum_{i=1}^n|a[i]-x|
∑i=1n∣a[i]−x∣要让这个式子最小取中位数就可以啦,高中知识
绝对值求和图像就这两种,分n的奇偶即可,不难证明
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
ll read()
{
ll x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
return f?-x:x;
}
ll T,Q,n,m,k,p,ans,cnt,sum,tmp;
ll a[N];
int main()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+1+n);
ans=0;
for(int i=1;i<=n;i++)ans+=abs(a[i]-a[(n+1)/2]);
cout<<ans;
return 0;
}
G-语汐玩游戏
分析
应该是jsc(啊啊我之前把果光当成jsc学长了,别打我)学长出的题吧dfs/打表应该可做,不过我选择逆序对嘻嘻,其实原型应该是奇数码问题吧
逆序对可用于解决:交换+可达局面(codeforces上也碰到过相关的用逆序对来构造)
直接说结论吧:
奇数码:每次变化逆序对变化偶数个,所以目标和开始的局面逆序对要奇偶相同
偶数码:逆序对之差和两局面下空格所在行数之差奇偶相同
这题偶数码用第二个结论即可
相关推导的话你们可以自己买一本蓝书来看
或者可以看看这篇知乎文章
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll T,Q,n,m,k,cnt,tmp;
char g[3][3],need[3][3];
string a,b;
int nxa,nxb,rowa,rowb;//nx为逆序数,row为x所在行
int main()
{
for(int i=0;i<2;i++)cin>>g[i];
for(int i=0;i<2;i++)cin>>need[i];
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
{
if(g[i][j]!='X')a+=g[i][j];
else rowa=i;
if(need[i][j]!='X')b+=need[i][j];
else rowb=i;
}
//计算逆序数
for(int i=0;i<3;i++)
{
for(int j=0;j<i;j++)
{
if(a[j]>a[i])nxa++;
if(b[j]>b[i])nxb++;
}
}
int ans=abs(nxa-nxb)+abs(rowa-rowb);
if(ans%2==0)printf("YES");
else printf("NO");
return 0;
}
H-字符串之谜
分析
这题训练做过吧,没做出来的话建议反思吧,训练里的是奶牛的,不讲了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
string s;
int main()
{
ll a=0,b=0,c=0;
cin>>s;
for(int i=0;s[i];i++)
{
if(s[i]=='a')a++;
if(s[i]=='b')b+=a;
if(s[i]=='c')c+=b;
}
cout<<c;
return 0;
}
I-小林取数
分析
直接全部取,如果不是10倍数直接输出,是的话看有没有不是10倍数的数,有的话减去
如果全都是10的倍数,那就裂开,直接输出0
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10,INF=0x3f3f3f3f;//INF为一个很大的值你写个1e9+7也可以
ll read()
{
ll x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
return f?-x:x;
}
int T,Q,n,m,k,p,ans,cnt,sum,tmp;
int a[N];
int main()
{
n=read();
int minx=INF;
bool flag=1;
for(int i=1;i<=n;i++)
{
a[i]=read();sum+=a[i];
if(a[i]%10!=0)
{
minx=min(minx,a[i]);
flag=0;
}
}
if(sum%10==0&&!flag)cout<<sum-minx;
else if(sum%10!=0&&!flag)cout<<sum;
else if(flag&&sum%10==0)cout<<0;
return 0;
}