wtpavssite

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

牛客每日一题

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();
}
posted on 2026-06-04 09:49  wt_pav  阅读(8)  评论(0)    收藏  举报