kuangbin专题十四数论基础总结

本文解析了一系列数论竞赛题目,包括最大独立集、求约数个数、大整数取模等问题,分享了解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做完感觉数论好像还没入门。。感觉并不基础。。
A - Bi-shoe and Phi-shoe
A题当时wa了好多次,做的时候逻辑出现了错误,只是将求出来的欧拉值和数字(pair)排了个序,然后找出第一个欧拉值大于等于它的那个数,但这样并不正确,因为有可能后面有欧拉值比他更大的但值更小的。
之后我的做法是维护一个1到当前数欧拉值的最大值,这样使得从1到n的first也是一个递增序列,从而可以求出第一个欧拉值大于等于它的那个数。
并不是所有的题都能这样做的,这个做法的正确性在于一个欧拉函数的性质:欧拉函数大于等于x的那个数就是x之后的第一个质数,那么这个性质就能保证,如果一个数不是质数的话,它的欧拉值在之前肯定出现过,所以我们要找的ans肯定不会是它,那么就能进行从前往后进行递推欧拉值从而覆盖不是ans的数的值。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
#define N 1500000
pair<int, int>pii[N+5];

void init()
{
    for (int i = 0;i <= N;i++)pii[i].first = 0;
    //memset(phi, 0, sizeof(phi));
    pii[1].first = 1;
    //phi[1] = 1;
    for (int i = 2;i <= N;i++)if (!pii[i].first)
    {
        for (int j = i;j <= N;j += i)
        {
            if (!pii[j].first)pii[j].first = j;
            pii[j].first = pii[j].first / i*(i - 1);
        }
    }
    for (int i = 1;i <= N;i++)pii[i].second = i,pii[i].first=max(pii[i].first,pii[i-1].first);
    sort(pii + 1, pii + N + 1);
}

int solve(int num)
{
    int l = 2, r = N;
    int mid, ans = 0;
    while (l <= r)
    {
        mid = l + r >> 1;
        if (pii[mid].first >= num)
        {
            ans = mid;r = mid - 1;
        }
        else
            l = mid + 1;
    }
    return ans;
}

int main()
{
    int T;
    scanf("%d", &T);
    init();
    //cout << pii[1500000].first << endl;
    //for (int i = 2;i <= 10000;i++)cout << pii[i].first << endl;
    int cas = 1;
    while (T--)
    {
        int n;
        scanf("%d", &n);
        int tmp;
        long long ans = 0;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d", &tmp);
            int idx = solve(tmp);
            ans = ans + pii[idx].second;
        }
        printf("Case %d: %lld Xukha\n", cas++, ans);
        //printf("%d\n", ans);
    }
}

B - Prime Independence
这题一看就是最大独立集,建图方式也很简单,但是N为4e4,如果暴力建图的话会T。那么应该怎么做呢?
我们来一步一步分析一下,如果一个数和另一个数是素数乘积关系,那么他们的唯一分解式因子的幂的和肯定是一奇一偶,所以我们可以根据这个来区分左右边,其次数字不大,我们可以用一个数组来记录这个数是否出现过,这种情况下我们只需把一个数的因子算出来然后枚举就行了。
另外学好用Hopcroft-Carp算法去代替匈牙利算法,毕竟快一些。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stdio.h>
#include<math.h>
using namespace std;

