本文已参与「新人创作礼」活动,一起开启掘金创作之路。
@[TOC]
扩展欧几里得
证明:
代码如下:
int exgcd(int a,int b,int &x,int &y){
// a>b a*x+b*y=gcd(a,b);
if(b==0){
x=1;
y=0;
return a;
}
int d=exgcd(b,a%b,x,y);
int z=x;
x=y;
y=z-(a/b)*y;
return d;
}
依照上面代码可求得特解:。 对于一般方程:如果满足有解的前提是: 不满足必然无解。 然后对于 的一组特解。 事实上:
方程的通解可以表示为:
例题Sumdiv
传送门 题意:所有的约数之和 ,数据范围() 思路:将A分解质因数,可以表示为 所以 约数之和为: 然后就是等比数列求和: 这里需要注意一点:9901是质数,当 因为 % 所以 %% 此时 代码如下:
#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=9901;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll qpow(ll a,ll x){
ll ans=1;
while(x){
if(x&1){
ans=(ans*a)%P;
}
a=(a*a)%P;
x>>=1;
}
return ans;
}
ll n;
void solve(){
ll A,B;
A=read();
B=read();
ll sum=1;
for(int i=2;i*i<=A;i++){
if(A%i==0){
ll cnt=0;
while(A%i==0){
cnt++;
A/=i;
}
// printf("P:%d cnt:%d\n",i,cnt);
if((i-1)%P==0){
sum=(sum*(cnt*B+1))%P;
}else{
ll ans=(qpow(i,cnt*B+1)-1+P)%P*qpow(i-1,P-2)%P;
sum=(sum*ans)%P;
}
}
}
if(A>1){
if((A-1)%P==0){
sum=(sum*(B+1))%P;
}else{
ll ans=(qpow(A,B+1)-1+P) %P *qpow(A-1,P-2)%P;
sum=(sum*ans)%P;
}
}
printf("%d\n",sum);
return ;
}
int main (){
// freopen("in.txt","r",stdin)
// freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
线性同余方程 :求x
给定,或者无解。 令,可得到 得到,这个方程就很熟悉了,就是扩展欧几里得。 求解即可。
例题:同余方程
传送门 题意很简单:求最小的正整数x,满足。保证x有解。 根据上面的推论得到 使用扩展欧几里得求得一个特解。 然后通解. 必须保证有解,那么 所以 那其实就是(%b+b)%b; 代码如下:
#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=9901;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll qpow(ll a,ll x){
ll ans=1;
while(x){
if(x&1){
ans=(ans*a)%P;
}
a=(a*a)%P;
x>>=1;
}
return ans;
}
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 z=x;
x=y;
y=z-(a/b)*y;
return d;
}
void solve(){
ll a,b;
ll x,y;
a=read();
b=read();
ll d=exgcd(a,b,x,y);
ll sum=(x%b+b)%b;
cout<<sum<<endl;
return ;
}
ll n;
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;
// T=read();
// while(1)
solve();
getchar();
getchar();
return 0;
}
中国剩余定理 :一个解满足多个同余方程组。求x
这里的一定是有解的,因为。 通解 这个解也很能说明
例题:Strange Way to Express Integers
传送门 题意:
.
思路:
考虑数学归纳法,来做这道题就好做了, 然后注意一个点,即是求t,这个求t值就是用到扩展欧几里得算法来实现他,然后我们要求得是的最小的他t.
代码如下:
// submitted by HNUST26
#include<iostream>
#define ll __int64
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=9901;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll qpow(ll a,ll x){
ll ans=1;
while(x){
if(x&1){
ans=(ans*a)%P;
}
a=(a*a)%P;
x>>=1;
}
return ans;
}
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 z=x;
x=y;
y=z-(a/b)*y;
return d;
}
int n;
void solve(){
while(scanf("%d",&n)!=EOF){
ll x0;
ll a1,r1,a2,r2;
ll l,d;
ll x,y;
scanf("%I64d %I64d",&a1,&r1);
l=a1; //最小公倍数
x0=r1; // 当前的最小解
int flag=0;
for(int i=1;i<=n-1;i++){
scanf("%I64d %I64d",&a2,&r2);
d=exgcd(l,a2,x,y);
ll c=r2-x0;
if(c%d){
flag=1;
continue;
}
ll t=a2/d;
// 求得最小的ans
ll ans=((c/d*x)%t+t)%t;
//叠加上去
x0=x0+l*ans;
l=(l*a2)/gcd(l,a2);
}
if(flag){
cout<<"-1"<<endl;
}else{
printf("%I64d\n",x0);
}
}
return ;
}
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;
// T=read();
// while(1)
solve();
getchar();
getchar();
return 0;
}
高次同余方程 求x
这里我们只展开一类来讲: 问题引入: 给定整数.其中a,p互质,求一个非负整数,使得 其实是BSGS算法。 可参考超详解证明数论篇(1)-最大公约数,素数筛,欧拉函数,同余,欧拉定理,BSGS
矩阵乘法: 在
之前写过有关矩阵乘法的有关题解。 详解证明
高斯消元:
double a[15][15];
// 起始下标1 增广矩阵
bool guass(int row,int col){
for(int i=1;i<=row;i++){
int k=i;
// 获取第i列的最大值的行位置。
for(int j=i+1;j<=row;j++){
if(fabs(a[j][i])>fabs(a[k][i])){
k=j;
}
}
if(k!=i){
// 第i列的最大值的行与i行互换。
for(int j=1;j<=col;j++){
swap(a[i][j],a[k][j]);
}
}
for(int j=1;j<=row;j++){
if(i==j) continue;
double temp=a[j][i]/a[i][i];
for(int k=1;k<=col;k++){
a[j][k]-=a[i][k]*temp;
}
}
}
return true;
}
例题
球形空间产生器sphere
题意:在一个维里,给定个点的坐标。求所有点都在一个球上的球心坐标。 思路:首先距离:设两个n为空间上的点A, B的坐标为,则的距离定义为: 对于任意两个点有 化简得到: 于是可以用高斯消元来解决啦。依据题意这样的球心的是一定存在的,所有不用判断是否有无解
代码如下:
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
double a[15][15];
bool guass(int row,int col){
for(int i=1;i<=row;i++){
int k=i;
for(int j=i+1;j<=row;j++){
if(fabs(a[j][i])>fabs(a[k][i])){
k=j;
}
}
if(k!=i){
for(int j=1;j<=col;j++){
swap(a[i][j],a[k][j]);
}
}
for(int j=1;j<=row;j++){
if(i==j) continue;
double temp=a[j][i]/a[i][i];
for(int k=1;k<=col;k++){
a[j][k]-=a[i][k]*temp;
}
}
}
return true;
}
ll n,m;
double x[20];
double y[20];
void solve(){
n=read();
double x1,x2,y1,y2;
for(int i=1;i<=n;i++) cin>>x[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>y[j];
}
for(int j=1;j<=n;j++){
a[i][j]=2*(y[j]-x[j]);
a[i][n+1]+=(y[j]*y[j]-x[j]*x[j]);
}
for(int j=1;j<=n;j++){
swap(x[j],y[j]);
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n+1;j++){
// printf("%.2f ",a[i][j]);
// }
// printf("\n");
// }
guass(n,n+1);
for(int i=1;i<n;i++){
printf("%.3f ",a[i][n+1]/a[i][i]);
}
printf("%.3f",a[n][n+1]/a[n][n]);
}
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
开关问题
题意:有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态。对于任意一个开关,最多只能进行一次开关操作。你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序) 思路:这里可以用
数据范围的所有可以用 二进制来表示, 然后就是一个增广矩阵。(稍微解释一下 意思就是第i个灯的状态可以被第2个开关或第4个开关影响) 对于(&就是结果矩阵)
代码如下
#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll n,m;
int a[100];
void solve(){
int n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=n;i++){
a[i]^=read();
a[i]|=(1<<i);
}
int x,y;
while(~scanf("%d %d",&x,&y) and x and y){
a[y]|=(1<<x);
}
int ans=1;
for(int i=1;i<=n;i++){
//最左边的1
for(int j=i;j<=n;j++){
if(a[j]>a[i]) swap(a[j],a[i]);
}
if(a[i]==1){
// 000001 无解的情况
ans=0;
break;
}
if(a[i]==0){
// 下面的行全是0 此时主元个数 i-1 自由元个数n-i+1;
ans=(1<<(n-i+1));
break;
}
//找到最左边的1,只执行一次
for(int j=n;j>=1;j--){
if((a[i]>>j)&1){
// 行数的遍历
for(int k=1;k<=n;k++){
if(k!=i and ( (a[k]>> j) & 1 ) ) a[k]^=a[i];
}
break;
}
}
}
if(ans==0){
printf("Oh,it's impossible~!!\n");
}else{
printf("%d\n",ans);
}
return ;
}
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T=read();
while(T--)
solve();
getchar();
getchar();
return 0;
}
线性空间
例题:装备购买
传送门 题意: 脸哥最近在玩一款神奇的游戏,这个游戏里有 件装备,每件装备有 个属性,用向量 表示 ,每个装备需要花费,现在脸哥想买一些装备,但是脸哥很穷,所以总是盘算着 怎样才能花尽量少的钱买尽量多的装备。对于脸哥来说,如果一件装备的属性能用购买的其他装备组合出(也就是 说脸哥可以利用手上的这些装备组合出这件装备的效果),那么这件装备就没有买的必要了。严格的定义是,如果 脸哥买了 zi1,.....zip这 p 件装备,那么对于任意待决定的 zh,不存在 使得 ,那么脸哥就会买 zh,否则 zh 对脸哥就是无用的了,自然不必购买。举个例子,z1 =(1; 2; 3);z2 =(3; 4; 5);zh =(2; 3; 4),b1 =1/2,b2 =1/2,就有 b1z1 + b2z2 = zh,那么如果脸哥买了 z1 和 z2 就不会再买 zh 了。脸哥想要在买下最多数量的装备的情况下花最少的钱,你能帮他算一下吗? 思路:求矩阵秩,然后在采取策略面前,使用优先选择价格最小的。
求矩阵秩模板:
long double a[N][N];
int p[N];
void Guass(int n,int m){
int cnt=0;
int sum=0;
rep(i,1,n){
rep(j,1,m){
if(abs(a[i][j])>eps){
if(!p[j]){
p[j]=i;
cnt++;
break;
}else{
ld temp=a[i][j]/a[p[j]][j];
rep(k,j,m){
a[i][k]-=temp*a[p[j]][k];
}
}
}
}
cout<<cnt<<endl;//秩
return ;
}
AC代码如下:
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
const double eps=1e-6;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
struct node{
ld col[501];
int cost;
}row[501];
bool cmp(node a,node b){
return a.cost<b.cost;
}
int p[501];
void Guass(int n,int m){
int cnt=0;
int sum=0;
rep(i,1,n){
rep(j,1,m){
if(abs(row[i].col[j])>eps){
if(!p[j]){ //第j列 在p[j]行
p[j]=i;
cnt++;
sum+=row[i].cost;
break;
}else{
ld temp=row[i].col[j]/row[p[j]].col[j];
rep(k,j,m){
row[i].col[k]-=temp*row[p[j]].col[k];
}
}
}
}
// rep(j,1,n){
// rep(k,1,m){
// cout<<row[j].col[k]<<" ";
// } cout<<endl;
// } cout<<endl;
}
cout<<cnt<<" "<<sum<<endl;
return ;
}
void solve(){
int n,m;
n=read();m=read();
rep(i,1,n){
rep(j,1,m){
scanf("%Lf",&row[i].col[j]);
}
}
rep(i,1,n){
scanf("%d",&row[i].cost);
}
sort(row+1,row+1+n,cmp);
Guass(n,m);
return ;
}
int main (){
solve();
getchar();
getchar();
return 0;
}
例题:XOR
题意:
思路: 异或操作也是满足线性无关。
对异或矩阵进行变换,化成阶梯型矩阵就好了。 那么他的秩为t,那么就是该异或矩阵得到的所有可能,当然还有0的情况,需要特判。
ACcode
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e5+10;
const ll P=1e9+7;
ull read(){
ull s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll n,m;
ull a[N];
void solve(int T){
n=read();
rep(i,1,n) scanf("%I64u",&a[i]);
int t=n;
bool zero=0;
rep(i,1,n){
rep(j,i+1,n){
if(a[j]>a[i]) swap(a[i],a[j]);
}
if(a[i]==0){
zero=1;
t=i-1;
break;
}
for(int k=63;k>=0;k--){
if(a[i]>> k&1){
rep(j,1,n){
if(i!=j and a[j] >> k & 1){
a[j]^=a[i];
}
}
break;
}
}
}
ull maxn=1ull <<t;
printf("Case #%d:\n",T);
ll q;
q=read();
while(q--){
ull k,ans=0;;
scanf("%I64u",&k);
if(zero) k--;
if(k>=maxn) printf("-1\n");
else{
// for(int i=t-1;i>=0;i--){
// if(k >> i & 1) ans^=a[t-i];
// }
int ind=t;
while(k){
if(k%2==1){
ans^=a[ind];
}
k/=2;
ind--;
}
printf("%I64u\n",ans);
}
}
return ;
}
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;
T=read();
rep(i,1,T)
solve(i);
getchar();
getchar();
return 0;
}
线性基
参考博客Yveh:[学习笔记]线性基
板子: 插入,合并,查询,最小最大,第k小
// 线性基 板子
struct L_B{
// d[i] 二进制最高位是第i位. p[i]就是第2^i个小的数是p[i]
long long d[61],p[61]; // 顶值1e18
int cnt; // p数组有效个数
L_B()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
//插入
bool insert(long long val)
{
for (int i=60;i>=0;i--)
if (val&(1ll<<i))
{
if (!d[i])
{
d[i]=val;
break;
}
val^=d[i];
}
return val>0;
}
//得到最大值
long long query_max()
{
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
//得到最小值
long long query_min()
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
// 题目中问到第k小的数是多少时,使用p数组
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1ll<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
// 找第几小的值
long long kthquery(long long k)
{
int ret=0;
if (k>=(1ll<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1ll<<i))
ret^=p[i];
return ret;
}
};