文章目录
前言
数论哇,数学这么差的我不知道能不能攻克这个难题哇!
辗转相除法
求最大公约数有一个非常有名的算法:欧几里得算法—辗转相除法
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;//0和任意一个数的最大公约数都是那个数本身
}
int main()
{
int a,b;
cin>>a>>b;
cout<<gcd(a,b)<<endl;
return 0;
}
一、等差数列
任意门
这一题的输入输出有点多,我们用scanf来读入哦!
首先已知这个是个等差数列,然后我们就利用等差数列的公式找规律,然后会发现其实找的就是最大公约数
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=100010;
int a[N];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
int d=0;
for(int i=1;i<n;i++)
d=gcd(d,a[i]-a[0]);//a[i]-a[0]是d的倍数
if(!d)
printf("%d",n);
else printf("%d",(a[n-1]-a[0])/d+1);
return 0;
}
算术基本定理
所有的正整数都可以唯一的分解成若干个质数相乘的情况(p为指数,α>0)
若要保证一个数列单调递增,那么他们的最大长度就是所有的阿尔法相加的总数。
筛法求素数–线性筛( O ( N ) O(N) O(N))
可以算出来1~n当中所有质数,以及每个数的最小质因子
①筛掉的数一定是合数,且一定是用其最小质因子筛的。
②合数一定会被筛掉。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1000010;
int primes[N],cnt;//存所有的质数
bool st[N];//当前数有没有被筛过
int get_primes(int n)
{
for(int i=2;i<=n;i++)//从2开始哦!
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]*i<=n;j++)
{
st[primes[j]*i]=true;
if(i%primes[j]==0)break;//p[j]一定不大于i的所有质因子
}
}
}
二、X的因子链
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=(1<<20)+10;//这个是不会爆int的,可以用计算机自己算算
int min_p[N];
int primes[N],cnt;//存所有的质数
bool st[N];//当前数有没有被筛过
void get_primes(int n)
{
for(int i=2;i<=n;i++)//从2开始哦!
{
if(!st[i]){
min_p[i]=i;
primes[++cnt]=i;
}
for(int j=1;primes[j]*i<=n;j++)
{
int t=primes[j]*i;
st[t]=true;
min_p[t]=primes[j];
if(i%primes[j]==0)break;//p[j]一定不大于i的所有质因子
}
}
}
int sum[N];
int main()
{
get_primes(N);
int x;
while(scanf("%d",&x)!=EOF)
{//tot用于记录最大长度,k表示第i个质因子的下标
int k=0,tot=0;
while(x>1)
{
int p=min_p[x];
sum[k]=0;
while(x%p==0)
{
sum[k]++;
tot++;
x/=p;
}
k++;
}
//求所有质因子出现总次数的全排列
long long res=1;
for(int i=2;i<=tot;i++)
res*=i;//tot!
//去除各个质因子重复出现的次数
for(int i=0;i<k;i++)
for(int j=1;j<=sum[i];j++)
res/=j;
printf("%d %lld\n",tot,res);//输出最长序列的长度, 以及满足最大长度的序列的个数
}
return 0;
}
三、聪明的燕姿
约数的个数与和
满足这种题目要求的数是十分少的。所以我们可以爆搜+剪枝。
这个讲解讲的还蛮清楚的,我就是看这个才看懂的。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=50000;
int primes[N],cnt;
bool st[N];
int ans[N];
int len =0;
void get_primes(int n)
{
for(int i = 2;i <= n;i++)
{
if(!st[i]) primes[cnt++] = i;
for(int j = 0;primes[j]*i <= n;j++)
{
st[primes[j]*i] = true;
if(i % primes[j] == 0) break;
}
}
}
int is_prime(int n)
{
if(n < N) return !st[n];//没有被筛过说明就是质数,返回true
for(int i = 0;primes[i] <= n / primes[i];i++)
{
if(n % primes[i] == 0) return false;
}
return true;
}
void dfs(int last,int prod,int s)//last上一个用到质数的下标是多少,prod是当前这个数,s是剩下的这个数
{
if(s==1)
{
ans[len++]=prod;
return ;
}
if(s-1>((last<0)?0:primes[last])&&is_prime(s-1))
{
ans[len++]=prod*(s-1);
}
for(int i=last+1;primes[i]<=s/primes[i];i++)//尽量不用乘法,否则会爆int
{
int p=primes[i];
for(int j=1+p,t=p;j<=s;t*=p,j+=t)
{
if(s%j==0)
dfs(i,prod*t,s/j);
}
}
}
int main()
{
get_primes(N-1);
int s;
while(cin>>s)
{
len=0;
dfs(-1,1,s);//因为我们的下标是从0开始
cout<<len<<endl;
if(len)
{
sort(ans,ans+len);
for(int i=0;i<len;i++)
cout<<ans[i]<<' ';
cout<<endl;
}
}
return 0;
}
裴属定理—扩展欧几里得定理
如果对于正整数a,b有他们的最大公约数d,那么必定存在一组系数,ax+by=d。欧几里得是求出d来,扩展欧几里得是求出x,y来,得到一组解。
只要知道一组解,那么所有的解都可以求出来了。
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
int a,b,x,y;
cin>>a>>b;
int d=exgcd(a,b,x,y);
printf("%d * %d +%d * %d =%d",a,x,b,y,d);
return 0;
}
四、五指山
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,d,x,y,a,b;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1,y=0;
return a;
}
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld%lld%lld",&n,&d,&x,&y);
int gcd=exgcd(n,d,a,b);
if((y-x)%gcd)puts("Impossible");
else
{
b*=(y-x)/gcd;
n/=gcd;
printf("%lld\n",(b%n+n)%n);//这样子一定可以得到一个正数
}
}
return 0;
}
五、最大比例
任意门
相当于等比数列告诉你各个数,然后求其最大等比
排序+判重
求k最大值的情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110;
int n;
ll x[N],a[N],b[N];
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll gcd_sub(ll a,ll b)
{
if(a>b)swap(a,b);
if(b==1)return a;
return gcd_sub(b,a/b);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>x[i];
sort(x,x+n);//排序
int cnt=0;
for(int i=1;i<n;i++)//去重
if(x[i]!=x[i-1])
{
ll d=gcd(x[i],x[0]);
a[cnt]=x[i]/d;
b[cnt]=x[0]/d;
cnt++;
}
ll up=a[0],down=b[0];
for(int i=1;i<cnt;i++)
{
up=gcd_sub(up,a[i]);
down=gcd_sub(down,b[i]);
}
cout<<up<<"/"<<b<<endl;
return 0;
}
六、C循环
任意门
可以将式子推出来像下面这个样子。
我们就可以用扩展欧几里得的式子将系数求出来。
exgcd的变化过程。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1,y=0;
return a;
}
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
ll a,b,c,k,res;
while(cin>>a>>b>>c>>k,a||b||c||k)//逗号表达式,只会看最后一个表达式的值
{
ll x,y;
ll mod=1ll<<k;
ll d=exgcd(c,mod,x,y);//扩展欧几里得
if((b-a)%d)cout<<"FOREVER"<<endl;
else {
x*=(b-a)/d;
mod/=d;
cout<<(x%mod+mod)%mod<<endl;//这样子就保证了他是正数
}
}
return 0;
}
pow求出来不是一个精确的值!
七、正则问题(递归)
任意门
所有的递归问题都可以看成一棵树。
难点在于建树。笑死,这怎么想的到哈哈哈哈
学数论逐渐癫狂
#include<bits/stdc++.h>
using namespace std;
int k;
string str;
int dfs()
{
int res=0;
while(k<str.size())
{
if(str[k]=='(')//处理(....)
{
k++;//跳过左括号
res+=dfs();
k++;//跳过右括号
}
else if(str[k]=='|')
{
k++;//跳过'|'
res=max(res,dfs());
}
else if(str[k]==')')break;//这里是个坑,很容易写成k++
else{
k++;//跳过x
res++;
}
}
return res;
}
int main()
{
cin>>str;
cout<<dfs()<<endl;
return 0;
}
八、糖果(爆搜,递归!!!)
任意门
这一道题目,不需要考虑个数,只需要考虑有没有。
模拟样例:
最少选几行使得每一列至少存在一个1
这题数据不是很大
这是一个经典的题目:重复覆盖问题
(还有一个精确覆盖问题:八皇后、数独)
Dancing links来做这两类题目
但是我们这里用爆搜,因为数据量很小
①顺序
{
经典优化,迭代加深(我们首先枚举,只需要1行能不能得到答案,如果不行,两行。。。以此类推)
}
②将选择最少的列
③可行性剪枝(至少要选多少行,我们还可以用二进制来进行有哈)
lowbit()可以返回x的最后一位1
写法一:这是y总的写法
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110, M = 1 << 20;
int n, m, k;
vector<int> col[N];
int log2[M];
int lowbit(int x)
{
return x & -x;
}
int h(int state) // 最少需要再选几行
{
int res = 0;
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
res ++ ;
for (auto row : col[c]) i &= ~row;
}
return res;
}
bool dfs(int depth, int state)
{
if (!depth || h(state) > depth) return state == (1 << m) - 1;
// 找到选择性最少的一列
int t = -1;
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
if (t == -1 || col[t].size() > col[c].size())
t = c;
}
// 枚举选哪行
for (auto row : col[t])
if (dfs(depth - 1, state | row))
return true;
return false;
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
for (int i = 0; i < n; i ++ )
{
int state = 0;
for (int j = 0; j < k; j ++ )
{
int c;
cin >> c;
state |= 1 << c - 1;
}
for (int j = 0; j < m; j ++ )
if (state >> j & 1)
col[j].push_back(state);
}
int depth = 0;
while (depth <= m && !dfs(depth, 0)) depth ++ ;
if (depth > m) depth = -1;
cout << depth << endl;
return 0;
}
写法二:01背包+状态压缩
//时间复杂度为O(n * 2^m) 为1e8勉强能过
//状态表示:dp[i][s]为从前i个糖果中选,选出来的糖果种类状态为s的所需最少糖果数量
//阶段划分:类似于01背包问题中,从前i个物品中选
//转移方程:划分依据:对于第i个物品选还是不选
//dp[i][s] = min(dp[i-1][s],dp[i-1][s & (~w[i])] + 1)
//dp[i-1][s]好说 表示不选第i个物品
//关键是dp[i-1][s & (~w[i])],举个例子若现在s为 11011,表示已经选出来的糖果种类为1,2,8,16
//假设第i包糖果的状态为01011,那么他是从10000这个状态转移过来的
//那么s & (~w[i])的目的就是先将w[i]的糖果种类去掉,~w[i]按位取非,在与s相于就行了,实质上就是s & (w[i]在s中的补集)
//边界:dp初始化为INF dp[0][0] = 0;
//目标:dp[n][(1<<m)-1]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110,M = 1 << 20,INF = 0x3f3f3f3f;
int w[N],dp[M],n,m,k;//注意优化成一维不然暴空间
int main(void)
{
cin>>n>>m>>k;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=k;j++)
{
int num;
cin>>num;
w[i] |= (1 << num - 1);//将每一件糖果所包含的糖果种类压缩
}
memset(dp,0x3f,sizeof dp);
dp[0] = 0;
for(int i = 1;i<=n;i++)
for(int s = 0;s < (1 << m);s ++) dp[s] = min(dp[s],dp[s & (~w[i])] + 1);
if(dp[(1<<m)-1] == INF) cout<<-1<<endl;
else cout<<dp[(1<<m) - 1]<<endl;
return 0;
}
写法三:状态压缩
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<string>
#include<map>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, k;
int can[100][20], dp[1 << 20];
int main(void){
cin >> n >> m >> k;
memset(can, 0, sizeof(can));
memset(dp, -1, sizeof(dp));
for (int i = 0; i < n; ++i)
for (int j = 0; j < k; ++j){
cin >> can[i][j];
can[i][j]--;
}
int lim = 1 << m;
dp[0] = 0;
for (int i = 0; i < n; ++i){
int t = 0;
for (int j = 0; j < k; ++j) t |= (1 << can[i][j]);
for (int st = 0; st < lim; ++st){
if (dp[st] == -1) continue;
int nst = st | t;
if (dp[nst] == -1 || dp[nst] > dp[st] + 1) dp[nst] = dp[st] + 1;
// printf("(%d, %d) ", nst, dp[nst]);
}
// printf("\n");
}
cout << dp[lim - 1];
return 0;
}
总结
果然很痛苦!
真的会谢!
最后一题,我真的觉得很妙,但是目前的我是真的干不来哈哈哈哈,所以希望以后我回顾我写的这些博客的时候,我能够常看看!