HENAU 冬令营 数学专题

题目链接:传送门
题目密码:202201150000
资料连接:
快速幂
逆元
容斥原理
扩展欧几里得
博弈论之取石子游戏的学习
题目内容:

这里是引用

小组题解

  • 数学问题
    • A - A^B Mod C
    • B - 逆元
    • C - 判决素数个数
    • D - 矩阵乘法
    • H - 互质数的个数(一)
    • I - Sumdiv
    • J - The Lottery
    • K - 组合数问题
    • L - 同余方程
  • 博弈题目
    • E - Bash游戏
    • F - 取石子游戏
    • G - Matches Game

数学问题

A - A^B Mod C

思路:这个题目首先能想到暴力,但是数据太大,所以不现实,因此用快速幂来解决,具体看上述链接
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=100100;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
//ll a,b,c;
ll pmod(ll a,ll b,ll c){
    ll ans=1;
    while(b){

        if(b&1)ans=(ans*a)%c;
        b>>=1;
        a=a*a%c;
        //cout<
    }
    return ans;
}
int main(){
    ll a,b,c;
    cin>>a>>b>>c;
    cout<<pmod(a,b,c);
    return 0;
}

B - 逆元

思路:本身是直接进行求解逆元的,但是超时。就看了看网上的题解,链接如下:题解
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e9;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
//ll a,b,c;
ll n;
map<ll,ll> in;

bool isprime(ll x)
{
    for(ll i=2;i*i<=x;i++)
    {
        if(x%i==0)
            return false;
    }
    return true;
}

int main(){
    cin>>n;
    if(!isprime(n)){
        cout<<"AKCniubi"<<endl;
    }
    else{
        cout<<n*(n-1)/2;
    }
    return 0;
}

C - 判决素数个数

思路:知道如何判别一个数是否时素数即可。这里用的素数筛,另外x和y的谁大谁小不确定
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
//ll a,b,c;
ll n;
ll pri[maxn+10],v[maxn+10];
ll m;
void prime(){
    v[1]=1;
    for(int i=2;i<=maxn;i++){
        if(v[i]==0){
            v[i]=i;
            m++;
            pri[m]=i;
            //cout<
        }
        for(int j=1;j<=m&&(i*pri[j]<=maxn);j++){
            v[i*pri[j]]=pri[j];
            if(i%pri[j]==0)break;
        }
        //cout<
    }
}
int main(){
    prime();
    ll x,y;
    scanf("%lld%lld",&x,&y);
    if(x>y){
        ll t=x;
        x=y;
        y=t;
    }
    ll ans=0;
    for(int i=x;i<=y;i++){
        if(v[i]==i&&i!=1){
            ans++;
            //cout<
        }
    }
    printf("%lld\n",ans);
    return 0;
}

D - 矩阵乘法

思路:了解下矩阵乘法就会了
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
ll n,m,k;
ll a[110][110],b[110][110],d[110][110];
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=k;j++){
            cin>>b[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            for(int h=1;h<=m;h++){
                d[i][j]+=a[i][h]*b[h][j];
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            cout<<d[i][j]<<" ";
        }
        cout<<endl;
    }

    return 0;
}

H - 互质数的个数(一)