const int maxn = 500005;
const int N = 40005;
const int inf = 0x3f3f3f3f;
int p[2][maxn];
bool is[maxn];
int prm[maxn], id;
void init()
{
    memset(is, 1, sizeof(is));
    is[0] = is[1] = 0;
    int k = 0;
    prm[k++] = 2;
    for (int i = 4;i < maxn;i+=2)is[i] = 0;
    int e = (int)sqrt(0.0 + maxn) + 1;
    int i;
    for (i = 3;i < e;i += 2)if(is[i])
    {
        prm[k++] = i;
        for (int s = 2 * i, j = i*i;j < maxn;j += s)
            is[j] = 0;
    }
    for (;i < maxn;i += 2)if (is[i])prm[k++] = i;
    id = k;
}
int a[N];
int nL, nR;
int Left[N], Right[N];
vector<int>V[N];
void build(int idx)
{

    int num = a[idx];
    int tmp = num;
    int e =(int)sqrt(num + 0.0) + 1;
    int cnt=0;
    for (int i = 0;i < id&&prm[i] < e;i++)
    {
        if (num == 1)break;
        if (num%prm[i] == 0)
        {
            V[idx].push_back(prm[i]);
            while (num%prm[i] == 0)
            {
                cnt++;
                num = num / prm[i];
            }
        }
    }
    if (num != 1)
    {
        cnt++;
        V[idx].push_back(num);
    }
    int go = cnt % 2;
    p[go][tmp] = idx;
    if (!go)
        Left[++nL] = idx;
    else
        Right[++nR] = idx;
}
struct Edge
{
    int u, v, nxt;
    Edge(int _u, int _v, int _nxt):u(_u),v(_v),nxt(_nxt){}
    Edge(){}
}edges[N * 10];
int head[N], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}
int n;
void buildmap()
{
    for (int i = 1;i <= n;i++)
    {
        int num = a[i];
        int Size = V[i].size();
        int go = p[0][num] ? 0 : 1;
        for (int j = 0;j < Size;j++)
        {
            int tmp = num / V[i][j];
            if (p[go ^ 1][tmp])
            {
                addedge(i, p[go ^ 1][tmp]);
                addedge(p[go ^ 1][tmp], i);
            }
        }
    }
}
int cx[N], cy[N];
int dx[N], dy[N];
bool used[N];
int dis;
bool bfs()
{
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    dis = inf;
    queue<int>Q;
    for (int i = 1;i <= nL;i++)
    {
        int u = Left[i];
        if (cx[u] == -1)
        {
            Q.push(u);
            dx[u] = 0;
        }
    }
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = head[u];~i;i = edges[i].nxt)
        {
            int v = edges[i].v;
            if (dy[v] == -1)
            {
                dy[v] = dx[u] + 1;
                if (cy[v] == -1)dis = dy[v];
                else
                {
                    dx[cy[v]] = dy[v] + 1;
                    Q.push(cy[v]);
                }
            }
        }
    }
    return dis != inf;
}
int find(int u)
{
    for (int i = head[u];~i;i = edges[i].nxt)
    {
        int v = edges[i].v;
        if (!used[v] && dy[v] == dx[u] + 1)
        {
            used[v] = 1;
            if (cy[v] != -1 && dy[v] == dis)continue;
            else if (cy[v] == -1 || find(cy[v]))
            {
                cx[u] = v;
                cy[v] = u;
                return 1;
            }
        }
    }
    return 0;
}
int MaxMatch()
{
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    int res = 0;
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <= nL;i++)
        {
            int u = Left[i];
            if (cx[u] == -1)
                res += find(u);
        }
    }
    return res;
}
int main()
{
    init();
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        memset(p, 0, sizeof(p));
        memset(head, -1, sizeof(head));
        tot = 0;
        nL = nR = 0;
        scanf("%d", &n);
        for (int i = 1;i <= n;i++)
            scanf("%d", &a[i]), V[i].clear();
        for (int i = 1;i <= n;i++)
            build(i);
        buildmap();
        int ans = n - MaxMatch();
        printf("Case %d: %d\n",cas++, ans);
    }
    return 0;
}

C - Aladdin and the Flying Carpet
好题一枚。
如果没有b这个要求,我们很容易可以算出a的约数的个数,所以现在的问题如何解决b这个的问题。
我们来分析一下b,如果b大于sqrt(a),那么无解。
所以基于这个条件b<=1e6,那么我们可以计算出约数个数后,再来枚举b,一减就完事了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<stdio.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e6+5;
bool is[maxn];
int prm[maxn], id;
void init()
{
    memset(is, 1, sizeof(is));
    is[0] = is[1] = 0;
    prm[++id] = 2;
    for (ll i = 4;i < maxn;i += 2)is[i] = 0;
    ll e = (ll)sqrt(0.0 + maxn) + 1;
    ll i;
    for (i = 3;i <= e;i += 2)if (is[i])
    {
        prm[++id] = i;
        for (ll s = 2 * i, j = i*i;j < maxn;j += s)
            is[j] = 0;
    }
    for (;i < maxn;i += 2)if (is[i])prm[++id] = i;
}


int main()
{
    init();
    int T;
    int cas = 1;
    scanf("%d", &T);
    while (T--)
    {
        ll a, b;
        scanf("%lld %lld", &a, &b);

        if ((int)sqrt(0.0 + a) < b)
        {
            printf("Case %d: 0\n", cas++);
            continue;
        }
        ll _a = a;
        ll ans = 1;
        for (int i = 1;i < id&&prm[i] <= a / prm[i];i++)
        {
            if (a%prm[i] == 0)
            {
                int cnt = 0;
                while (a%prm[i] == 0)
                {
                    a /= prm[i];
                    cnt++;
                }
                ans *= (cnt + 1);
            }
        }
        if (a != 1)
            ans *= (1 + 1);
        ans /= 2;
        for (int i = 1;i < b;i++)if (_a%i == 0)ans--;
        printf("Case %d: %lld\n", cas++, ans);
    }
    return 0;
}

