有关数学 结论 规律 题目 小结

最近会抽空总结一部分 luogu 或者 校oj 或者 其他网站的 思维难度不大但是有意思的数学题目 不过关于jzyz 题库的 整理中也会涉及 一部分数学题

包含新题 或者 以前做过的题目;

更新于:11.4

神仙推荐题目

求N个数字的排列 并且保证 存在M个逆序对 保证字典序最小

思考一节课 无果... 或许是个贪心构造的数学题 或许可以做 首先知道一个事情是 一个长度为n的排列 逆序对个数 不会超过 n*(n-1)/2 然后??

我终于 会了 先写一个 nlog^2 的算法 就是逐位判断当前最小可以放什么 然后考虑 此时对逆序对的贡献的贡献 抵消 同时判断  后面能否在放这个多数字 好像可以写树状数组 或者主席树

那么来探讨一下 (n) 的做法 那么考虑构造 我认为这个题目 可以是 一部分升序 一部分调整 然后一部分降序 这三部分 不一定都有数字 但是 我们考虑通过这样来构造序列

根据我们刚才已知的序列逆序对贡献 那么我们可以直接找到 最大的 一个 i 保证后面 能贡献 大于等于 m 的逆序对 然后 考虑此时 前面都可以 放 一个升序的 序列

直接输出 第一部分 那么考虑此时多出来的逆序对 我们肯定是 选择一个第二部分 的 n-i*(i-1)/2 位置 这里的 i 指前面找到的 i 然后 调整到前面 从而消掉 多余的逆序对贡献  最后降序输出

此时不要输出调整到前面的数字即可 

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
typedef long long ll;
ll n,m; 
int main() {
    read(n); read(m);
    if(m==0) {
        for(int i=1;i<=n;i++) {
            cout<<i<<' ';
        }
        cout<<endl;
        return 0;
    }
    for(ll i=2;i<=n;i++) {
        //cout<<"((";
        if(i*(i-1)/2>=m&&(i-1)*(i-2)/2<m) {
            int pos=i;
            int res=i*(i-1)/2-m;
            for(int i=1;i<=n-pos;i++) {
                cout<<i<<' ';
            }
            cout<<n-res<<' ';
            for(int i=n;i>=(n-pos+1);i--) {
                if(i!=n-res) cout<<i<<' ';
            }
            return 0;
        }
    }
    return 0;
}
View Code

当然不能少了 鬼谷子的钱袋了qwq

鬼谷子也是一个非常节俭的人,他想方设法使自己在满足上述要求(在他现有金币的支付能力下,任何数目的金币他都能用这些封闭好的小钱的组合来付账)的前提下,

所用的钱袋数最少,并且不有两个钱袋装有相同的大于1的金币数。

假设他有m个金币,你能猜到他会用多少个钱袋,并且每个钱袋装多少个金币吗?

我们知道任何n以内的数字 都可以 用 n/2 以内的数字 表示出来 那么 我们就可以不断除以2 同时把这些数字 加到 答案里面 然后输出即可 

其实 挺难想的 我觉得 这个构造是很巧妙的 还会存在一些问题

#include<bits/stdc++.h>
using namespace std;
int n,num,a[1000050]; 
int main() {
    cin>>n;
    while(n>0) {
        if(n%2==0)
            a[++num]=n/2;
        else a[++num]=n/2+1;
        n/=2;
    }
    cout<<num<<endl;
    sort(a+1,a+num+1);
    for(int i=1;i<=num;i++) {
        cout<<a[i]<<" ";
    }
    return 0;
}
View Code

现在有1 2 3 4.... n的一个排列 那么每次可以选择 若干个数字 减去一个相同的数字 那么输出最小次数 使得他们减为0

贪心的去想 一定是 找到中间那个数字 把后面的都减去 例如1 2 3 4 5 6 这时候肯定是 把4 5 6 减成 0 1 2 此时变成1 2 3 0 1 2