思路:首先时了解互质的概念,其次是利用欧拉函数求结果
推导过程:
欧拉公式:phi(x) = (p1-1)/p1*(p2-1)/p2…(pn-1)/pn * x
推导过程如下:
x=p1^k1 p2k2*……pnkn ,对于某个数m=a^b(a为质数),与m不互质的数只有a的倍数,即m/a,即a ^ (b-1),所以与m互质的个数为m-m/a=a ^ b-a^(b-1);
类似地,
phi(x)=[p1^ k1-p1^(k1-1)] * [p2^ k2-p2^ (k2-1)]……
[pn ^ kn-pn^(kn-1)]
=[p1^(k1-1) * (p1-1)] * [p2^(k2-1)
(p2-1)]……* [pn^(kn-1)*(pn-1)]
=[p1^ (k1-1) * p2^ (k2-1)……pn^(kn-1)][(p1-1) * (p2-1) * …… * (pn-1)]
=x * [(p1-1) * (p2-1)… * (pn-1)]/[(p1-1) * (p2-1) * …… *(pn-1)]
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
ll t;
ll solve(ll n){
        ll ans=n;
        for(ll i=2;i*i<=n;i++){
            if(n%i==0){
                ans=ans/i*(i-1);
                for(;n%i==0;n/=i);
            }
        }
        if(n!=1){
            ans=ans/n*(n-1);
        }
        return ans;
}
int main(){
    scanf("%lld",&t);
    ll n;
    while(t--){
        scanf("%lld",&n);

        printf("%lld\n",solve(n));
    }
    return 0;
}

I - Sumdiv