D - Sigma Function
又是一道好题。
我们来认真分析一下。
一个数的因子和为

sum=(1+p11+p21+...+pk1)(1+p12+p22+...+pk2).... s u m = ( 1 + p 1 1 + p 1 2 + . . . + p 1 k ) ∗ ( 1 + p 2 1 + p 2 2 + . . . + p 2 k ) ∗ . . . .

奇数*奇数=奇数
偶数*偶数=偶数
偶数*奇数=偶数
所以我们要求偶数的个数,不妨求奇数的个数然后再一减。
如果质因子有2,那么可以不用管,它的和肯定为奇数,那么后面的质因子由于都是奇数,那么我们必须得要奇数个奇数才能使和为奇数,所以k必须是个偶数。
观察除去2的其他质因子我们会发现,这个数其实是一个平方数。
所以答案就为n-sqrt(n)-sqrt(n/2).(读者可以自己思考一下)。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        long long n;
        scanf("%lld", &n);
        printf("Case %d: %lld\n",cas++, n - (int)sqrt(n + 0.0) - (int)sqrt((n + 0.0) / 2));
    }
}

E - Leading and Trailing
后三位好求,快速幂模1000即可。
前三位就需要一点数学知识了。
所有的数可以表示成 n=10a n = 10 a
这个a一般是小数,那么 nk=10ak n k = 10 a k
这里把ak分成两部分,整数和小数部分,即x和y,那么 nk=10x10y n k = 10 x ∗ 10 y ,这里规定x为整数,y为小数,那么x就是指定位数的,y则是指定值的。
因为 n=10a n = 10 a ,所以 a=logn a = l o g n ,所以我们只需求出a*k的小数位,然后乘以100即可。
fmod(x,y),可求出x模y后的值(double类型)。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
typedef long long ll;
int quick_mul(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b % 2)ans = ans*a % 1000;
        a = a*a % 1000;
        b /= 2;
    }
    return ans%1000;
}

int main()
{
    int T, k, n, kase = 0;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        scanf("%d %d", &n, &k);
        int x = (int)pow(10.0, 2.0 + fmod(1.0*k*log10(1.0*n), 1));
        int y = quick_mul(n, k);
        printf("Case %d: %d %03d\n", cas++, x, y);
    }
    return 0;
}

G - Harmonic Number (II)
这题有几个结论比较重要。
第一个结论: 一个数k出现的区间为(n/(k+1),n/k](k<=sqrt(n))
第二个结论:大于sqrt(n)的数并不是所有都出现了,他们的出现都与小于sqrt(n)的一一对应。
综上,我们可以用sqrt(n)的时间来计算出所有值的和。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
typedef long long ll;

int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        ll n;
        scanf("%lld", &n);
        int e = (int)sqrt(0.0 + n);
        ll ans = 0;
        for (int i = 1;i <= e;i++)
        {
            ans += 1LL * (n / i - n / (i + 1))*i + n / i;
            if (i == n / i)ans -= i;
        }
        printf("Case %d: %lld\n", cas++, ans);
    }

}

H - Pairs Forming LCM
把两个数变成唯一分解式,那么他们的lcm就为各项因子的幂的最大值的乘积。
所以基于这一点,我们就可以计算出答案了。
假如当前因子的幂的最大值为cnt,那么另一个数 的因子的幂的个数就的区间为[0,cnt],理论上有cnt+1个选择,然后再乘以2表示不同边,但是如果它也选cnt的话,实际上会算重1次,再乘以后面的数就会导致重复计算很多次,所以我们将其减1,那么它就只计算了1次。之后再把它的1加进去,这样所有的答案就算了两次。再除以2即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
bool is[maxn];
int prm[700000], id;
void init()
{
    memset(is, 1, sizeof(is));
    is[0] = is[1] = 0;
    prm[++id] = 2;
    for (ll i = 4;i < maxn;i += 2)is[i] = 0;
    int e = (int)sqrt(0.0 + maxn) + 1;
    ll i;
    for (i = 3;i <= e;i += 2)if (is[i])
    {
        prm[++id] = i;
        for (ll s = 2 * i, j = i*i;j < maxn;j += s)
            is[j] = 0;
    }
    for (;i < maxn;i += 2)if (is[i])prm[++id] = i;
    //cout << id << endl;
}

