《算法竞赛进阶指南》数论篇(2)-扩展欧几里得,线性同余方程,中国剩余定理,高次同余方程,矩阵乘法,高斯消元,线性空间,线性基

扩展欧几里得

对 于 任 意 整 数 a , b , 存 在 一 对 整 数 x , y , 满 足 a x + b y = g c d ( a , b ) . 对于任意整数a,b,存在一对整数x,y,满足ax+by=gcd(a,b). a,b,x,yax+by=gcd(a,b).

证明:

在这里插入图片描述
代码如下:

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;
}

依照上面代码可求得特解: x 0 , y 0 x_0,y_0 x0,y0
对于一般方程: a x + b y = c , d = g c d ( a , b ) ax+by=c,d=gcd(a,b) ax+by=c,d=gcd(a,b)如果满足有解的前提是: d ∣ c d|c dc
不满足必然无解。
然后对于 a x + b y = c ax+by=c ax+by=c 的一组特解 x = ( c / d ) ∗ x 0 , y = ( c / d ) ∗ y x=(c/d)*x_0,y=(c/d)*y x=(c/d)x0,y=(c/d)y
事实上:

方程 a x + b y = c ax+by=c ax+by=c的通解可以表示为: x = c d x 0 + k b d , y = c d y 0 − k a d . ( k ∈ Z ) x=\frac{c}{d}x_0+k\frac{b}{d},y=\frac{c}{d}y_0-k\frac{a}{d}.(k\in Z) x=dcx0+kdb,y=dcy0kda.(kZ)

例题Sumdiv

传送门
题意: A B A^B AB所有的约数之和 m o d mod mod 9901 9901 9901 ,数据范围( 1 < = A , B < = 5 ∗ 1 0 7 1<=A,B<=5*10^7 1<=A,B<=5107)
思路:将A分解质因数,可以表示为 A = P 1 c 1 P 2 c 2 P 3 c 3 . . . . P n c n A=P_1^{c_1}P_2^{c_2}P_3^{c_3}....P_n^{c_n} A=P1c1P2c2P3c3....Pncn
所以 A B = P 1 B ∗ c 1 P 2 B ∗ c 2 P 3 B ∗ c 3 . . . . P n B ∗ c n A^B=P_1^{B*c_1}P_2^{B*c_2}P_3^{B*c_3}....P_n^{B*c_n} AB=P1Bc1P2Bc2P3Bc3....PnBcn
约数之和为: ( 1 + P 1 + P 1 2 . . . . + P 1 B ∗ c 1 ) ( 1 + P 2 + P 2 2 . . . . + P 2 B ∗ c 2 ) ( 1 + P n + P n 2 . . . . + P n B ∗ c n ) (1+P_1+P_1^2....+P_1^{B*c_1})(1+P_2+P_2^2....+P_2^{B*c_2})(1+P_n+P_n^2....+P_n^{B*c_n}) (1+P1+P12....+P1Bc1)(1+P2+P22....+P2Bc2)(1+Pn+Pn2....+PnBcn)
然后就是等比数列求和: 1 − P 1 c 1 ∗ B + 1 1 − P 1 ∗ 1 − P 2 c 2 ∗ B + 1 1 − P 2 . . . . . ∗ 1 − P n c n ∗ B + 1 1 − P n \frac{1-P_1^{c_1*B+1}}{1-P_1}*\frac{1-P_2^{c_2*B+1}}{1-P_2}.....*\frac{1-P_n^{c_n*B+1}}{1-P_n} 1P11P1c1B+11P21P2c2B+1.....1Pn1PncnB+1
这里需要注意一点:9901是质数,当 P i − 1 是 9901 的 倍 数 时 逆 元 就 不 适 用 啦 , 需 要 特 判 P_i-1是9901的倍数时逆元就不适用啦,需要特判 Pi19901
因为 ( P i − 1 ) (P_i-1) (Pi1) % m o d = 0 mod=0 mod=0 所以 P i P_i Pi % m o d = 1 , 得 到 P i n mod=1,得到P_i^n mod=1Pin% m o d = 1 mod=1 mod=1
此时 ( 1 + P i + P i 2 . . . . + P i B ∗ c i ) = ( 1 + 1 1 + 1 2 + . . . + 1 B ∗ c i ) = ( B ∗ c i + 1 ) (1+P_i+P_i^2....+P_i^{B*c_i})=(1+1^1+1^2+...+1^{B*c_i})=(B*c_i+1) (1+Pi+Pi2....+PiBci)=(1+11+12+...+1Bci)=(Bci+1)
代码如下:

