牛客小白月赛110
A
模拟
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
cin>>n;
int cnt=0;
while(n>500){
++cnt;
n-=500;
}
char c='A'+cnt;
string s;
while(n){
s+=n%10+'0';
n/=10;
}
while(s.size()<3){
s=s+'0';
}
reverse(s.begin(),s.end());
cout<<c<<s<<endl;
return 0;
}
B
按照题意
点击查看代码
#include<bits/stdc++.h>
using namespace std;
string n,m;
string ans;
int main(){
cin>>n>>m;
for(int i=0;i<8;++i)
if(n[i]==m[i]) ans+='g';
else{
if(n.find(m[i])!=-1) ans+='y';
else ans+='r';
}
cout<<ans<<endl;
if(ans.find('y')==-1) cout<<"congratulations"<<endl;
else cout<<"defeat"<<endl;
return 0;
}
C
这种题比较典
通常这种题,让你在满足条件的数组成的数列中,找出第k大的数,而且,这些数单调递增,很适合二分查找
以此题为例:
满足条件:
奇数同时,满足以下之一条件
1.以5为结尾的数
$ 5,15,25,......(a_n=5+(n-1)10$
2.各位数是3的倍数(也就是这个数是3的倍数)
$ 3,9,15,21,......(b_n=3+(n-1)6$
但敏锐地发现,15重复了,再我们确定第k大时,需要剔除重复以免误算
这里就可以用容斥定理
\(|A_1 \cup A_2| = |A_1| + |A_2| - |A_1 \cap A_2|\)
此时我们验证数m
(利用上述数列公式,推出小于m的个数)
以5结尾的奇数 $|A_1|=(m+5)/10 $
3的倍数的奇数 \(|A_2|=(m+6)/6\)
\(15,45,75,......(c_n=15+(n-1)*30)\)
满足上述条件的奇数\(|A_1 \cap A_2|=(m+15)/30\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int t;
ll k;
bool check(ll x){
//分别表示,满足“各个数位相加的和是3的倍数”
//以5结尾
//同时满足上述两个条件的个数
ll a=(x+3)/6,b=(x+5)/10,c=(x+15)/30;
if(a+b-c<k) return 1;
return 0;
}
void solve(){
ll l=3,r=4385714285;
cin>>k;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid)) l=mid+1;
else r=mid-1;
}
cout<<l<<"\n";
return ;
}
int main(){
cin>>t;
while(t--){
solve();
}
return 0;
}
D
提示很明显,长期主义者就是DP,短期主义就是贪心
而这样的选择方式,类似于区间DP
我们定义\(f[l][r]\)是长期主义者,选择剩下区间[l,r]时最大的收益
按照区间DP定义,从区间为1的长度开始转移,所以这个题的DP其实是这个题博弈的逆过程
初始状态f[i][i]=n&1?0:a[i]
- 如果区间长度是奇数,最后选择的是贪心,所以f[i][i]就是0
- 否则,\(f[i][i]=a[i]\)
状态转移看代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int l,r;
const int maxn=1e3+10;
int f[maxn][maxn];
int n;
int a[maxn];
ll sum=0;
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],sum+=a[i];
for(int i=1;i<=n;++i) f[i][i]=n&1?0:a[i];//因为是奇数,所以最后选到i时是长期为0
for(int i=2;i<=n;++i){
for(int l=1;l+i-1<=n;++l){
int r=l+i-1;
if((n-i)&1) f[l][r]=max(a[l]+f[l+1][r],a[r]+f[l][r-1]);//此时区间长度是n-i,所以是长期主义者选择
else f[l][r]=a[l]>=a[r]?f[l+1][r]:f[l][r-1];//贪心选择
}
}
cout<<sum-f[1][n]<<" "<<f[1][n]<<endl;//答案就很容易得到了
return 0;
}
E
对于一个数组,只有下标\(i-j=k\)或者|\(a_i-a_j|=k\),才能交换
所以我们直接想,如果一个数,能同时与两个其他的数交换,那么这三个都能交换,更抽象地想,能交换就说明有关系,就能抽象成图,能交换形成集合,于是可以用并查集记录可以交换的
进一步思考,既然交换后有序,那么排序后的数组和原数组的集合应该相同,也就是连通性相同,所以我们需要各自记录连通性,验证是否完全相同就行
而此时我们很容易发现无论是否排序,|\(a_i-a_j|=k\)都是恒成立的,所以我们可以直接将满足该条件的形成一个并查集
只需要验证\(i-j=k\)也是一定连通的,所以只需要用一个集合set记录下标相差为k的,将其连通
具体实现看代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k;
int a[maxn],b[maxn];
int f[maxn];
map<int,int>mp;
multiset<int>x[maxn],y[maxn];
int find(int x){
if(x!=f[x]) f[x]=find(f[x]);
return f[x];
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;++i){
f[i]=i;
cin>>a[i];
b[i]=a[i];
mp[a[i]]=i;
}
for(int i=1;i<=n;++i){
if(mp[a[i]+k])
f[find(mp[a[i]+k])]=find(mp[a[i]]);//值相差为k可以交换,连通
}
for(int i=1;i<=n;++i)
x[i%k].insert(find(i));//下标相差为k可以交换,加入连通块
sort(b+1,b+1+n);
for(int i=1;i<=n;++i)//有序数组的连通
y[i%k].insert(find(mp[b[i]]));//下标相差为k的,加入b[i]所在在原数组中的位置的连通块
//这里其实是一个反证法,假设,这两个集合是一样的,那么b[i]在原数组的下标所在的连通块,也一定与原数组所在的连通块相同,否则则不能转换
for(int i=0;i<min(n,k);++i)
if(x[i]!=y[i]){//有序序列的连通性和无序的相同才能转换
puts("No");
return ;
}
puts("Yes");
}
int main(){
cin.tie(0);cout.tie(0);
solve();
return 0;
}
F
显然直接地找出每一对\(val(i,j)=\sum_{t=i}^{j}a[t] \% p\)是超时的,而注意,问题问的是是否存在\(val(i,j) \equiv x \pmod p\),
我们稍加转换\(val(i,j)=\frac{val(1,j)}{val(1,i-1)}\),也就是用前缀积转化
于是
\(\frac{val(1,j)}{val(1,i-1)} \equiv x \pmod p\),
这里眼熟的小伙伴应该知道,可以用逆元:
\(val(1,j) \times val(1,i-1)^{-1} \equiv x \pmod p\),
所以根据这个表达式,当我们固定右端点\(j\)时,若存在一个端点\(i\),使得上式成立,那我们就应该修改\(a[i]\),最简单的方法就是修改为\(0\),因为这样就不用担心后面的值累乘之后等于\(x\),这样次数同时也最少
而当我们枚举到\(j\)时,前面\(val(1,i-1)\)已经求出来了,所以这里就问题
就只需要验证\(val(1,i-1)^{-1}\)是否存在就行
如果在思考,你会发现如果\(x=0\),那么就不能修改为0,此时就要就数列中所有的数修改为不为0
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,p,x;
const int maxn=1e5+10;
ll a[maxn];
ll quick_pow(ll a,ll b){
ll base=a;
ll ans=1;
while(b){
if(b&1) ans=ans*base%p;
base=base*base%p;
b>>=1;
}
return ans%p;
}
void solve(){
cin>>n>>p>>x;
for(int i=1;i<=n;++i) cin>>a[i];
if(x==0){
for(int i=1;i<=n;++i)
if(a[i]==0) a[i]=1;
}
else {
ll t=1;//前缀积
ll inv=1;//逆元
set<ll> s;
for(int i=1;i<=n;++i){
if(a[i]==0){
t=inv=1;
s.clear();
}
else {
s.insert(inv);
t=t*a[i]%p;
inv=inv*quick_pow(a[i],p-2)%p;
ll check=inv*x%p;
if(s.count(check)){
a[i]=0;
inv=t=1;
s.clear();
}
}
}
}
for(int i=1;i<=n;++i) cout<<a[i]<<" ";
puts("");
return ;
}
int main(){
int t=1;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号