int main()
{
    init();
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        ll n;
        scanf("%lld", &n);
        ll ans = 1;
        for (int i = 1;i <= id&&prm[i] <= n / prm[i];i++)
        {
            //if (n == 1)break;
            if (n%prm[i] == 0)
            {
                int cnt = 0;
                while (n%prm[i] == 0)
                {
                    n /= prm[i];
                    cnt++;
                }
                ans *= (2*cnt + 1);
            }
        }
        if (n != 1) { ans *=  (2 + 1); }
        ans = (ans + 1) / 2;
        printf("Case %d: %lld\n", cas++, ans);
    }

    return 0;
}

I - Harmonic Number
这题一看就会想到打表,但是打表会mle,所以这里用一个小技巧,我们可以100个一存,那么就可以减少一些内存的使用了。之后再计算的时候暴力即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1000005;
double H[maxn];
const double eps = 1e-7;
void init()
{
    double tmp = 0;
    for (int i = 1;i <= 100000000;i++)
    {
        tmp += 1.0 / i;
        if (i % 100 == 0)H[i / 100] = tmp;
    }
}
int main()
{
    init();
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        int n;
        scanf("%d", &n);
        int tmp = n / 100;
        double ans = H[tmp];
        for (int i = tmp * 100 + 1;i <= n;i++)
        {
            ans += 1.0 / i;
        }
        printf("Case %d: %.10f\n", cas++, ans);
    }
    return 0;
}

J - Mysterious Bacteria
我们将数字进行唯一分解,那么所有因子的幂的最大公约数就是答案。
此外,当x为负数时,答案不能为偶数,所以我们就得将答案一直除以2直到它不是偶数为止。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
typedef long long ll;
const int maxn = 70000;
bool is[maxn];
int prm[maxn], id;
void init()
{
    memset(is, 1, sizeof(is));
    is[0] = is[1] = 0;
    prm[++id] = 2;
    for (int i = 4;i < maxn;i += 2)is[i] = 0;
    int i;
    int e = (int)sqrt(0.0 + maxn) + 1;
    for (i = 3;i <= e;i += 2)if (is[i])
    {
        prm[++id] = i;
        for (ll s = 2 * i, j = i*i;j < maxn;j += s)
            is[j] = 0;
    }
    for (;i < maxn;i += 2)if (is[i])prm[++id] = i;
}
int a[maxn],cnt;

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a%b);
}

int solve()
{
    if (cnt == 1)return a[1];
    int now = a[1];
    for (int i = 2;i <= cnt;i++)
    {
        now = gcd(now, a[i]);
    }
    return now;
}

int main()
{
    init();
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        memset(a, 0, sizeof(a));
        cnt = 0;
        ll n;
        scanf("%lld", &n);
        int flag = true;
        if (n < 0) { n = -n;flag = false; }
        for (int i = 1;i <= id&&prm[i] <= n / prm[i];i++)
        {
            if (n == 1)break;
            if (n%prm[i] == 0)
            {
                ++cnt;
                while (n%prm[i] == 0)
                {
                    n /= prm[i];
                    a[cnt]++;
                }
            }
        }
        if (n != 1)
        {
            ++cnt;
            a[cnt] = 1;
        }
        int ans = solve();
        if (!flag)
        {
            while (ans%2==0)
            {
                ans /= 2;
            }
        }
        printf("Case %d: %d\n", cas++,ans);
    }
    return 0;
}

K - Large Division
大整数取模。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<string>
using namespace std;
typedef long long ll;


int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        string a;
        ll b;
        cin >> a >> b;
        b = abs(b);
        int Size = a.size();
        ll ans = 0;
        int i=0;
        if (a[0] == '-')i = 1;
        for (;i < Size;i++)
        {
            ans = (ans * 10 + a[i] - '0') % b;
        }
        printf("Case %d: ", cas++);
        if (ans)puts("not divisible");
        else puts("divisible");
    }
    return 0;
}