#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;
}

线性同余方程 : a ∗ x ≡ b ( m o d   m ) a*x\equiv b(mod \ m) axb(mod m)求x

给定 a , b , m , 求 一 个 整 数 x 满 足 a ∗ x ≡ b ( m o d   m ) a,b,m,求一个整数x满足 a*x\equiv b(mod \ m) a,b,mxaxb(mod m),或者无解。
a ∗ x − b 是 m 的 − y 倍 a*x-b是m的-y倍 axbmy,可得到 a ∗ x − b = − y ∗ m a*x-b=-y*m axb=ym
得到 a ∗ x + y ∗ m = b a*x+y*m=b ax+ym=b,这个方程就很熟悉了,就是扩展欧几里得。
求解即可。

例题:同余方程

传送门
题意很简单:求最小的正整数x,满足 a ∗ x ≡ 1 ( m o d   b ) a*x\equiv 1(mod \ b) ax1(mod b)。保证x有解。
根据上面的推论得到 a ∗ x + b ∗ y = 1 a*x+b*y=1 ax+by=1
使用扩展欧几里得求得一个特解 x 0 x_0 x0
然后通解 x = 1 g c d ( a , b ) x 0 + k b g c d ( a , b )   ( k ∈ Z ) x=\frac{1}{gcd(a,b)} x_0+k\frac{b}{gcd(a,b)} \ (k\in Z) x=gcd(a,b)1x0+kgcd(a,b)b (kZ).
必须保证 x x x有解,那么 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1
所以 x = x 0 + k ∗ b   ( k ∈ Z ) x=x_0+k*b \ (k\in Z) x=x0+kb (kZ)
那其实 x x x就是( x 0 x_0 x0%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

在这里插入图片描述
这里的 t i t_i ti一定是有解的,因为 M i t i 与 m i 互 质 M_it_i与m_i互质 Mitimi
通解 x = x 0 + k ∗ m   ( k ∈ Z ) x=x_0+k*m \ (k\in Z) x=x0+km (kZ)
这个解也很能说明
在这里插入图片描述

例题:Strange Way to Express Integers

传送门
题意:

在这里插入图片描述
x ≡ r i ( m o d   a i ) x\equiv r_i(mod \ a_i) xri(mod ai).

思路:

在这里插入图片描述
考虑数学归纳法,来做这道题就好做了, 然后注意一个点,即是求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;
}

高次同余方程 a x ≡ b ( m o d   p ) a^x\equiv b(mod \ p) axb(mod p) 求x

这里我们只展开一类来讲: a x ≡ b ( m o d   p ) a^x\equiv b(mod \ p) axb(mod p)
问题引入:
给定整数 a , b , p a,b,p a,b,p.其中a,p互质,求一个非负整数 x x x,使得 a x ≡ b ( m o d   p ) a^x\equiv b(mod \ p) axb(mod p)
其实是BSGS算法。
可参考超详解证明数论篇(1)-最大公约数,素数筛,欧拉函数,同余,欧拉定理,BSGS

矩阵乘法: f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2) o ( l o g ( n ) ) 下 得 到 f ( n ) o(log(n))下得到f(n) o(log(n))f(n)

在这里插入图片描述

之前写过有关矩阵乘法的有关题解。
详解证明

高斯消元: 线 性 方 程 组 : 求 M 个 N 元 一 次 方 程 组 的 解 线性方程组:求M个N元一次方程组的解 线MN

