数论基础
快速乘(转载)
快速乘法的思想和快速幂的思想一样,快速幂是求一个数的高次幂,快速乘法是求两个数相乘,什么时候才用得到快速乘法呢,当两个数相乘可能超过long long (大约10的18次方)范围的时候用,因为在加法运算的时候不会超,而且可以直接取模,这样就会保证数据超不了了。
LL fast_multi(LL m, LL n, LL mod)//快速乘法
{
LL ans = 0;//注意初始化是0,不是1
while (n)
{
if (n & 1)
ans += m;
m = (m + m) % mod;//和快速幂一样,只不过这里是加
m %= mod;//取模,不要超出范围
ans %= mod;
n >>= 1;
}
return ans;
}
在快速幂中使用快速乘
LL fast_pow(LL a, LL n, LL mod)//快速幂
{
LL ans = 1;
while (n)
{
if (n & 1)
ans = fast_multi(ans, a, mod);//不能直接乘
a = fast_multi(a, a, mod);
ans %= mod;
a %= mod;
n >>= 1;
}
return ans;
}
素数线性筛法
如果我们知道一个数p是质数,那么我们可以确定k*p都不是质数,k=2,3…,相对的,如果一个数p满足小于他的数都没有指出他不是质数,那么他是个质数。
for(int i = 2; i < 1000; i++)
{
if(!f[i])
{
prime[cnt++] = i;
for(int j = 2; i*j < 1000; j++) f[i*j] = 1;
}
}
SG函数(转载)
给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。
任何一个Impartial Combinatorial Games都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下 面我们就在有向无环图的顶点上定义Sprague-Grundy函数。
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Grundy函数g如下:g(x)=mex{ g(y) | y是x的后继 },这里的g(x)即sg[x]
例如:取石子问题,有1堆n个的石子,每次只能取{1,3,4}个石子,先取完石子者胜利,那么各个数的SG值为多少?
sg[0]=0,f[]={1,3,4},
x=1时,可以取走1-f{1}个石子,剩余{0}个,mex{sg[0]}={0},故sg[1]=1;
x=2时,可以取走2-f{1}个石子,剩余{1}个,mex{sg[1]}={1},故sg[2]=0;
x=3时,可以取走3-f{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}={0,0},故sg[3]=1;
x=4时,可以取走4-f{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}={1,1,0},故sg[4]=2;
x=5时,可以取走5-f{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}={2,0,1},故sg[5]=3;
以此类推…..
x 0 1 2 3 4 5 6 7 8….
sg[x] 0 1 0 1 2 3 2 0 1….
计算从1-n范围内的SG值。
f(存储可以走的步数,f[0]表示可以有多少种走法)
f[]需要从小到大排序
1.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
2.可选步数为任意步,SG(x) = x;
3.可选步数为一系列不连续的数,用GetSG()计算
模板1如下(SG打表):
//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//hash[]:mex{}
int f[N],sg[N],hash[N];
void getSG(int n)
{
int i,j;
memset(sg,0,sizeof(sg));
for(i=1;i<=n;i++)
{
memset(hash,0,sizeof(hash));
for(j=1;f[j]<=i;j++)
hash[sg[i-f[j]]]=1;
for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数
{
if(hash[j]==0)
{
sg[i]=j;
break;
}
}
}
}
模板2如下(dfs):
//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[110],sg[10010],n;
int SG_dfs(int x)
{
int i;
if(sg[x]!=-1)
return sg[x];
bool vis[110];
memset(vis,0,sizeof(vis));
for(i=0;i<n;i++)
{
if(x>=s[i])
{
SG_dfs(x-s[i]);
vis[sg[x-s[i]]]=1;
}
}
int e;
for(i=0;;i++)
if(!vis[i])
{
e=i;
break;
}
return sg[x]=e;
}
组合数学
排列
从N个元素中,取出R个元素按顺序排成一列,称为从N中取出R的排列,他的方案数表示为P(N,R);
P(N,R) = N!/(N-R)!
组合
从N个元素中,任意取出R个元素为一组倘若不考虑取出的顺序,则称为从N中取出R的组合,他的方案数表示为C(N,R);
C(N,R) = P(N,R)/R!
LUCAS定理
C(n,m)%p = C(n/p,m/p)*C(n%p,m%p)%p;
当p比较小的时候,通过LUCAS定理能够明显减少求C(n,m)的时间复杂度。
欧几里得算法
也就是辗转相除法,用于求两个非负整数的最大公约数
gcd(a,b) = gcd(b,a mod b)
扩展欧几里得算法用于解对于给定的a,b,方程ax+by=gcd(a,b)=d的一组解(x,y)。
根据裴蜀定理:
a/d*x+b/d*y=1有解,且有无数组解,将两边同乘d就可以得到上式。
我们回想我们求gcd(a,b)的过程:当发现a=gcd,b=0的时候,停止计算,显然此时x=1,y=0,即可满足上述方程。
于是我们得到了扩展欧几里得算法的递归做法:
int exGcd(int a , int b, int &x, int &y)
{
if(b == 0)
{
x = 1;
y = 0;
return a;
}
int d = exGcd(b, a%b,y,x);
y-=a/b*x;
return d;
}
所求得的x,y,分别是a%b,b%a的乘法逆元。
中国剩余定理(孙子定理)(转载)
韩信带1500名兵士打仗,战死四五百人,站3人一排,多出2人;站5人一排,多出4人;站7人一排,多出6人。韩信马上说出人数:1049。
显然这个问题我们可以暴力枚举解决,但是如果数字很大,限制条件很多呢?
设正整数m1,m2,…,mk两两互素,则同余方程组
有整数解,并且在模 M = m1×m2×…×mk 下的解是唯一的,解为
其中 Mi = M/mi , Mi^-1 为 Mi % mi 的逆元
int CRT(int a[],int m[],int n)
{
int M = 1;
int ans = 0;
for(int i=1; i<=n; i++)
M *= m[i];
for(int i=1; i<=n; i++)
{
int x, y;
int Mi = M / m[i];
exGcd(Mi, m[i], x, y);
ans = (ans + Mi * x * a[i]) % M;
}
if(ans < 0) ans += M;
return ans;
}
欧拉函数
欧拉函数用希腊字母φ表示,φ(N)表示N的欧拉函数.
对φ(N)的值,我们可以通俗地理解为小于N且与N互质的数的个数.
φ(N)=N×(1-1/p1)×(1-1/p2)×…×(1-1/pn). 其中,p1..pn是x的所有质因数,N不为0。
欧拉函数是积性函数,但不是完全积性函数,即φ(mn)=φ(n)*φ(m)只在(n,m)=1时成立.
O(n)复杂度的同时求素数+欧拉函数的模板:
for(phi[1] = 1,int i = 2; i <= Maxn ; i++)
{
if(!f[i]) prime[top++] = i,phi[i] = i-1;
for(int j = 0; j < top && i*prime[j] <= Maxn; j++)
{
f[i*prime[j]] = 1;
if(i%prime[j])
phi[i*prime[j]] = phi[i]*(prime[j]-1);
else
{
phi[i*prime[j]] = phi[i]*prime[j];
break;
}
}
}
//今日代码
Fzu 7月25日专题训练
D - Biorhythms
(x 同余 a%23 同余 b%28 同余 c%33 中国剩余定理. 感觉用枚举应该也行..)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int arr[3];
int m[3];
void exGcd(int a, int b, int &x ,int &y)
{
if(b == 0)
{
x = 1;
y = 0;
return ;
}
exGcd(b,a%b,y,x);
y-=a/b*x;
}
int crt(int a[],int m[],int n)
{
int M = 1;
int ans = 0;
for(int i = 0; i < n; i++)
M *= m[i];
for(int i = 0; i < n; i++)
{
int x,y;
int Mi = M / m[i];
exGcd(Mi,m[i],x,y);
ans = (ans + Mi*x*a[i]) % M;
}
if(ans < 0) ans+=M;
return ans;
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int k = 21252;
m[0] = 23;
m[1] = 28;
m[2] = 33;
int a,b,c,d;
int cnt = 0;
while(cin >> a >> b >> c >> d && !(a==-1&&b==-1&&c==-1&&d==-1))
{
cnt++;
arr[0] = a;
arr[1] = b;
arr[2] = c;
int n = crt(arr,m,3)-d;
if(n <= d) n+=k;
cout << "Case " << cnt << ": the next triple peak occurs in "<< n <<" days."<< endl;
}
return 0;
}
E - Benefit
(求能使得a和b的最小公倍数为c的最小b。当c不能除断a时必然不存在。要满足条件,a与b的公约数为1。gcd(c/a,a)若不为1,则从a中把其扣除,直到为1,此时c/a即为答案)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int gcd(int a,int b)
{
if(a < b) return gcd(b,a);
else
return a%b==0?b:gcd(b,a%b);
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int t;
cin >> t;
while(t--)
{
int a, c;
cin >> a >> c;
if(c%a)
{
cout << "NO SOLUTION" << endl;
continue;
}
int g = 1;
while(1)
{
a = a/g;
g = gcd(c/a,a);
if(g == 1) break;
}
cout << c/a << endl;
}
return 0;
}
F - 青蛙的约会
(两只青蛙分别从x和y出发,跳一个长度为L的环,青蛙A一次能跳m米,青蛙B一次能跳n米,问要几次跳跃后能见面。扩展欧几里得定律,两只青蛙跳的次数一致,(?m+x)%L = (?n+y)%L。
设过s步后两青蛙相遇,则必满足等式: (x+m×s)-(y+n×s)=k×L(k=0,1,2….)
变形得 (n-m)×s+k×L=x-y
令n-m=a,k=b,x-y=c,即 a×s+b×L=c
只要上式存在整数解,则两青蛙能相遇,且步数为s,否则不能)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
void exgcd(LL a,LL b,LL &d,LL &x,LL &y)
{
if(b == 0)
{
d = a;
x = 1;
y = 0;
return ;
}
exgcd(b,a%b,d,y,x);
y-=a/b*x;
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
LL x,y,m,n,L;
cin >> x >> y >> m >> n >> L;
LL d,xx,yy;
exgcd(n-m,L,d,xx,yy);
int b = L/d;
int t = (x-y)/d;
if((x-y)%d)
cout << "Impossible" << endl;
else
{
xx = xx*t;
xx = (xx%b + b) % b;
cout << xx << endl;
}
return 0;
}
H - How do you add?
(n=1 k=k时为k种,n=n,k=1时为1种。一般的情况是,arr[n][k]可视作n-1,k不变和k-1,n不变两种情况的和(由前一种的每个情况+1,后一种的每个情况+0得到)。 另一种解法是,看做C(n+k-1,k-1)的组合数,如果没有可加0的情况,相当于隔板问题,n个物品隔成k块,有加0的情况则再多k个0一起隔。)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int arr[110][110];//n由k个数组成
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
for(int i = 1; i < 110; i++)
{
arr[1][i] = i;
arr[i][1] = 1;
}
for(int i = 2; i < 110; i++)
for(int j = 2; j < 110; j++)
arr[i][j] = (arr[i-1][j] + arr[i][j-1])%1000000;
int n,k;
while(cin >> n >> k && !(n == 0 && k == 0))
{
cout << arr[n][k] << endl;
}
return 0;
}
I - Matches Game
(有m堆火柴,每堆火柴的根数给定,每次可以从任意堆中拿取任意根火柴,最后没火柴拿的一方输,问先手能否获胜。1堆能一次拿光于是获胜,2堆若相同数量,则无论先手怎么拿后手都拿另一堆相同数目的,于是先手必输,但如果不同数量,先手可以在第一次先拿成两堆相同的,然后把鸡肋丢给对方,则先手比胜。3堆情况就更复杂。但大致可以看出有一个【多出】的问题,如果这些堆火柴有【多出】,那就可以把【多出】拿走然后鸡肋丢给对方,先手必赢。如果恰好持平没有【多出】,则先手必输。(火柴数全部异或判持平还是多出))
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int n;
while(cin >> n)
{
int ans =0;
for (int i =0; i < n; i++)
{
int a;
cin >> a;
ans = ans ^ a;
}
if (ans) printf("Yes\n");
else printf("No\n");
}
return 0;
}
J - A Funny Game
(由于能把环分为两条链的决定权在Bob手上,所以当n>2的情况下Bob总能构造两条对称链,这样无论Alice怎样拿Bob都在对称链上拿相同的即可。)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int n;
while(cin >> n && n!=0)
{
if(n == 1 || n == 2) cout << "Alice"<< endl;
else cout << "Bob" << endl;
}
return 0;
}