M - Help Hanzo
计算小范围内质数个数模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int small[maxn], prime[maxn];
ll a, b;
void segment_sieve()
{
    for (int i = 0;(ll)i*i <= b;i++)small[i] = 0;
    for (int i = 0;i <= b - a;i++)prime[i] = 0;

    for (int i = 2;(ll)i*i <= b;i++)
    {
        if (!small[i])
        {
            for (int j = i*i;(ll)j*j <= b;j += i)small[j] = 1;
            for (ll j = (a + i - 1) / i*i;j <= b;j += i)
            {
                if (j == i)continue;
                prime[j - a] = 1;
            }
        }
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        cin >> a >> b;
        int ans = 0;
        segment_sieve();
        for (int i = 0;i <= b - a;i++)if (!prime[i])ans++;
        if (a == 1)ans--;
        printf("Case %d: %d\n", cas++, ans);
    }
}

N - Trailing Zeroes (III)
0必定是由1个2和一个5组成的,而从1到N,2的个数肯定多于5,所以我们只需计算1到N有多少个5就可以知道有多少个0了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#pragma warning (disable:4996)
using namespace std;
int Q;
int check(int num)
{
    int ans = 0;
    while (num)
    {
        ans += num / 5;
        num /= 5;
    }
    if (ans == Q)return 1;
    else if (ans > Q)return 2;
    return 0;
}

int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {

        scanf("%d", &Q);
        int l = 5, r = 5e8;
        int mid, ans = 0;
        while (l<=r)
        {
            mid = l + r >> 1;
            if (check(mid))
            {
                ans = mid;
                r = mid - 1;
            }
            else
                l = mid + 1;
        }
        printf("Case %d: ", cas++);
        if (check(ans) == 1)
            printf("%d\n", ans);
        else puts("impossible");
    }
}

O - GCD - Extreme (II)
这题好难啊。写题解的时候都不知道怎么做的了。。

下面的题解摘自hyogahyoga
1.建立递推关系,s(n)=s(n-1)+gcd(1,n)+gcd(2,n)+……+gcd(n-1,n);

2.设f(n)=gcd(1,n)+gcd(2,n)+……+gcd(n-1,n)。

gcd(x,n)=i是n的约数(x< n),按照这个约数进行分类。设满足gcd(x,n)=i的约束有g(n,i)个,则有f(n)=sum(i*g(n,i))。

而gcd(x,n)=i等价于gcd(x/i,n/i)=1,因此g(n,i)等价于phi(n/i).phi(x)为欧拉函数。

3.降低时间复杂度。用筛法预处理phi[x]表

用筛法预处理f(x)- > 枚举因数,更新其所有倍数求解。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
const int maxn = 4000005;
int phi[maxn];

void phi_table(int n)
{
    for (int i = 2;i <= n;i++)phi[i] = 0;
    phi[1] = 1;
    for(int i=2;i<=n;i++)if(!phi[i])
        for (int j = i;j <= n;j += i)
        {
            if (!phi[j])phi[j] = j;
            phi[j] = phi[j] / i*(i - 1);
        }
}
typedef long long ll;
ll s[maxn];
ll f[maxn];
void init()
{
    for (int i = 1;i < maxn / 2;i++)
        for (int j = i + i;j < maxn;j+=i)
            f[j] += phi[j / i] * i;
}


int main()
{
    phi_table(maxn - 5);
    init();
    int N;
    while (~scanf("%d",&N)&&N)
    {
        //scanf("%d", &N);
        memset(s, 0, sizeof(s));
        s[1] = 0;
        for (int i = 2;i <= N;i++)s[i] = s[i - 1] + f[i];
        printf("%lld\n", s[N]);
    }


}

P - Code Feat
当总个数很小的话,我们可以用中国剩余定理暴力求出所有的解,但个数特别大的话,这里有一个玄妙的地方,就是找出k/m最小的那个,然后通过从小到大枚举符合它的一个模的值,在判断其他的是否符合看这个值可不可行。感觉有点玄妙。。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<set>
#include<vector>
using namespace std;
#pragma warning (disable:4996)
typedef long long ll;
const int maxC = 15;
const int limit = 1e4;
ll  m[maxC];
int k[maxC];
ll Y[maxC][105];
int bestc;
set<ll>vis[maxC];
int C, S;
void solve()
{
    for (int i = 1;i <= C;i++)if (i != bestc)
        for (int j = 1;j <= k[i];j++)
        {
            vis[i].insert(Y[i][j]);
        }

    for (int ti = 0;S;ti++)
    {
        for (int i = 1;i <= k[bestc];i++)
        {
            ll num = 1LL * ti*m[bestc] + Y[bestc][i];
            if (num == 0)continue;
            bool flag = true;
            for (int j = 1;j <= C;j++)if (j != bestc)
            {
                ll tmp = num%m[j];
                if (!vis[j].count(tmp))
                {
                    flag = false;
                    break;
                }
            }
            if (flag)
            {
                printf("%lld\n", num);
                if (--S == 0)break;
            }
        }
    }
}
vector<ll>ans;
ll _a[15];
ll M;
void initchina()
{
    M = 1;
    for (int i = 1;i <= C;i++)
        M *= m[i];
}