思路:
约数和公式:
对于已经分解的整数A=(p1 ^ k1) * (p2 ^ k2) * (p3 ^ k3)… * (pn^kn)
有A的所有因子之和为
S = (1+p1+p1 ^ 2+p1 ^ 3+…p1^k1) * (1+p2+p2 ^ 2+p2 ^ 3+….p2^k2) * (1+p3+ p3 ^ 3+…+ p3^k3) * … * (1+pn+pn ^ 2+pn ^ 3+…pn^kn)
等比数列计算:直接用二分递归求:举例来说,1+a+a ^ 2+a ^ 3+a ^ 4=(1+a) * (1+a2)+a2;(1+a)=1
(1+a)。根据奇偶二分下去。只要只有一个数为止。
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=9901;
ll n,m;
struct node{
    ll x,y;
}p[maxn];
ll pmod(ll x,ll y){
    ll res=1;
    while(y){
        if(y&1){
            res=res*x%mod;
        }
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
ll update(ll pn,ll pm){
    //cout<
    if(pm==0){
        return 1;
    }
    if(pm&1){
        return update(pn,pm/2)*(1+pmod(pn,pm/2+1))%mod;
    }else{
        return (update(pn,pm/2-1)*(1+pmod(pn,pm/2+1))+pmod(pn,pm/2))%mod;
    }
}
int main(){
    while(scanf("%lld%lld",&n,&m)!=EOF){
        ll k=0;
        for(int i=2;i*i<=n;){
            if(n%i==0){
                k++;
                p[k].x=i;
                p[k].y=0;
                while(n%i==0){
                    p[k].y++;
                    n/=i;
                }
            }
            if(i==2){
                i++;
            }else{
                i+=2;
            }
        }
        if(n!=1){
            k++;
            p[k].x=n;
            p[k].y=1;
        }
        ll ans=1;
        //cout<
        for(int i=1;i<=k;i++){
            ans=(ans*update(p[i].x,p[i].y*m)%mod)%mod;
            //cout<
        }
        printf("%lld\n",ans);
    }
    return 0;
}

J - The Lottery

思路:用二进制进行枚举所有情况,采用容斥原理去重,其中进行反面考虑,a的倍数有n/a个;既是a,也是b的倍数,即lcm(a,b)的倍数有n/lcm(a,b)个。是a,b,c的倍数,即lcm(a,b,c)的倍数有n/lcm(a,b,c)个。
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=9901;
ll t,k,n,m;
ll a[maxn];
ll gcd(ll a,ll b){
    return b ? gcd (b,a%b) : a;
}
ll lcm(ll a,ll b){
    ll t=gcd(a,b);
    return a/t*b;
}
int main(){
    while(cin>>n>>m){
        for(int i=0;i<m;i++){
            cin>>a[i];
        }
        ll sum=0;
        for(int i=1;i<(1<<m);i++){//枚举所有的情况
            ll cnt=0;
            ll ans=1;
            for(int j=0;j<m;j++){
                if(i&(1<<j)){
                    ans=lcm(ans,a[j]);
                    if(ans>n){
                        break;
                    }
                    cnt++;
                }
            }
            if(cnt&1){
                sum+=n/ans;
            }else{
                sum-=n/ans;
            }
        }
        cout<<n-sum<<endl;
    }
    return 0;
}

K - 组合数问题

思路:首先进行预处理,防止超时。理解组合数的运算,即C(n,m)=C(n-1,m-1)+C(n-1,m),然后简单的动态规划
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=9901;
ll t,k;
ll n,m;
ll a[2010][2010],num[2010][2010];
void init(){
    a[1][1]=1%k;
    if(a[1][1]==0)num[1][1]=1;
    for(int i=2;i<2001;i++){
        a[i][1]=i%k;
        if(a[i][1]==0)num[i][1]=1;
        for(int j=2;j<=i;j++){
            a[i][j]=(a[i-1][j-1]+a[i-1][j])%k;
            if(a[i][j]==0){
                num[i][j]=num[i][j-1]+1;
            }else{
                num[i][j]=num[i][j-1];
            }
        }
    }
}
int main(){
    cin>>t>>k;
    init();
    while(t--){
        cin>>n>>m;
        int ans=0;
        for(int i=1;i<=n;i++){
            ll t=i;
            if(m<i){
                t=m;
            }
            ans+=num[i][t];
        }
        cout<<ans<<endl;
    }
    return 0;
}

L - 同余方程

思路:ax≡1(modb) 可以通过变化得到ax+kb=gcd(a,b),因为一定有解,这个就是扩展欧几里得原理的结果
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=9901;
ll a,b,x=0,y=0;
void exgcd(ll a,ll b,ll &x,ll &y){
    if(!b){
        x=1,y=0;
    }else{
        exgcd(b,a%b,x,y);
        ll t;
        t=x,x=y,y=t-a/b*y;
    }
}
int main(){
    cin>>a>>b;
    exgcd(a,b,x,y);
    cout<<(x%b+b)%b;
    return 0;
}

博弈题目

E - Bash游戏

思路:
1.n<=k,A必赢 ,n=k+1,B必赢
2.k+1k+1)时A只需拿n-(k+1)(必定小于等于K)个,A必赢
3.n=2(k+1)时,无论A拿多少个,假设x个,此时n-x>=k+2,B只需拿(n-x-(k+1))个即可,B必赢
4.以此类推
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
ll n,k;
//B n==k+1
//A n<=k
int main(){
    ll t;
    cin>>t;
    while(t--){
        cin>>n>>k;
        if(n%(k+1)){
            cout<<"A"<<endl;
        }else{
            cout<<"B"<<endl;
        }
    }
    return 0;
}

F - 取石子游戏

思路:属于威佐夫博奕(Wythoff Game)
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
ll n,k;
//A 1 1
//B 2 1;
double x = (sqrt(5.0) + 1)/2.0;
int main(){
    ll a,b;
    while(cin>>a>>b){
        ll t=abs(b-a);
        if(a>b){
            ll k=a;
            a=b;
            b=a;
        }
        if((int)(t*x)==a){
            cout<<"0"<<endl;
        }else{
            cout<<"1"<<endl;
        }
    }
    return 0;
}

G - Matches Game

思路:当异或和为奇数时,先手先拿,拿完后,后手无论怎么拿,先手一定拿和后手一样的,一定可以拿到最后。当异或和为偶数时,无论先手怎么拿,后手只需拿一样的,一定可以拿到最后
代码:

#include
using namespace std;
typedef long long int ll;
typedef pair<ll,ll> P;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f;
const int maxm=5e4+5;
const int mod=1000000007;
ll n;
int main(){
    while(cin>>n){
        ll ans=0;
        for(int i=1;i<=n;i++){
            ll x;
            cin>>x;
            ans=ans^x;
        }
        //cout<
        if(ans){
            cout<<"Yes"<<endl;
        }else{
            cout<<"No"<<endl;
        }
    }
    return 0;
}

你可能感兴趣的:(竞赛,思维题,动态规划,深度优先,算法,线性代数)