概念
思想
模板
bool is_prime(int n){
if(n<2) return 0; //若小于2直接返回false
for(int i=2;i<=n/i;i++){ //优化为sqrt(n)
if(n%i==0) return 0;
}
return 1;
}
例题 866. 试除法判定质数
描述
给定 n 个正整数 ai,判定每个数是否是质数。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式 共 n 行,其中第 i 行输出第 i 个正整数 ai 是否为质数,是则输出 Yes,否则输出 No。
数据范围 1≤n≤100, 1≤ai≤2^31−1 输入样例:
2
2
6
输出样例:
Yes
No
代码
#include <bits/stdc++.h>
using namespace std;
bool is_prime(int n){
if(n<2) return 0;
for(int i=2;i<=n/i;i++){
if(n%i==0) return 0;
}
return 1;
}
int main(){
int t;
cin>>t;
while(t--){
int x;
cin>>x;
if(is_prime(x)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
概念
思想
模板
map<int,int> primes; //存储质因子底数和其指数的映射
void get_div(int n){
primes.clear(); //清空数据
for(int i=2;i<=n/i;i++){ //从2开始枚举质因子
if(n%i==0){ //当其为质因子时
while(n%i==0){
primes[i]++; //指数增加
n/=i;
}
}
}
if(n>1) primes[n]++; //剩余的数大于1则为最后的质因子
}
例题 867. 分解质因数
描述
给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式 对于每个正整数 ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。
每个正整数的质因数全部输出完毕后,输出一个空行。
数据范围 1≤n≤100, 2≤ai≤2×109 输入样例:
2
6
8
输出样例:
2 1
3 1
2 3
代码
#include <bits/stdc++.h>
using namespace std;
map<int,int> primes;
void get_div(int n){
primes.clear();
for(int i=2;i<=n/i;i++){
if(n%i==0){
while(n%i==0){
primes[i]++;
n/=i;
}
}
}
if(n>1) primes[n]++;
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
get_div(x);
for(auto &p : primes) cout<<p.first<<" "<<p.second<<endl;
cout<<endl;
}
return 0;
}
思想
模板
int cnt; //记录质数个数
int primes[N]; //存储当前筛选出的质数
bool vis[N]; //标记是否被筛掉
void get_primes(int n){
for(int i=2;i<=n;i++){ //外层从2~n迭代
if(!vis[i]) primes[cnt++]=i; //没有被筛掉说明是质数,记录到primes[N]中
for(int j=0;primes[j]<=n/i;j++){ //将1~n范围内质数primes[j]的i倍的合数筛掉
vis[primes[j]*i]=1; //用最小质因子primes[j]筛掉合数
if(i%primes[j]==0) break;
//i%primes[j]!=0 : 说明primes[j] < i的所有质因子,故primes[j]是primes[j]*i的最小质因子
//i%primes[j]==0 : 说明从小到大枚举到此时的primes[j],一定是i的最小质因子
}
}
}
例题 868. 筛质数
描述
给定一个正整数 n,请你求出 1∼n 中质数的个数。
输入格式 共一行,包含整数 n。
输出格式 共一行,包含一个整数,表示 1∼n 中质数的个数。
数据范围 1≤n≤106 输入样例:
8
输出样例:
4
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+3;
int cnt; //记录质数个数
int primes[N]; //存储当前筛选出的质数
bool vis[N]; //标记是否被筛掉
void get_primes(int n){
for(int i=2;i<=n;i++){ //外层从2~n迭代
if(!vis[i]) primes[cnt++]=i; //没有被筛掉说明是质数,记录到primes[N]中
for(int j=0;primes[j]<=n/i;j++){ //将1~n范围内质数primes[j]的i倍的合数筛掉
vis[primes[j]*i]=1; //用最小质因子primes[j]筛掉合数
if(i%primes[j]==0) break;
//i%primes[j]!=0 : 说明primes[j] < i的所有质因子,故primes[j]是primes[j]*i的最小质因子
//i%primes[j]==0 : 说明从小到大枚举到此时的primes[j],一定是i的最小质因子
}
}
}
int main(){
int n;
cin>>n;
get_primes(n);
cout<<cnt<<endl;
return 0;
}
概念
思想
模板
const int N=1e6+3;
int res[N]; //存储约数
int cnt; //记录数量
void get_div(int n){
cnt=0; //初始化
for(int i=1;i<=n/i;i++){ //从1开始枚举
if(n%i==0){
res[cnt++]=i; //将i作为约数
if(i!=n/i) res[cnt++]=n/i; //将n/i作为约数
}
}
sort(res,res+cnt); //将约数从小到大排序
}
例题 869. 试除法求约数
描述
给定 n 个正整数 ai,对于每个整数 ai,请你按照从小到大的顺序输出它的所有约数。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式 输出共 n 行,其中第 i 行输出第 i 个整数 ai 的所有约数。
数据范围 1≤n≤100, 2≤ai≤2×109 输入样例:
2
6
8
输出样例:
1 2 3 6
1 2 4 8
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+3;
int res[N];
int cnt;
void get_div(int n){
cnt=0;
for(int i=1;i<=n/i;i++){
if(n%i==0){
res[cnt++]=i;
if(i!=n/i) res[cnt++]=n/i;
}
}
sort(res,res+cnt);
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
get_div(x);
for(int i=0;i<cnt;i++) cout<<res[i]<<" ";
cout<<endl;
}
return 0;
}
思想
模板例题 870. 约数个数
描述
给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式 输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7 取模。
数据范围 1≤n≤100, 1≤ai≤2×109 输入样例:
3
2
6
8
输出样例:
12
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
LL cnt=1;
map<int,int> primes; //存储质因子底数和其指数的映射
void get_div(int n){
for(int i=2;i<=n/i;i++){ //从2开始枚举质因子
if(n%i==0){ //当其为质因子时
while(n%i==0){
primes[i]++; //指数增加
n/=i;
}
}
}
if(n>1) primes[n]++; //剩余的数大于1则为最后的质因子
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
get_div(x);
}
for(auto &p : primes) cnt=cnt*(p.second+1)%mod; //核心:N的约数个数为(a1+1)*(a2+1)*(a3+1)*…*(ai+1)
cout<<cnt<<endl;
return 0;
}
思想
模板例题 871. 约数之和
描述
给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式 输出一个整数,表示所给正整数的乘积的约数之和,答案需对 109+7 取模。
数据范围 1≤n≤100, 1≤ai≤2×109 输入样例:
3
2
6
8
输出样例:
252
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
LL res=1;
map<int,int> primes; //存储质因子底数和其指数的映射
void get_div(int n){
for(int i=2;i<=n/i;i++){ //从2开始枚举质因子
if(n%i==0){ //当其为质因子时
while(n%i==0){
primes[i]++; //指数增加
n/=i;
}
}
}
if(n>1) primes[n]++; //剩余的数大于1则为最后的质因子
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
get_div(x);
}
for(auto &p : primes){
LL t=1;
int a=p.first,b=p.second;
while(b--){
t=(t*a+1)%mod; //核心:求出 p0一直加到p的k的次方的和
}
res=res*t%mod;
}
cout<<res<<endl;
return 0;
}
概念
思想
模板
//最大公约数
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
//最小公倍数
int lcm(int a,int b){
return a/gcd(a,b)*b;
}
概念
思想
模板
typedef long long LL;
LL phi(LL n){
LL res=n;
for(int i=2;i<=n/i;i++){
if(n%i==0){
res=res/i*(i-1); //(1-1/i)转换为(i-1)/i
while(n%i==0) n/=i;
}
}
if(n>1) res=res/n*(n-1);
return res;
}
例题 873. 欧拉函数
描述
给定 n 个正整数 ai,请你求出每个数的欧拉函数。
欧拉函数的定义 1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。 若在算数基本定理中,N=pa11pa22…pamm,则: ϕ(N) = N×p1−1p1×p2−1p2×…×pm−1pm
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式 输出共 n 行,每行输出一个正整数 ai 的欧拉函数。
数据范围 1≤n≤100, 1≤ai≤2×109 输入样例:
3
3
6
8
输出样例:
2
2
4
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL phi(LL n){
LL res=n;
for(int i=2;i<=n/i;i++){
if(n%i==0){
res=res/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) res=res/n*(n-1);
return res;
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
cout<<phi(x)<<endl;
}
return 0;
}
思想
模板
int primes[N]; //存储当前筛选出的质数
bool vis[N]; //标记是否被筛掉
int phi[N]; //记录欧拉函数的值
int cnt; //记录质数个数
void get_phi(int n){
phi[1]=1; //特别的,phi[1]=1
for(int i=2;i<=n;i++){
if(!vis[i]){
primes[cnt++]=i; //没有被筛掉说明是质数,记录到primes[N]中
phi[i]=i-1; //质数的欧拉函数的情况
}
for(int j=0;primes[j]<=n/i;j++){ //将1~n范围内质数primes[j]的i倍的合数筛掉
vis[primes[j]*i]=1;
if(i%primes[j]==0){ //用最小质因子primes[j]筛掉合数
phi[primes[j]*i]=primes[j]*phi[i]; //包含(1-1/primes[j])的情况
break;
}
else phi[primes[j]*i]=(primes[j]-1)*phi[i]; //不包含(1-1/primes[j])的情况
}
}
}
例题 874. 筛法求欧拉函数
描述
给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。
输入格式 共一行,包含一个整数 n。
输出格式 共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。
数据范围 1≤n≤106 输入样例:
6
输出样例:
12
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+3;
typedef long long LL;
int primes[N];
bool vis[N];
int phi[N];
int cnt;
void get_phi(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]){
primes[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;primes[j]<=n/i;j++){
vis[primes[j]*i]=1;
if(i%primes[j]==0){
phi[primes[j]*i]=primes[j]*phi[i];
break;
}
else phi[primes[j]*i]=(primes[j]-1)*phi[i];
}
}
}
int main(){
int n;
cin>>n;
get_phi(n);
LL res=0;
for(int i=1;i<=n;i++){
res+=phi[i];
}
cout<<res<<endl;
return 0;
}
概念
思想
进行按位>>
操作后,更新
模板
typedef long long LL;
LL qmi(LL a,LL k,LL p){ //计算 a^k % p 的结果
LL res=1; //记录累乘结果
while(k){
if(k&1) res=res*a%p; //k&1得到当前位,若为1则累乘a^pi
a=a*a%p; //更新a^pi
k>>=1; //右移1位
}
return res;
}
概念
注意
思想
模板例题 876. 快速幂求逆元
描述
给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。
注意:请返回在 0∼p−1 之间的逆元。
乘法逆元的定义 若整数 b,m 互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)。 b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2 即为 b 的乘法逆元。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一个数组 ai,pi,数据保证 pi 是质数。
输出格式 输出共 n 行,每组数据输出一个结果,每个结果占一行。
若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。
数据范围 1≤n≤105, 1≤ai,pi≤2∗109 输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL qmi(LL a,LL k,LL p){
LL res=1;
while(k){
if(k&1) res=res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
int main(){
int n;
cin>>n;
while(n--){
int a,p;
cin>>a>>p;
if(a%p==0) cout<<"impossible"<<endl; //a与p不互质则说明无逆元
else cout<<qmi(a,p-2,p)<<endl;
}
return 0;
}
思想
注意
模板
void exgcd(int a,int b,int &x,int &y){
if(!b){ //若b=0时
x=1,y=0;
return ;
}
else{ //b!=0时
exgcd(b,a%b,x,y); //递归到下一层
int t=x; //返回时执行
x=y;
y=t-a/b*y;
}
}
例题 877. 扩展欧几里得算法
描述
给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 ai×xi+bi×yi=gcd(ai,bi)。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含两个整数 ai,bi。
输出格式 输出共 n 行,对于每组 ai,bi,求出一组满足条件的 xi,yi,每组结果占一行。
本题答案不唯一,输出任意满足条件的 xi,yi 均可。
数据范围 1≤n≤105, 1≤ai,bi≤2×109 输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1
代码
#include <bits/stdc++.h>
using namespace std;
void exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return ;
}
else{
exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
}
}
int main(){
int n;
cin>>n;
while(n--){
int a,b,x,y;
cin>>a>>b;
exgcd(a,b,x,y);
cout<<x<<" "<<y<<endl;
}
return 0;
}
概念
思想
例题 878. 线性同余方程
描述
给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi),如果无解则输出 impossible
。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一组数据 ai,bi,mi。
输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi,如果无解则输出 impossible
。
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 int 范围之内。
数据范围 1≤n≤105, 1≤ai,bi,mi≤2×109
2
2 3 6
4 3 5
输出样例:
输出样例:
impossible
-3
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void exgcd(LL a,LL b,LL &x,LL &y){
if(!b){ //若b=0时
x=1,y=0;
return ;
}
else{
exgcd(b,a%b,x,y);
LL t=x;
x=y;
y=t-a/b*y;
}
}
LL gcd(LL a,LL b){
return b ? gcd(b,a%b) : a;
}
int main(){
int n;
cin>>n;
while(n--){
LL a,b,m,x,y;
cin>>a>>b>>m;
LL d=gcd(a,m);
exgcd(a,m,x,y);
if(b%d) cout<<"impossible"<<endl;
else cout<<b/d*x%m<<endl;
}
return 0;
}
概念
思想
例题 204. 表达整数的奇怪方式
描述
给定 2n 个整数 a1,a2,…,an 和 m1,m2,…,mn,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡mi(mod ai)。
输入格式 第 1 行包含整数 n。
第 2…n+1 行:每 i+1 行包含两个整数 ai 和 mi,数之间用空格隔开。
输出格式 输出最小非负整数 x,如果 x 不存在,则输出 −1。 如果存在 x,则数据保证 x 一定在 64 位整数范围内。
数据范围 1≤ai≤231−1, 0≤mi<ai 1≤n≤25 输入样例:
2
8 7
11 9
输出样例:
31
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void exgcd(LL a,LL b, LL &x,LL &y){
if(!b){
x=1,y=0;
return ;
}
else{
exgcd(b,a%b,x,y);
LL t=x;
x=y;
y=t-a/b*y;
}
}
LL gcd(LL a,LL b){
return b ? gcd(b,a%b) : a;
}
int main(){
int n;
cin>>n;
LL x=0,m1,a1; //第一个方程的系数 备份数据
cin>>m1>>a1; //先输入第一个方程
for(int i=0;i<n-1;i++){ //合并接下来的n-1个方程
LL m2,a2;
cin>>m2>>a2;
LL k1,k2;
LL d=gcd(m1,m2);
exgcd(m1,m2,k1,k2);
if((a2-a1)%d){ //此时无解
x=-1;
break;
}
k1*=(a2-a1)/d;//特解
k1=(k1%(m2/d)+m2/d)%(m2/d); //让特解k1取到最小正整数解
x=k1*m1+a1;
LL m=abs(m1/d*m2);
a1=k1*m1+a1;
m1=m;
}
if(x!=-1) x=(a1%m1+m1)%m1 //当循环结束时,此时的值应该与最小公倍数取模,以求得最小正整数解
cout<<x<<endl;
return 0;
}
概念
初等行(列)变换
思想
模板
const int N=110;
const double eps=1e-8;
int n;
double a[N][N];
int gauss(){
int c,r;
for (c=0,r=0;c<n;c++){
int t=r;
for(int i=r;i<n;i++){ //筛选出所在列元素最大的行
if(fabs(a[i][c])>fabs(a[t][c])) t=i;
}
if(fabs(a[t][c])<eps) continue; //正对角线中有元素为0,这时有无穷解和无解
if(t!=r) for(int i=c;i<=n;i++) swap(a[t][i],a[r][i]); //若t改变,则交换两行
for(int i=n;i>=c;i--) a[r][i]/=a[r][c]; //将所在行所在列元素变为1
for(int i=r+1;i<n;i++){ //将所在行所在列的下方的行的所在列元素变为0
if(fabs(a[i][c])>eps){
for(int j=n;j>=c;j--) a[i][j]-=a[r][j]*a[i][c];
}
}
r++;
}
if(r<n){ //row<n,即对角线中元素为0的行未被算上
for(int i=r;i<n;i++){
if(fabs(a[i][n])>eps) return 2;
}
return 1;
}
for(int i=n-1;i>=0;i--){ //将非主元位置的A系数矩阵的其他x消去
for(int j=i+1;j<n;j++) a[i][n]-=a[j][n]*a[i][j];
}
return 0;
}
模板例题 883. 高斯消元解线性方程组
描述
输入一个包含 n 个方程 n 个未知数的线性方程组。
方程组中的系数为实数。
求解这个方程组。
下图为一个包含 m 个方程 n 个未知数的线性方程组示例:
输入格式 第一行包含整数 n。
接下来 n 行,每行包含 n+1 个实数,表示一个方程的 n 个系数以及等号右侧的常数。
输出格式 如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解,结果保留两位小数。
如果给定线性方程组存在无数解,则输出 Infinite group solutions
。
如果给定线性方程组无解,则输出 No solution
。
数据范围 1≤n≤100, 所有输入系数以及常数均保留两位小数,绝对值均不超过 100。
输入样例:
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00
输出样例:
1.00
-2.00
3.00
分析
代码
#include <bits/stdc++.h>
using namespace std;
const int N=110;
const double eps=1e-8;
int n;
double a[N][N];
int gauss(){
int c,r;
for (c=0,r=0;c<n;c++){
int t=r;
for(int i=r;i<n;i++){ //筛选出所在列元素最大的行
if(fabs(a[i][c])>fabs(a[t][c])) t=i;
}
if(fabs(a[t][c])<eps) continue; //正对角线中有元素为0,这时有无穷解和无解
if(t!=r) for(int i=c;i<=n;i++) swap(a[t][i],a[r][i]); //若t改变,则交换两行
for(int i=n;i>=c;i--) a[r][i]/=a[r][c]; //将所在行所在列元素变为1
for(int i=r+1;i<n;i++){ //将所在行所在列的下方的行的所在列元素变为0
if(fabs(a[i][c])>eps){
for(int j=n;j>=c;j--) a[i][j]-=a[r][j]*a[i][c];
}
}
r++;
}
if(r<n){ //row<n,即对角线中元素为0的行未被算上
for(int i=r;i<n;i++){
if(fabs(a[i][n])>eps) return 2;
}
return 1;
}
for(int i=n-1;i>=0;i--){ //将非主元位置的A系数矩阵的其他x消去
for(int j=i+1;j<n;j++) a[i][n]-=a[j][n]*a[i][j];
}
return 0;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<=n;j++){
cin>>a[i][j];
}
}
int t=gauss();
if(t==2){
cout<<"No solution"<<endl;
}
else if(t==1) cout<<"Infinite group solutions"<<endl;
else{
for(int i=0;i<n;i++){
if(fabs(a[i][n])<eps) a[i][n]=0;
printf("%.2lf\n",a[i][n]);
}
}
return 0;
}
思想
模板例题 884. 高斯消元解异或线性方程组
描述
输入一个包含 n 个方程 n 个未知数的异或线性方程组。
方程组中的系数和常数为 0 或 1,每个未知数的取值也为 0 或 1。
求解这个方程组。
异或线性方程组示例如下:
M[1][1]x[1] ^ M[1][2]x[2] ^ … ^ M[1][n]x[n] = B[1] M[2][1]x[1] ^ M[2][2]x[2] ^ … ^ M[2][n]x[n] = B[2] … M[n][1]x[1] ^ M[n][2]x[2] ^ … ^ M[n][n]x[n] = B[n] 其中 ^ 表示异或(XOR),M[i][j] 表示第 i 个式子中 x[j] 的系数,B[i] 是第 i 个方程右端的常数,取值均为 0 或 1。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含 n+1 个整数 0 或 1,表示一个方程的 n 个系数以及等号右侧的常数。
输出格式 如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解。
如果给定线性方程组存在多组解,则输出 Multiple sets of solutions。
如果给定线性方程组无解,则输出 No solution。
数据范围 1≤n≤100 输入样例:
3
1 1 0 1
0 1 1 0
1 0 0 1
输出样例:
1
0
0
代码
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n;
int a[N][N];
int guass(){
int c,r;
for (c=0,r=0;c<n;c++){
int t=r;
for (int i=r;i<n;i++){
if (a[i][c]) t = i;
}
if(!a[t][c]) continue;
for (int i=c;i<=n;i++) swap(a[r][i],a[t][i]);
for (int i=r+1;i<n;i++){
if (a[i][c]){
for (int j=n;j>=c;j--) a[i][j]^=a[r][j];
}
}
r++;
}
if(r<n){
for(int i=r;i<n;i++){
if(a[i][n]) return 2;
}
return 1;
}
for(int i=n-1;i>=0;i--){
for(int j=i+1;j<n;j++){
a[i][n]^=a[i][j]*a[j][n];
}
}
return 0;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n+1;j++){
cin>>a[i][j];
}
}
int t=guass();
if(t==0){
for(int i=0;i<n;i++) cout<<a[i][n]<<endl;
}
else if(t==1) cout<<"Multiple sets of solutions"<<endl;
else cout<<"No solution"<<endl;
return 0;
}
概念
公式
思想
模板例题 885. 求组合数 I
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式 共 n 行,每行输出一个询问的解。
数据范围 1≤n≤10000, 1≤b≤a≤2000 输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010,mod=1e9+7;
int c[N][N]; //组合数
void init(){ //预处理出全部答案
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
if(!j) c[i][j]=1; //如果j = 0,那么把c[i][j]初始化为1
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; //递推式
}
}
}
int main(){
init();
int n;
cin>>n;
while(n--){
int a,b;
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
思想
模板例题 886. 求组合数 II
描述
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式 共 n 行,每行输出一个询问的解。
数据范围 1≤n≤10000, 1≤b≤a≤105 输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+3,mod=1e9+7;
LL fact[N],infact[N];
LL qmi(LL a,LL k,LL p){
int res=1;
while(k){
if(k&1) res=res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
int main(){
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=fact[i-1]*i%mod; //存储i!
infact[i]=infact[i-1]*qmi(i,mod-2,mod)%mod; //qmi(i,mod-2,mod)快速幂求逆元
}
LL n;
cin>>n;
while(n--){
LL a,b;
cin>>a>>b;
cout<<fact[a]*infact[b]%mod*infact[a-b]%mod<<endl; //计算公式
}
return 0;
}
思想
模板例题
描述
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 Cbamodp 的值。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式 共 n 行,每行输出一个询问的解。
数据范围 1≤n≤20, 1≤b≤a≤1018, 1≤p≤105,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL qmi(LL a,LL k,LL p){
LL res=1;
while(k){
if(k&1) res=res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
LL C(LL a,LL b,LL p){
if(b>a) return 0;
LL res=1;
for(LL i=1,j=a;i<=b;i++,j--){
res=res*j%p;
res=res*qmi(i,p-2,p)%p;
}
return res;
}
LL lucas(LL a,LL b,LL p){
if(a<p&&b<p) return C(a,b,p);
return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
int main(){
int n;
cin>>n;
while(n--){
LL a,b;
LL p;
cin>>a>>b>>p;
cout<<lucas(a,b,p)<<endl;
}
return 0;
}
思想
模板例题 888. 求组合数 IV
描述
输入 a,b,求 Cba 的值。
注意结果可能很大,需要使用高精度计算。
输入格式 共一行,包含两个整数 a 和 b。
输出格式 共一行,输出 Cba 的值。
数据范围 1≤b≤a≤5000
5 3
输出样例:
输出样例:
10
代码
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int primes[N];
int sum[N];
bool vis[N];
int cnt;
void get_primes(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){
vis[primes[j]*i]=1;
if(i%primes[j]==0) break;
}
}
}
int get(int n,int p){
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
vector<int> mul(vector<int> a,int b){
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++){
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(t){
c.push_back(t%10);
t/=10;
}
return c;
}
int main(){
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++){
int p=primes[i];
sum[i]=get(a,p)-get(a-b,p)-get(b,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++){
for(int j=0;j<sum[i];j++){
res=mul(res,primes[i]);
}
}
for(int i=res.size()-1;i>=0;i--) cout<<res[i];
return 0;
}
思想
的面积:
的面积:
模板例题 890. 能被整除的数
给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm。
请你求出 1∼n 中能被 p1,p2,…,pm 中的至少一个数整除的整数有多少个。
输入格式 第一行包含整数 n 和 m。
第二行包含 m 个质数。
输出格式 输出一个整数,表示满足条件的整数的个数。
数据范围 1≤m≤16, 1≤n,pi≤109 输入样例:
10 2
2 3
输出样例:
7
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=20;
int p[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++) cin>>p[i];
LL res=0;
for(int i=1;i<1<<m;i++){ // 1~2^m-1这些数代表2^m-1个不同的项
LL t=1,s=0; //s代表当前枚举的项中包含集合的个数
for(int j=0;j<m;j++){ //看这个项的第 j 位是否包含,1代表包含,0代表不包含
if(i>>j&1){ //选
if(t*p[j]>n){ //此时 n/t = 0,直接break即可
t=-1;
break;
}
t*=p[j];
s++;
}
}
if(t!=-1){
if(s%2) res+=n/t; //加奇数项的个数
else res-=n/t; //减偶数项的个数
}
}
cout<<res<<endl;
return 0;
}
概念
若一个游戏满足:
则称该游戏为一个公平组合游戏
尼姆游戏(NIM)属于公平组合游戏,但常见的棋类游戏,比如围棋就不是公平组合游戏,因为围棋交战双方分别只能落黑子和白子,胜负判定也比较负责,不满足条件2和3
游戏规则
给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。 问如果两人都采用最优策略,先手是否必胜。
例如:有两堆石子,第一堆有2个,第二堆有3个,先手必胜。
操作步骤
思想
堆石子,数目分别是
,如果
,先手必胜;否则先手必败。
证明
模板例题 891. Nim游戏
描述
给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式 第一行包含整数 n。
第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。
输出格式
如果先手方必胜,则输出 Yes
。
否则,输出 No
。
数据范围 1≤n≤105, 1≤每堆石子数≤109 输入样例:
2
2 3
输出样例:
Yes
代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int res;
cin>>res;
for(int i=0;i<n-1;i++){
int m;
cin>>m;
res^=m;
}
if(res) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
描述
现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式 第一行包含整数 n。
第二行包含 n 个整数,其中第 i 个整数表示第 i 级台阶上的石子数 ai。
输出格式
如果先手方必胜,则输出 Yes
。
否则,输出 No
。
数据范围 1≤n≤105, 1≤ai≤109 输入样例:
3
2 1 3
输出样例:
Yes
分析
Nim
游戏代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int res;
cin>>res;
for(int i=2;i<=n;i++){
int m;
cin>>m;
if(i%2) res^=m;
}
if(res) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
游戏规则
给定n堆石子以及一个由k个不同正整数构成的数字集合S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
核心思想
SG函数图解
证明有向图游戏的和符合Nim游戏
模板例题 893. 集合-Nim游戏
描述
给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式 第一行包含整数 k,表示数字集合 S 中数字的个数。
第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。
第三行包含整数 n。
第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。
输出格式
如果先手方必胜,则输出 Yes
。
否则,输出 No
。
数据范围 1≤n,k≤100, 1≤si,hi≤10000 输入样例:
2
2 5
3
2 4 7
输出样例:
Yes
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+3;
int n,m;
int s[N],f[N]; //s存储的是可供选择的集合,f存储的是所有可能出现过的情况的sg值
int sg(int x){
if(f[x]!=-1) return f[x]; //如果存储过了,直接返回
set<int> vis; //vis代表的是当前存在的数的集合
for(int i=0;i<m;i++){
int sum=s[i];
if(x>=sum) vis.insert(sg(x-sum)); //先得到到终点的sg值后,再从后往前倒推出所有数的sg值
}
for(int i=0;;i++){
if(!vis.count(i)) return f[x]=i; //Mex操作
}
int main(){
cin>>m;
for(int i=0;i<m;i++) cin>>s[i];
cin>>n;
memset(f,-1,sizeof f);
int res=0;
for(int i=0;i<n;i++){
int x;
cin>>x;
res^=sg(x); //Nim游戏
}
if(res) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
描述
给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式 第一行包含整数 n。
第二行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 ai。
输出格式
如果先手方必胜,则输出 Yes
。
否则,输出 No
。
数据范围 1≤n,ai≤100
输出样例:
2
2 3
输出样例:
Yes
分析
集合-Nim
,这里的每一堆可以变成小于原来那堆的任意大小的两堆,即a[i]
可以拆分成(b[i],b[j])
b[i]>=b[j]
,即:a[i]>b[i]>=b[j]
sg(b[i])^sg(b[j])
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+3;
int n;
int f[N];
set<int> vis;
int sg(int x){
if(f[x]!=-1) return f[x];
for(int i=0;i<x;i++){
for(int j=0;j<=i;j++){ //规定j不大于i,避免重复
vis.insert(sg(i)^sg(j)); //相当于一个局面拆分成了两个局面
}
}
for(int i=0;;i++){
if(!vis.count(i)) return f[x]=i;
}
}
int main(){
cin>>n;
memset(f,-1,sizeof f);
int res=0;
for(int i=0;i<n;i++){
int x;
cin>>x;
res^=sg(x);
}
if(res) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}