ll exgcd(ll a, ll b, ll &x, ll &y)
{
    if (b == 0) { x = 1;y = 0;return a; }
    ll d = exgcd(b, a%b, x, y);
    ll t = x;x = y;y = t - a / b*y;
    return d;
}

void gcd(ll a, ll b, ll& d, ll& x, ll& y) {
    if (!b) {
        d = a;
        x = 1;
        y = 0;
    }
    else {
        gcd(b, a%b, d, y, x);
        y -= x*(a / b);
    }
}

void solvechina()
{
    ll x, d;
    ll res = 0;
    for (int i = 1;i <= C;i++)
    {
        ll Mi = M / m[i];
        gcd(m[i], Mi, d, d, x);
        //x = (x%m[i] + m[i]) % m[i];
        ll tmp = Mi*x%M;
        tmp = tmp*_a[i] % M;
        res = (res + tmp) % M;
    }
    if (res<0)
        res = (res + M) % M;
    ans.push_back(res);
}

void dfs(int d)
{
    if (d == C + 1)
    {
        solvechina();
        return;
    }
    for (int i = 1;i <= k[d];i++)
    {
        _a[d] = Y[d][i];
        dfs(d + 1);
    }
}
void init()
{
    for (int i = 1;i <= C;i++)vis[i].clear();
    ans.clear();
}
int main()
{
    while (~scanf("%d %d", &C, &S) && C)
    {
        init();
        bestc = -1;
        ll casnum = 1;
        for (int i = 1;i <= C;i++)
        {
            scanf("%lld %d", &m[i], &k[i]);
            casnum = casnum*k[i];
            for (int j = 1;j <= k[i];j++)
            {
                scanf("%lld", &Y[i][j]);
            }
            sort(Y[i] + 1, Y[i] + k[i] + 1);
            if (bestc == -1 || m[bestc] * k[i] < m[i] * k[bestc])
            {
                bestc = i;
            }
        }
        if (casnum > limit)
            solve();
        else
        {
            initchina();
            dfs(1);
            sort(ans.begin(), ans.end());
            for (int i = 0;S;i++)
            {
                for (int j = 0;j < ans.size();j++)
                {
                    ll theans = M*i + ans[j];
                    if (theans > 0)
                    {
                        printf("%lld\n", theans);

                        if (--S == 0)break;
                    }
                }
            }
        }
        puts("");
    }
    return 0;
}

Q - Emoogle Grid
这题也挺好的。
首先判断最大x的blocks上面的ans是否等于R,如果不等那么再加1行在判断,如果仍然不等的话就可以用BSGS求离散对数了。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<map>
#include<vector>
#include<math.h>
using namespace std;
map<pair<int,int>,int>M;
typedef pair<int,int>pii;
typedef long long ll;
const ll mod=1e8+7;

void change(int x,int y)
{
    pii tmp=make_pair(x,y);
    M[tmp]=0;
    tmp=make_pair(x+1,y);
    if(!M.count(tmp))
        M[tmp]=1;
}
 int N,K,B,R;
void takeone()
{
    for(int i=1;i<=N;i++)
        {
            pii tmp=make_pair(1,i);
            if(M.count(tmp))continue;
            M[tmp]=1;
        }
}

ll Countone()
{
    ll cnt=0;
    map<pii,int>::iterator itor;
    for(itor=M.begin();itor!=M.end();itor++)
    {
        if(itor->second==1)
            cnt++;
    }
    return cnt;
}

ll quick_mul(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b%2)ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;

}

ll extgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0){x=1;y=0;return a;}
    ll d=extgcd(b,a%b,x,y);
    int t=x;x=y;y=t-a/b*y;
    return d;
}

ll Inv(ll a,ll p)
{
    ll x,y,d;
    d=extgcd(a,p,x,y);
    if(d==1)return (x%p+p)%p;
    return -1;
}