在这里插入图片描述

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 n n维里,给定 n + 1 n+1 n+1个点的坐标。求所有点都在一个球上的球心坐标。
思路:首先距离:设两个n为空间上的点A, B的坐标为 ( a 1 , a 2 , … , a n ) , ( b 1 , b 2 , … , b n ) (a_1, a_2, …, a_n), (b_1, b_2, …, b_n) (a1,a2,,an),(b1,b2,,bn),则 A B AB AB的距离定义为: d i s t = s q r t ( ( a 1 − b 1 ) 2 + ( a 2 − b 2 ) 2 + … + ( a n − b n ) 2 ) dist = sqrt( (a_1-b_1)^2 + (a_2-b_2)^2 + … + (a_n-b_n)^2 ) dist=sqrt((a1b1)2+(a2b2)2++(anbn)2)
对于任意两个点 i 和 j i和j ij
( x − x i ) 2 + ( y − y i ) 2 + ( z − z i ) 2 + . . . . . . + = ( x − x j ) 2 + ( y − y j ) 2 + ( z − z j ) 2 . . . . (x-x_i)^2 + (y-y_i)^2+(z-z_i)^2+......+=(x-x_j)^2 + (y-y_j)^2+(z-z_j)^2.... (xxi)2+(yyi)2+(zzi)2+......+=(xxj)2+(yyj)2+(zzj)2....
化简得到: 2 ( x j − x i ) x + 2 ( y j − y i ) y + 2 ( z j − z i ) z + . . . + = ( x j 2 − x i 2 ) + ( y j 2 − y i 2 ) + ( z j 2 − z i 2 ) + . . . + . 2(x_j-x_i)x+2(y_j-y_i)y+2(z_j-z_i)z+...+=(x_j^2-x_i^2)+(y_j^2-y_i^2)+(z_j^2-z_i^2)+...+. 2(xjxi)x+2(yjyi)y+2(zjzi)z+...+=(xj2xi2)+(yj2yi2)+(zj2zi2)+...+.
于是可以用高斯消元来解决啦。依据题意这样的球心的是一定存在的,所有不用判断是否有无解

代码如下:

#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个开关达到一个特定的状态。对于任意一个开关,最多只能进行一次开关操作。你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)
思路:这里可以用
在这里插入图片描述
数据范围的 n < 32 n<32 n<32所有可以用 二进制来表示,
然后 a [ N ] a[N] a[N]就是一个增广矩阵。(稍微解释一下 a [ i ] = 1010 1 ( 2 ) a[i]=10101_{(2)} a[i]=10101(2) 意思就是第i个灯的状态可以被第2个开关或第4个开关影响) 对于( a [ i ] a[i] a[i]& 1 1 1就是结果矩阵)

代码如下

#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;
}

线性空间

例题:装备购买

传送门
题意:
脸哥最近在玩一款神奇的游戏,这个游戏里有 n n n 件装备,每件装备有 m m m 个属性,用向量 z i ( a j , . . . . . , a m ) z_i(a_j ,.....,a_m) zi(aj,.....,am) 表示
( 1 < = i < = n ; 1 < = j < = m ) (1 <= i <= n; 1 <= j <= m) (1<=i<=n;1<=j<=m),每个装备需要花费$ c_i$,现在脸哥想买一些装备,但是脸哥很穷,所以总是盘算着
怎样才能花尽量少的钱买尽量多的装备。对于脸哥来说,如果一件装备的属性能用购买的其他装备组合出(也就是
说脸哥可以利用手上的这些装备组合出这件装备的效果),那么这件装备就没有买的必要了。严格的定义是,如果
脸哥买了 zi1,…zip这 p 件装备,那么对于任意待决定的 zh,不存在 b 1 , . . . . , b p b1,....,bp b1,....,bp 使得 b 1 z i 1 + . . . + b p z i p = z h ( b 是 实 数 ) b1zi1 + ... + bpzi p = zh(b 是实数) b1zi1+...+bpzip=zhb,那么脸哥就会买 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,那么 2 t 2^t 2t就是该异或矩阵得到的所有可能,当然还有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;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值