2022GDUT寒训专题四

C题 Beautiful Numbers

题面

image

样例

image

题意

意思是问你给定数字a, b和所想要生成数字的位数n,问你在这样的条件下有多少个数满足每一位相加之后也只由数字a, b构成

思路

因为我们知道n的长度是1e6,也就是估摸着求和不会超过1e7,所以求和出来的数的位数应该最多只有7位数,直接一个暴力枚举+组合数就好了
也就是枚举在长度n限制下有多少个a,有多少个b就好了
然后这个地方要预处理一下组合数,也要运用到逆元

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> PII;
const int inf = 0x3f3f3f;
const ll p = 1e9+7;
const int maxn = 1e6+10;
ll a, b, n;
ll f[maxn];

void pre(){
    f[0] = 1;
    f[1] = 1;
    for(int i = 2;i <= 1000000;++i){
        f[i] = f[i-1]*i%p;
    }
}

ll fp(ll base, ll power){
    ll ret = 1;
    while(power > 0){
        if(power&1){
            ret = ret*base%p;
        }
        base = base*base%p;
        power >>= 1;
    }
    return ret;
}

bool check(ll a, ll b, ll x){
    while(x){
        ll temp = x % 10;
        if((temp != a) && (temp != b)) return false;
        x /= 10;
    }
    return true;
}

int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    //
    pre();
    cin >> a >> b >> n;
    ll ans = 0;
    for(int i = 0;i <= n;++i){
        ll t = a*i + b*(n-i);
        if(check(a, b, t)){
            ll down = f[n-i]*f[i]%p;
            ll tot = f[n]*fp(down, p-2)%p;
            ans = ans%p + tot%p;
            ans %= p;
        }
    }
    cout << ans %p << endl;
    return 0;
}

// printf("**********test**********\n");
// printf("%d\n");
// printf("**********test**********\n");

E题 Prime Distance

题面

image

样例

image

题意

给定l, r区间,问你距离最近的素数对以及距离最远的素数对
如果有多对答案相同的话,输出从左往右找到的第一位

思路

这题给的l,r范围很大,区间长度却很小,考虑区间筛质数
显然直接使用埃筛是存不下这么大的数的
所以我们考虑使用两次埃筛,第一次预处理筛出这个大范围内可能可以成为因子的素数,也就是sqrt(_max),其中_max为题目所给范围。
第二次筛我们就根据所给的l r区间来筛,给lr区间加一个偏移量就可以存下了
然后筛掉区间内的合数,剩下的数就是区间内的素数咯

代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> PII;
const int inf = 0x3f3f3f;
//这个是区间筛素数的典例
//主要还是用埃筛的思想吧
//还有主要是看到这个l, r之差只有1e6,所以对数组下标加一个偏移量就可以存下咯
//不过写的时候确实还有很多细节要注意啦...
ll l, r;
ll first_l, first_r, second_l, second_r;
bool x[50010];
bool y[6000010];
vector<int>prime;
vector<ll>ans;
//先得到5e4以内的素数
void Smallprime(){
	memset(x, 1, sizeof(x));
	x[1] = 0;
	x[2] = 1;
	for(ll i = 2;i <= 50000;++i){
		if(x[i]){
			prime.push_back(i);
			for(ll j = 2;i*j <= 50000;++j){
				x[i*j] = 0;
			}
		}
	}
}

//然后用5e4的素数来筛更大区间的合数
//因为区间长度不超过1e6,所以检验数组加一个偏移量就好啦,就是让l作为0就好咯
void Bigprime(){
	memset(y, 1, sizeof(y));
	for(ll i = 0;i < prime.size();++i){
        //要先处理一下倍数...不然单纯暴力会t的
        //距离l最近是prime的几倍
        ll a = (l-1)/prime[i]+1;
        ll b = r/prime[i];
		for(ll j = a;j <= b;++j){
			if(j > 1) y[j*prime[i]-l] = 0;
		}
	}
}

//检验当前点是否为素数,是的话压入答案容器里
void _push(){
	for(ll i = 0;i <= r-l;++i){
		if(y[i-l]) ans.push_back(i);
	}
}
//遍历ans容器,找到答案
bool check(){
    //起码要有两个素数才有答案噢
	if(ans.size() <= 1) return false;
    //for(auto x:ans){cout << x << endl;}
	ll _min = inf, _max = -1;
	ll len = 0;
	for(ll i = 1;i < ans.size();++i){
		len = ans[i]-ans[i-1];
		if(len > _max){
			_max = len;
			second_l = ans[i-1];
			second_r = ans[i];
		}
		if(len < _min){
			_min = len;
			first_l = ans[i-1];
			first_r = ans[i];
		}
	}
	return true;
}
int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    //
    Smallprime();
	while(~scanf("%lld%lld", &l, &r)){
        //这个地方要特判一下1,不然会wa
        if(l == 1) l = 2;
		ans.clear();
		Bigprime();
		_push();
		if(!check()) printf("There are no adjacent primes.\n");
		else {
			printf("%lld,%lld are closest, %lld,%lld are most distant.\n", first_l, first_r, second_l, second_r);
		}
	}
    return 0;
}