ll BSGS(ll a,ll b,ll p)
{
    a%=p,b%=p;
    map<ll,ll>h;
    ll m=ceil(sqrt(p)),x,y,d,t=1,v=1;
    for(ll i=0;i<m;i++)
    {
        if(h.count(t))h[t]=min(h[t],i);
        else h[t]=i;
        t=(t*a)%p;
    }
    for(ll i=0;i<m;i++)
    {
        d=extgcd(v,p,x,y);
        x=(x*b/d%p+p)%p;
        if(h.count(x))return i*m+h[x];
        v=(v*t)%p;
    }
    return -1;
}

int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        M.clear();

        scanf("%d %d %d %d",&N,&K,&B,&R);
        int x,y;
        int mxrow=0,mxcnt=0;
        int one=0;
        for(int i=1;i<=B;i++)
        {
            scanf("%d %d",&x,&y);
            change(x,y);
            if(x==1)one++;
            if(mxrow<x)
            {
                mxrow=x;
                mxcnt=1;
            }
            else if(mxrow==x)
                mxcnt++;
        }
        printf("Case %d: ",cas++);
        ll cnt=Countone();
        if(mxrow>=1)
            cnt+=N-one;
        ll sum=1LL*mxrow*N-B;
        ll ans=quick_mul(K,cnt-mxcnt)*quick_mul(K-1,sum-(cnt-mxcnt))%mod;
        if(ans==R)
        {
            printf("%d\n",mxrow);
        }
        else
        {
            if(mxrow>=1)
                ans*=quick_mul(K,mxcnt)*quick_mul(K-1,N-mxcnt)%mod;
            else
                ans*=quick_mul(K,N);
            if(ans==R)
                printf("%d\n",mxrow+1);
            else
            {
                mxrow++;
                ll _k=quick_mul(K-1,N);
                ll invans=Inv(ans,mod);
                ll theans=BSGS(_k,1LL*R*invans%mod,mod);
                printf("%lld\n",theans+mxrow);
            }
        }
    }
    return 0;
}

R - 青蛙的约会
解同余方程。
求最小正整数解那个地方想了很久终于想通了。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<math.h>
#include<stdlib.h>
using namespace std;
typedef long long ll;
#define Abs(x) ((x)<0?-x:x)
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b, a%b);
}

ll exgcd(ll a, ll b, ll &x, ll &y)
{
    if (b == 0) { x = 1;y = 0;return a; }
    ll d = exgcd(b, a%b, x, y);
    ll t = x;x = y;y = t - a / b*y;
    return d;
}

int main()
{
    ll x, y, m, n, L;
    cin >> x >> y >> m >> n >> L;
    ll d = gcd(m - n, L);
    if (d==0||(y - x) % d != 0)
        puts("Impossible");
    else
    {
        ll t, k;
        if (m - n > 0)
            exgcd(m - n, L, t, k);
        else
            exgcd(n - m, -L, t, k);

        ll ans = t*(y - x) / d;
        d = Abs(d);
        ans %= (L / d);
        ans = (ans + L / d) % (L / d);
        printf("%lld\n", ans);
    }

}

W - Prime Time
这题虽然相比较上面的而言比较简单,但还是有一些坑点,就是精度问题。所以涉及浮点数输出的时候不妨加个eps。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define eps 1e-7
bool check(ll a)
{
    ll e=(ll)sqrt(0.0+a)+1;
    for(ll i=2;i<e;i++)if(a%i==0)return 0;
    return 1;
}
int t[10005];
void init()
{
    t[0]=1;
    for(int i=1;i<=10000;i++)
    {
        t[i]=t[i-1];
        ll tmp=1LL*i*i+i+41;
        if(check(tmp))t[i]++;
    }
}

int main()
{
    //freopen("D:\\1.txt","r",stdin);
    //freopen("D:\\2.txt","w",stdout);
    init();
    int a,b;
    while(~scanf("%d %d",&a,&b))
    {
        int left;
        if(a==0)left=0;
        else left=t[a-1];
       printf("%.2f\n",(0.0+t[b]-left)/(b-a+1)*100.0+eps);

    }
}

X - The equation
解不等式,有好多坑的地方。。。
首先判断一下a和b为0的情况。
除去这种特殊情况后,用exgcd求出答案,那么X=x+k*(b/d),Y=y-k*(a/d)。联立不等式求出k的范围,为了避免负数的情况,我们可以预处理一下a和b和c的正负。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
using namespace std;
typedef long long ll;
ll extgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
    if(!b)
        d=a,x=1,y=0;
    else
    {
        extgcd(b,a%b,d,y,x);
        y-=a/b*x;
    }
}