那个考虑 此时序列相当于1 2 3 然后 不断这样去做 我们发现 答案是 $f_n=f_{n/2}+1$

其实 不断思考 我们发现答案就是 fn=log(n)+1 反正当时 我是这样写的

[8.21]8的倍数

这个题 看到 呜呜呜 只会写暴力 考虑怎么优化这个题目呢 思考了一会 发现没有什么思路

那么考虑 此时 我去问了一下强者 强者跟我说 容斥一下就行了 不会 出去和老师恰了个饭 一餐的方便面还不错 回到机房 等待晚上讲 前四场的模拟赛题解 那么思考了一下 发现了 其中的规律

其实 我们可以预处理 1-y 的满足条件的 个数 和1 -  x-1 的个数 然后二者相减 就是个数 

考虑数论了 显然是2^n 枚举因子的使用情况 那么寻找当前使用因子和8的lcm 根据容斥 那个神奇的例子 我们奇减偶加 然后用x-1或者y除以这个lcm 然后就求出了出现个数

后来发现可以dfs 其实二进制枚举就可以

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
typedef long long ll;
const int N=20;
int n,m,x,y,a[N];
inline ll gcd(ll a,ll b) {
    return b?gcd(b,a%b):a;
}
inline ll ask()  {
    ll ans=0;
    for(int i=0;i<(1<<n);i++) {
        ll tot=0,num=8;
        for(int j=0;j<n;j++) {
            if(i&(1<<j)) {
                num*=a[j]/gcd(a[j],num);
                if(num>y) break;
                tot++;
            } 
        }
        if(tot&1) ans-=y/num;
        else ans+=y/num;  
    }
    return ans;
} 
inline ll ask1() {
    ll ans=0;
    for(int i=0;i<(1<<n);i++) {
        ll tot=0,num=8;
        for(int j=0;j<n;j++) {
            if(i&(1<<j)) {
                num*=a[j]/gcd(a[j],num);
                if(num>(x-1)) break;
                tot++;
            }
        }
        if(tot&1) ans-=(x-1)/num;
        else ans+=(x-1)/num;
    }
    return ans;
}
int main() {
    read(n);
    for(int i=0;i<n;i++) read(a[i]);
    read(x); read(y);
    printf("%lld
",ask()-ask1());
    return 0;
} 
View Code

题目大意:

一个圆被分成了n个部分 然后每个部分可以染m种颜色 然后考虑 此时相邻颜色不同的方案数 思考了一会 联想到一个题目

果然学数学还是好啊 记得原来数学竞赛书上关于递推关系的讲解 有个题目 对于一个凸n多边形n>=3 然后考虑 此时 可以染三种颜色 那么 相邻颜色不同的方案数

也就是m=3 的情况 简单分析一下 我们发现 把边进行编号 那么 考虑 此时 对应的是a1 a2 .... an-1 an

那么狠容易 我们发现a1 有三种选择然后a2 a3....an-1 都只有两种选择 那么an也有两种选择

但是显然存在an和a1 颜色相同情况 那么这种情况 就是对应 n-1 多边形的时候 an和a1 不同的方案数 

所以 递推关系 我们Pn=2^(n-1)*3-Pn-1 其实这是(n)的 考虑 优化 求一下 通项 不难发现 Pn=2^n+(-1)^n*2 (n>=3) 

复杂度就变成了 logn 的一个快速幂即可 

那么 我认为这个问题也可以抽象成这个简例 那么提供另外一种思路 比较适合计算机qwq

那么 用f[i][0/1] 表示 i 边形 并且 ai和a1 颜色相同(1) 和a1 颜色不同(0);

然后递推 仿照上面的 $f[n][0]=f[n-1][0]*(m-2)+f[n-1][1]*(m-1),f[n][1]=f[n-1][0]$ 其实发现这个递推式也用了上面的思想 

不过这样我们就可以列出来矩阵进行求解了 一个2*2 的转移矩阵 1*2 的状态矩阵 就可以了

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
ll t,n,m,f[2],a[2][2];
void mul(ll f[2],ll a[2][2]) {
    ll c[2];
    memset(c,0,sizeof(c));
    for(int i=0;i<2;i++)
        for(int k=0;k<2;k++)
            c[i]=(c[i]+f[k]*a[k][i]%mod)%mod;
    memcpy(f,c,sizeof(c));
}
void mulself(ll a[2][2]) {
    ll c[2][2];
    memset(c,0,sizeof(c));
    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
            for(int k=0;k<2;k++)
                c[i][j]=(c[i][j]+a[i][k]*a[k][j]%mod)%mod;
    memcpy(a,c,sizeof(c));
}
int main(){
    freopen("orzzzq.in","r",stdin);
    freopen("orzzzq.out","w",stdout);
    scanf("%lld",&t);
    while(t--) {
        scanf("%lld%lld",&n,&m);
        if(n==1) {
            printf("%lld
",m);
            continue;
        }
        f[0]=0;f[1]=1;
        a[0][0]=m-2;a[0][1]=1;
        a[1][0]=m-1;a[1][1]=0;
        n--;
        for(;n;n>>=1) {
            if(n&1) mul(f,a);
            mulself(a);
        }
        printf("%lld
",m*f[0]%mod);
    }
    return 0;
}
View Code

 不来点数论都对不起这个名字 鉴于模拟赛的文章加密 所以 考虑 把模拟赛遇到的文章 搬过来一点 

其实就是关于excrt的探讨的研究 

T1:给定 {m},询问有多少种可能的 {a} 使得不存在 x 使得关于 {a}, {m} 的同余方程成立。

考场上暴力解了excrt 然后求了方案数 大致是nlogn的复杂度 然后 考试快结束的时候 我的电脑系统自己把自己删掉了 比较nb 就不放考场代码了;

最后 这是个结论题 我丢 我太难了; 

不妨复习一下exgcd相关的知识 我的数学比较垃圾qwq; 

好了我顺带复习一下gcd 先丢几道比较有意思的题目:

给定一个D 求满足lcm(x,y)+gcd(x,y)=D的x,y有多少对;

刚看到这个题目 我无从下手qwq 显然我只知道有一个东西 是a*b=lcm(a,b)*gcd(a,b) 这带进去有个锤子作用我丢;

我们考虑 x,y 可以由什么唯一确定?显然 对于x=gcd(x,y)*a, y=gcd(x,y)*b,

对于一组唯一的x,y 那么a,b的值也是唯一的 并且是最大公约数 我们发现a,b就是互质的 有点思路了 在思考一下;

我们发现a*b=(x/gcd(x,y))*(y/gcd(x,y))=lcm(x,y)/gcd(x,y) 显然是把那个我觉得没有用的式子带入hh

根据题目给出的D=lcm(x,y)+gcd(x,y); 我们代入 发现a*b=(D-gcd(x,y))/gcd(x,y)=D/gcd(x,y) - 1;

D显然是可以整除gcd(x,y)的 所以我们不妨找出他所有因子 然后求出RHS的值 找到一个数z是多少个互质的数的乘积;

没有代码 你筛就完事了 开个数组记录一下个数就行了 听说还可以质因数分解 

好了以上打住 对于数论的文章 我也不清楚 什么时候能鸽出来

我们回到excrt 显然对于模数两两互质的情况 对于任意一组{an} 都是满足条件的数列 所以这一定是有解的 

注意我们一直考虑的都是互质 而不是 每个数都是质数 如果每个质数都相同 我们显然可以构造出来不满足的情况

我们考虑这个解是什么 应该都清楚exgcd的流程 我先丢个代码解不定方程的代码 以免我忘了 

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

[TJOI2009]猜数字

保证mi互质 我们构造m=∏mi  令 Mi=m/mi 然后解同余方程 MiTi ≡ 1(mod bi);

这里我们可以写成一个不定方程用欧几里得求解 那么对于方程的一个解:

 题目要求最小 我们不断对m取模控制范围即可 因为求出一个特解x之后 我们的通解可以表示成 x+km (k为整数)的形式;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
ll n,m=1,a[13],b[13],M,t[13];
inline ll ksc(ll a,ll b) {
    ll cnt=0;
    while(b) {
        if(b&1) cnt=(cnt+a)%m;
        a=a*2%m;
        b=b>>1;
    }
    return cnt%m;
}
ll x,y;
inline ll exgcd(ll a,ll b,ll &x,ll &y) {
    if(!b) {x=1,y=0;return a;}
    ll d=exgcd(b,a%b,x,y);
    ll z=x;x=y;y=z-y*(a/b);
    return d;
}
int main() {
//    freopen("1.in.cpp","r",stdin);
    read(n);
    for(ll i=1;i<=n;i++) read(a[i]);
    for(ll i=1;i<=n;i++) read(b[i]),m*=b[i];
    ll ans=0;
    for(int i=1;i<=n;i++) {
        M=m/b[i];
        exgcd(M,b[i],x,y);
        //cout<<x<<endl;
        ans=(ans+ksc(ksc((M%m+m)%m,(x%m+m)%m),(a[i]%m+m)%m))%m;
    }
    printf("%lld
",(ans%m+m)%m);
    return 0;
}
View Code

我们在这里考虑 模数不互质的情况 这就用到了扩展中国剩余定理我们考虑使用数学归纳法证明 古人的智慧 我就不写证明了;

那么我们考虑求出来前k-1个方程的解 m=lcm(m1,m2,m3,...mk-1) 那么x+i*m就是前k-1个方程的通解 考虑第k个方程 ,求出一个整数t st x+t*m ≡ ak(mod mk) 该方程等价于 m*t ≡ ak-x(mod mk)其中t是未知量 然后这是一个线性同余方程

我们用扩展欧几里得判断是否有解 如果有解 x’=x+t*m 是前k个方程的解 那么n次扩欧 就可以求出方程的解;

考虑到我们上面那个题目 有人说题目是Dove 的疑惑 所以考虑到小凯的疑惑 乘法减去一个东西 有人就累乘 从而发现了 规律qwq;

不妨我们来分析一下 我们取 M = lcm{m},容易观察到,对于任意一个 x,其 mod{m} 得到的 {a} 总是与 x + M mod {m} 得到的 {a} 是一致的。

而对于任意两个不同的数 x, y 来说,如果他们的差值不超过 M,那么则说明他们至少在模一个 mi 时不同,也就是说存在不同的 {a}。

所以我们可以说,对于 {m} 来说,如果保证了 mi 互不相同,那么恰好存在 M 个 x 存 在 {a} 且互不相同。 所以,答案就是 ∏ m − M。这里是累乘模数

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
typedef long long ll;
ll n,v;
inline ll mul(ll b,ll p) {
    ll cnt=0;
    while(p) {
        if(p&1)cnt=cnt+b;
        b=b<<1;
        p=p>>1;
    }
    return cnt;
}
inline ll gcd(ll a,ll b) {
    return b?gcd(b,a%b):a;
}
int main() {
    freopen("math.in","r",stdin); 
    freopen("math.out","w",stdout); 
    read(n);
    ll m=1,lcm=1;
    for(int i=1;i<=n;i++) {
        read(v);
        m=mul(m,v); lcm=lcm/gcd(lcm,v)*v;
    }
    cout<<m-lcm<<endl;
    return 0;
}
View Code

屠龙勇士 excrt好题

该到组合数啦qwq

luogu青原樱 这个题目了 是一道模拟赛的题目 所以多写比赛 多见题是好的Y  

青原樱那个题目的大致意思就是 对于n个位置 然后放置m个花盆 保证两两之间有一个空位,问所有合法的方案数;

那我们怎么考虑求解呢 对于一个长为n的数轴 每两个之间空一个空地 然后 我们将其映射到一个坐标上 我们发现 对于这样一个序列 {am}

我们满足 任意两个 ai+1-ai>=2; 也就是把题目转化为了这样一个问题;

我们考虑 在n个空地里 先放进去m个花盆 至少要有m-1个空位置 然后剩余n-m+1个空位置 对于这n-m+1个空位之中 我们放进去m个 求方案数就是答案

因为 此时我们已经保留了 m-1个位置 我们任意填进去花盆 都会保证 存在空位能够让他和相邻的 相差一个空位 对于两个花盆 我们对他的位置上没有任何要求

因为 我们存在空位使其与当前位置交换之后满足相差一个位置 所以说 我们根本不关心 他把花盆放在那里 所以 我们只需要保留了至少m-1个空位置 所以剩下就是求方案了

所以对于那个题目答案就是C(n-m+1,m)*m!,因为题目不要求前后顺序;

T1 就是让你搞一个长为m的数列 然后第一个数是1,最后一个数是n,然后相邻两个数ai+1-ai>=k;

也就是说 上面那个题目就是 k==2 的情况 而这个题目就是k取任意值的方案数

我们依旧类比放空位置 我们不考虑 第一个数和第二个数 所以我们的范围变成了n-2;

我们将其对应到数轴上 然后 我们先留出来(m-1)*(k-1)的空位置 ,然后对于剩下长度为n-2-(m-1)*(k-1)的数轴上 放m-2的数字 因为第一个和最后一个确定了

所以方案数就是C(n-2-(m-1)*(k-1),m-2);

对于判断奇偶 我们可以用卢卡斯定优化这个过程 对于一个组合数C(n,m)当n&m==m的时候就是奇数;当然 我想到了阶乘分解2的个数 不过是多个log的复杂

很好证明吧 毕竟都是前人的智慧就不讲了了;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int T,n,m,k;
int main() {
    read(T);
    while(T--) {
        read(n); read(m); read(k);
        if(((n-2-(m-1)*(k-1))&(m-2))==m-2) puts("Yes");
        else puts("No");
    }    
    return 0;
}

T1 满分做法
View Code

题目:一个数字串S 一个S打乱后的T串 用S - T 注意是转化后的数字相减 )考场上以为是 S到T 得到一个新的数字 然后随机抽一个数字 得到一个字符串P

现在给定P 那么求一组合法解以及删掉的数字

其实算是一个构造题目吧

不过 这个很巧妙的小结论 大家还是有同学看出来的 我考场上按了好几遍计算器 我发现了这个东西 不过原来确实见过

也就是 对于 一个数字 他的每一位数字任意排列 将会得到一个新的数字 那么此时用较大的数字 减去 较小的数字 我们将会得到一个差值

那么这个差值 一定是9的倍数 那么 怎么找到这个数字 我们考虑 什么样子的数字是9的倍数

其实 我们要注重积累 我原来写计蒜客 普及 模拟赛 遇到一个题目 给你一个区间$[L,R]$ 这个区间的数字 L,L+1,L+2,....R 组在一起 构成一个数字

判断这个数字是不是9的倍数 L,R是1e12级别的

那么存在一个定理 是不是9的倍数 只需要判断 所有位的数字之和 是不是9的倍数即可 那么就是一个等差数列求和

为什么 正确呢 大家利用同余证明一下即可 其实 我很早发现9就是一个很神奇的数字 他的所有倍数 的 个位数 能把1-9都表示一边 看似没有用 

那么 考虑这个数字是谁 只需要所有位数字加起来 然后 % 9 再用 9 这个数字减去即可

那么 有的同学就开始暴搜全排列 qwq 不过 我们发现此时这个东西一个能表示成9x的形式 那么 10x-x=9x 题目允许前导零 所以我们直接求X即可

注意除的时候处理一下即可 放考场代码 qwq 不过乱搞有50 或者 80??

原文地址:https://www.cnblogs.com/Tyouchie/p/11648925.html