// printf("**********test**********\n");
// printf("%d\n");
// printf("**********test**********\n");

G题 青蛙的约会

题面

image

样例

image

思路

先记二者为A和B。设跳的次数为t,走的总路程为p,因为是转圈,所以得到A走的路程和B走的路程对L同余即可,对方程进行变形,发现变量只有t和p,也就是求解这个不定方程。利用拓展欧几里得算法就可以求得答案
因为拓展欧几里得算法所求得的整数解不一定是最小正整数解,所以利用其通解形式可以得到最小正整数解

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> PII;
const int inf = 0x3f3f3f;
ll x, y, m, n, l, t, p;
ll gcd(ll a, ll b) {if(b == 0) return a;else return gcd(b, a%b);}

ll exgcd(ll a, ll b, ll &x, ll&y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    ll g = exgcd(b, a%b, x, y);
    ll temp = x;
    x = y;
    y = temp - y*(a/b);
    return g;
}
ll want(ll x0, ll y0){
    return (x0%y0+y0)%y0;
}
int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    //
    cin >> x >> y >> m >> n >> l;
    int a1 = n-m, b1 = l, c1 = x-y;
    if(a1 < 0) {a1 = -a1;c1 = -c1;}
    ll k = gcd(a1, b1);
    if(c1 % k) puts("Impossible");
    else{
        exgcd(a1, b1, t, p);
        //x的通解x_0 + k*u, u = b/gcd(a, b),所以求最小解就mod这个就好咯
        //y的通解y_0 - k*u, u = a/gcd(a, b)
        ll mod = b1/k;
        //因为刚刚求的是右边是gcd(a,b)即这里说的k的情况,所以要还原为原本的解噢
        cout << want(t*c1/k, mod) << "\n";
    }
    return 0;
}

// printf("**********test**********\n");
// printf("%d\n");
// printf("**********test**********\n");

后话

对于拓展欧几里得算法,只需要记住一点,我们默认传入exgcd所解方程的右侧为gcd(a, b),那么得到的解不一定是原题所要求的解,我们需要乘上一定的系数才是原题所求的答案。

J题 Cheerleaders

题面

image

样例

image

题意

给定n x m的棋盘和k个人,现在要求每条边上一定都得有人站着,问你满足条件的情况数

思路

思路很好想到,就是利用容斥原理
但是这个地方很有意思的点是我第一次遇到状态压缩,感觉是一个很有用的小技巧

代码

#include <bits/stdc++.h>
#define SF ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define ms(x, y) memset(x, y, sizeof(x))
#define INF 0x3f3f3f
#define X first
#define Y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef long double ld;
const int p = 1e6+7;
const int maxn = 410;
//这题是容斥原理+状态压缩,还是挺有意思的qvq
ll f[maxn][maxn];

//因为数据范围很小,所以预处理组合数的话直接上状态转移就好咯
void cal(){
	for(int i = 0;i <= 400;++i){
        f[i][0] = f[i][i] = 1;
    }
    for(int i = 0;i <= 400;++i){
        for(int j = 0;j < i;++j){
            f[i][j] = (f[i-1][j] + f[i-1][j-1]) % p;
        }
    }
}
int main(){
    SF
	cal();
	int t;cin >> t;
	int cnt = 0;
	while(t--){
		int m, n, k;cin >> m >> n >> k;
        ll ans = 0;
        //这个地方很有趣欸,是状态压缩!
        //对于四条边,都只有两种状态:有或者没有人
        //所以我们用四位的01串来分别表示上下左右有没有人

        for(int i = 0; i < 16;++i){
            int cnt = 0, r = n, c = m;
            if(i&1) {cnt++;c--;}
            if(i&2) {cnt++;c--;}
            if(i&4) {cnt++;r--;}
            if(i&8) {cnt++;r--;}
            //对于容斥原理来说,过程是奇减偶加,所以统计有奇数条边就是减,反之为加
            if(cnt&1) ans = (ans%p + p - f[r*c][k]%p) % p;
            else ans = (ans%p + f[r*c][k]%p) % p;
        }
		printf("Case %d: %lld\n", ++cnt, ans);
	}
    
    return 0;
}

// printf("**********test**********\n");
// printf("%d\n");
// printf("**********test**********\n");

后话

容斥原理是奇减偶加的

总结

数论专题的话,反而是接触的稍微多一点,感觉主要是组合数学+素数+拓展欧几里得用的比较多一些
还有快速幂求逆元也是非常常用的地方,毕竟一般给的模数都是质数,不过如果模数不是质数的话利用exgcd也可以
还有欧拉函数也是稍微会用到一点,也就是会用到性质吧(L题)。不过这里没给出来,因为我利用欧拉函数的解法写起来会稍微复杂一点,不如直接用gcd直接求哈哈哈。

posted @ 2022-02-15 18:08  _77  阅读(22)  评论(0编辑  收藏  举报