牛客每日一题
3
https://www.nowcoder.com/practice/032c72fab5fe4a2ea8e11d40378a493d?channelPut=tracker2
题意:
给出一个长度不超过1e5的数字字符串,可以执行以下操作若干次:选择字符串上一位数字若该数字的平方不超过10,可以将该数字替换成这个数字的平方。当这个字符串代表的十进制数字能被9整除的情况下,这个字符串可以被叫做好字符串。判断t个字符串是否可以通过上述的操作变成好字符串
思路:
发现一个十进制数字能被9整除的时候好像每一位数字的和能被9整除。(严格数字证明:对于每一个十进制的数字,都是a乘上10的某次方,然后把所有单独的数字相加。因为10在mod9的世界,是1,而10的多少次方都是1(mod9)所以这个十进制的数字在mod9之后,就变成了每一位数字相加,若这个十进制数能被9整除,那就等价与原本的数字每一位相加和能整除9)。然后我们观察到只有2和3的平方小于10,并且能改变原本数字位数之和,那么我们就计算这个字符串里面2和3的个数,然后枚举ij计算改变多少个2和3能导致改变量加上原本的位数之和能被9整除。但是s的长度有1e5,n^2的时间复杂度会T。因为在模的世界,所以考虑到周期性,因为改变九个2和没有改变是一样的,改变三个3和没有改变也是一样的。所以在枚举的时候限制ij最大只能到2和8。
代码
点击查看代码
int main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
int all = 0;
int cnt2 = 0, cnt3 = 0;
for (auto c : s) {
all += c - '0';
if (c == '2')cnt2++;
if (c == '3')cnt3++;
}
if(all%9==0){
cout<<"YES"<<endl;
continue;
}
int res = all % 9;
bool ok = false;
for (int i = 0; i <= min(cnt2,8); i++) {
for (int j = 0; j <= min(2,cnt3); j++) {
int now = i * 2 + j * 6;
now%=9;
if ((now+res)%9==0) {
ok = true;
break;
}
}
if (ok)
break;
}
if (ok) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
}
4
https://www.nowcoder.com/practice/bb9b70dcebda4f6e9b5c841793b95563?channelPut=tracker2
题意:
给出一个长度为n的数字字符串,设置其中密码的长度为m,当我们输入的字符串中的最后m位组成的字符串为密码的话就会清除现在输入的所有内容,然后解锁。当我们把这个长度为n的字符串输入完成之后一共解锁了k次。求出可能的密码的情况。(n<1e6)
思路:
我们不难想到当一个m长度字符串恰好出现了k次的时候那么这个字符串就有可能是密码,但是由于这个输入序列的特殊性:当输入的内容最后m个是密码的时候,整个输入的内容会清空。所以我们必须重新计算相同的字符串的出现次数:两个字符串不能有一点重叠。这个时候如果我们通过滑动窗口加上map的记录出现的m长度的字符串的话一定是会MLE的,我们可以考虑字符哈希,也就是将一个字符串用一个数字来作为这个字符串的身份识别,由于哈希冲突的原因,我们可以使用双哈希减小冲突的概率。具体做法如下:一直一个十进制的数字我们可以写作每位上的数字乘上对应位置的10的次方,我们把这个叫做base,我们考虑用一个很大的质数来作为base这样能减小这个哈希值的大小,那么每个数字的哈希值就是位上的数字乘上base对应的次方。我们考虑将所有的m长度的字符串排序,让都是一种字符串也就是两个哈希值完全相等的字符串排在一起,然后计算上一次字符串开始的位置加上m的长度是不是在下一个字符串中间,如果是的话说明两者重叠了,然后计算每种字符串出现的次数;对于那些次数恰好等于k的就是一种可能的密码
代码:
点击查看代码
struct Item{
ull id1,id2;
int pos;
bool operator<(const Item &o)const{
if(id1!=o.id1)return id1<o.id1;
if(id2!=o.id2)return id2<o.id2;
return pos<o.pos;
}
};
void solve(){
int n,m,k;
cin>>n>>m>>k;
string s;
cin>>s;
vector<Item>it;
const ull base1=131,base2=137;
ull pow1=1,pow2=1;
for(int i=1;i<m;i++){
pow1*=base1;
pow2*=base2;
}
ull hash1=0,hash2=0;
for(int i=0;i<n;i++){
int dight=s[i]-'0'+1;
hash1=hash1*base1+dight;
hash2=hash2*base2+dight;
if(i>=m-1){
it.push_back({hash1,hash2,i-m+1});
int ld=s[i-m+1]-'0'+1;
hash1-=ld*pow1;
hash2-=ld*pow2;
}
}
sort(it.begin(),it.end());
int p=0;
int total=it.size();
int ans=0;
while(p<total){
int j=p+1;
while(j<total&&it[p].id1==it[j].id1&&it[p].id2==it[j].id2){
j++;
}
int last=-m;
int cnt=0;
for(int i=p;i<j;i++){
if(it[i].pos>=last+m){
cnt++;
last=it[i].pos;
}
}
if(cnt==k)ans++;
p=j;
}
cout<<ans<<endl;
}
5
https://www.nowcoder.com/practice/67fe6737ef7447e4ab95bfdbb7f6de6f?channelPut=tracker2
题意:
我们定义一个序列的权值为这个序列里面所有数的gcd乘上这个序列的和,给出n个数字,求出其中权值最大的子序列
思路1:
我们可以将一个因子看做一族,将这个所有有这个因子的数加入这一族,最后遍历所有族求出最大的权值,也就是模拟了选择了不同的gcd。时间复杂度为O(nsqrt(n))
代码1:
点击查看代码
void solve(){
int n;
cin>>n;
vector<int>a(n);
map<ll,ll>cnt;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++){
for(int j=1;j*j<=a[i];j++){
if(a[i]%j==0){
cnt[j]+=a[i];
if(j*j!=a[i])
cnt[a[i]/j]+=a[i];
}
}
}
ll ans=-1;
for(auto [k,v]:cnt){
ans=max(ans,k*v);
}
cout<<ans<<endl;
}
思路2:
与思路1相对,与其找到每个数字的因子,我们不如计算每个因子的倍数出现了多少次,思想和前一个还是分成不同的族,计算每个族的大小。
代码2:
点击查看代码
const int N=1e6+10;
int cnt[N],mx;
ll ans=-1;
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int a;
cin>>a;
mx=max(mx,a);
cnt[a]++;
}
for(int i=1;i<=mx;i++){
if(cnt[i]==0)continue;
ll sum=0;
for(int j=i;j<=mx;j+=i)sum+=j*cnt[j];
sum*=i;
ans=max(ans,sum);
}
cout<<ans<<endl;
}const int N=1e6+10;
int cnt[N],mx;
ll ans=-1;
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int a;
cin>>a;
mx=max(mx,a);
cnt[a]++;
}
for(int i=1;i<=mx;i++){
if(cnt[i]==0)continue;
ll sum=0;
for(int j=i;j<=mx;j+=i)sum+=j*cnt[j];
sum*=i;
ans=max(ans,sum);
}
cout<<ans<<endl;
}
16
https://www.nowcoder.com/practice/9da360a56fa846708542b0fb3985c647?channelPut=tracker2
题意:
给出一个正整数n判断是不是素数(n的数据范围是1e18)
思路:
由于n的数据范围来到了1e18,所以根号n的时间复杂度是算法已经无法通过了。这里我们使用米勒-罗宾算法
米勒-罗宾算法:
前提1:费马小定理,如果p是一个质数并且a和p互质,那么a^(p-1)恒等于1(modp)
前提2:二次探测定理,如果p是一个质数且x^2恒等于1,那么x在modp的世界等于1或者-1(也就是p-1)。
知道上面两个,那么我们考虑使用二次探测定理的逆命题来对这个n判断是不是合数,首先我们已经判断n不是偶数,那么n-1就是偶数,那么由于费马小定理那么a(p-1)在modp的世界一定等于1。我们考虑对这个p-1进行处理,我们将考虑融合二次探测,我们将n-1变成d*2的s次方的形式,那么当s从0开始增加,使得这次的a(d*2^(s))的结果是上次的两倍,在这个变大的过程中,只要我们发现其中有一项的结果(modp)是1,那么我们判断上一项的结果是不是1或者-1.如果上一次不是1或者-1,那么就说明违反了二次探测定理。就说明这个n一定不是素数。最后我们需要注意的是一个合数可能在某个单次基底(a)的检测下是素数,但是实际上不是素数。所以我们必须选多个基底,在64位整数的情况下,已经找到了能%100确保的基底组合2,3,5,7,11,13,17,19,23,29,31,37。
代码:
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
ll power(ll base,ll exp,ll mod){
ll res=1;
base%=mod;
while(exp){
if(exp&1)res=(__int128)res*base%mod;
base=(__int128)base*base%mod;
exp>>=1;
}
return res;
}
bool isprime(ll x) {
if (x < 2) return false;
if (x == 2 || x == 3) return true;
if (x % 2 == 0) return false;
long long bases[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
ll d = x - 1;
while (!(d & 1)) d >>= 1;
for (ll a : bases) {
if (x == a) return true;
ll prev = power(a, d, x);
if (prev == 1 || prev == x - 1) continue;
ll t = d;
bool maybe_prime = false;
while (t != x - 1) {
ll cur = (__int128)prev * prev % x;
t *= 2;
if (cur == 1) {
if (prev != 1 && prev != x - 1)
return false;
maybe_prime = true;
break;
}
if (cur == x - 1) {
maybe_prime = true;
break;
}
prev = cur;
}
if (!maybe_prime) return false;
}
return true;
}
void solve(){
ll x;
cin>>x;
if(isprime(x)){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
signed main(){
int t;
cin>>t;
// t=1;
while(t--)solve();
}
浙公网安备 33010602011771号