int main()
{
    ll a,b,c,x1,x2,y1,y2;
    scanf("%lld %lld %lld %lld %lld %lld %lld",&a,&b,&c,&x1,&x2,&y1,&y2);
    c=-c;
    if(c<0)
        c=-c,a=-a,b=-b;
    if(a<0)
        a=-a,swap(x1,x2),x1=-x1,x2=-x2;
    if(b<0)
        b=-b,swap(y1,y2),y1=-y1,y2=-y2;
    if(a==0||b==0)
    {
        if(a==0&&b==0)
        {
            if(c==0)
                cout<<(x2-x1+1)*(y2-y1+1)<<endl;
            else puts("0");
        }
        else if(a==0)
        {
            ll tmp=c/b;
            if(c%b==0&&tmp>=y1&&tmp<=y2)printf("%lld\n",x2-x1+1);
            else puts("0");
        }
        else if(b==0)
        {
            ll tmp=c/a;
            if(c%a==0&&tmp>=x1&&tmp<=x2)printf("%lld\n",y2-y1+1);
            else puts("0");
        }
    }
    else
    {
        ll x,y,d;
        extgcd(a,b,d,x,y);
        if(c%d)puts("0");
        else
        {
            x=x*(c/d);
            y=y*(c/d);
            double tmp1=b/d;
            double tmp2=a/d;
            ll r=min(floor(1.0*(x2-x)/tmp1),floor(1.0*(y-y1)/tmp2)),l=max(ceil(1.0*(x1-x)/tmp1),ceil(1.0*(y-y2)/tmp2));
            if(l>r)puts("0");
            else printf("%lld\n",r-l+1);
        }
    }


}

Y - Farey Sequence
求phi,然后前缀和即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
ll phi[maxn];
void init()
{
    for(int i=2;i<maxn;i++)phi[i]=0;
    phi[1]=1;
    for(int i=2;i<maxn;i++)if(phi[i]==0)
        for(int j=i;j<maxn;j+=i)
        {
            if(!phi[j])phi[j]=j;
            phi[j]=phi[j]/i*(i-1);
        }
    for(int i=3;i<maxn;i++)
        phi[i]+=phi[i-1];
}
int main()
{
    init();
    int n;
    while(~scanf("%d",&n)&&n)
    {
        printf("%lld\n",phi[n]);
    }

}

Z - The Super Powers
只要幂不是质数,那么就是 super power number,所以先处理出质数,然后快速幂的时候额外判断一下是否超过了limit。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<set>
#include<vector>
#include<math.h>
using namespace std;
typedef unsigned long long ll;
set<ll>S,ans;
const int maxn=100;
ll limit=(1ULL<<64)-1;
bool is[maxn];
int prm[maxn],id;
void init()
{
    memset(is,1,sizeof(is));
    int i, j, k = 0;
    int s, e = (int)(sqrt(0.0 + maxn) + 1);
    prm[k++] = 2; is[0] = is[1] = 0;
    for (i = 4; i < maxn; i += 2) is[i] = 0;
    for (i = 3; i < e; i += 2) if (is[i]) {
            prm[k++] = i;
            for (s = i * 2, j = i * i; j < maxn; j += s)
                is[j] = 0;
            //  因为j 是奇数,所以+ 奇数i 后是偶数,不必处理!
    }
    for ( ; i < maxn; i += 2) if (is[i]) prm[k++] = i;
    id=k;

}

ll quick_mul(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b%2)
        {
            //if(ans>limit/a)return -1;
            ans*=a;
            if(b==1)break;
        }
        if(ans>limit/a/a)return -1;
        a*=a;
        b/=2;
    }
    return ans;
}

int main()
{
    init();
    //cout<<limit<<endl;
    for(int i=2;i<=64;i++)if(!is[i])
    {
        ll now=1;
        while(1)
        {
            ll tmp=quick_mul(now,i);
            if(tmp==-1)break;
            ans.insert(tmp);
            now++;
        }
    }
    set<ll>::iterator itor;
   for(itor=ans.begin();itor!=ans.end();itor++)
        cout<<*itor<<endl;
}

注意,快速幂中b=1的时候实际上不用往下走了,所以要加个判断来break掉,否则可能会输出-1.

中国剩余定理即求离散对数的BSGS会在另一篇博客详细介绍。总之数论感觉仍然没有入门,还需要加油啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值