目录
零.一些组合位运算
操作 运算 取出整数n在二进制表示下的第k位 (n>>k)&1 取出整数n在二进制表示下的第0~k-1位(后k位) (n&((1<<k)-1) 把整数n在二进制表示下的第k位取反 n xor (1<<k) 对整数n在二进制表示下的第k位赋值1 n|(1<<k) 对整数n在二进制表示下的第k位赋值0 n&(~(1<<k))
一.快速幂
求 a 的 b 次方对 p 取模的值。
输入格式
三个整数 a,b,p,在同一行用空格隔开。
输出格式
输出一个整数,表示
a^b mod p
的值。数据范围
0≤a,b≤109
1≤p≤109输入样例:
3 2 7
输出样例:
2
b&1取出b在二进制表示下的最低位,b>>1可以舍去最低位,两者结合,可以得到b在二进制表示下的所有数位。a^b其实相当于(i表示b的二进制表示下的第i个数位,而
。
其中k = log2(b+1)(向上取整),所以该算法的时间复杂度位O(log2(b))。
#include<bits/stdc++.h>
using namespace std;
int power(int a, int b , int p){
int ans = 1 % p ;
for( ; b ; b >>= 1) {
if(b & 1) ans = (long long)ans * a % p;
a = (long long)a * a % p;
}
return ans;
}
int main(){
int a , b , p;
cin >> a >> b >>p;
int ans = power(a, b , p) % p;
cout << ans << endl;
return 0;
}
二.64位乘法取模
求 aa 乘 bb 对 pp 取模的值。
输入格式
第一行输入整数aa,第二行输入整数bb,第三行输入整数pp。
输出格式
输出一个整数,表示
a*b mod p
的值。数据范围
1≤a,b,p≤1018
输入样例:
3 4 5
输出样例:
2
原理与快速幂相似。
#include<bits/stdc++.h>
using namespace std;
long long mul(long long a, long long b, long long p){
long long ans = 0;
for(; b;b >>= 1) {
if(b & 1) ans = (ans + a) % p;
a = a * 2 % p;
}
return ans;
}
int main(){
long long a , b , p;
cin >> a >> b >> p;
long long ans = mul(a, b, p);
cout << ans <<endl;
return 0;
}
python做法(419ms)
a=input()
b=input()
p=input()
print((a*b)%p)
三.状态压缩
二进制状态压缩:将一个长度为m的bool数组用一个m位二进制整数表示并存储的方法。
给定一张 n 个点的带权无向图,点从0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n。
接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 ii 到 jj 的距离(记为 a[i,j]a[i,j])。
对于任意的 x,y,z,数据保证a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤n≤201≤n≤20
0≤a[i,j]≤1070≤a[i,j]≤107输入样例:
5 0 2 4 5 1 2 0 6 5 3 4 6 0 8 3 5 5 8 0 5 1 3 3 5 0
输出样例:
18
可以通过暴力的方法,枚举n个点的全排列,时间复杂度为O(n*n!),通过二进制状态压缩dp可以优化到O()。
我们可以使用一个n位二进制数表示在任意时刻有哪些点被经过,哪些点没有被经过。若其第i位为1,则表示第i个点已经被经过,反之未被经过。用F[i,j]表示“点被经过的状态”对应的二进制数为i,且目前处于点j时的最短路径。起点时,有f[1,0]=0,即只经过了点0(i只有第0位为1),目前出于起点0,最短路长度为0。由于要求最短路径,所以方便起见,将f内的其他数值设置为无穷大。目标状态为f[(1<<n)-1,n-1],即经过所有点(i的所有位都是1),处于终点n-1的最短路。
在任意时刻有f[i,j] = f[ i^(1<<j),k] + w(k,j)] ,其中 0<= k < n&&((i >> j)&1) = 1,即当前时刻处在j点的状态,由未处于j点但其他路径相同的状态转移而来,所以i^(1<<j)找到未经过j点的状态(除了未经过j点,其他点的情况与i相同)然后k可能时i的二进制表示中任意一个除j外的数位为1的点。所以k要满足((i^1<<j)>>k&1)==1,即将第j位取反后,取出第k位,如果为1,则可以有f[i^(1<<j),k]转移到f[i,j]。
#include<bits/stdc++.h>
using namespace std;
int f[1 << 20][20];
int Hamilton(int n , int w[20][20]){
memset(f, 0x3f , sizeof(f));
f[1][0] = 0;
for (int i = 1; i < 1 << n; ++i)
for(int j = 0; j < n; ++j) if (i >> j & 1)
for(int k = 0; k < n; k++) if ((i^1<<j) >> k & 1)
f[i][j] = min(f[i][j], f[i^1<<j][k]+w[k][j]);
return f[(1<<n) - 1][n - 1];
}
int main(){
int w[20][20], n;
cin >> n;
for(int i = 0; i < n; ++i)i
for(int j = 0; j < n; ++j)
scanf("%d", &w[i][j]);
int ans = Hamilton(n , w);
printf("%d",ans);
}
以下是一些模板和题目,不做详细讲解。
四.求一个数在二进制表示下为一的数位
模板,不做讲解。
#include<bits/stdc++.h>
using namespace std;
int Hash[37],n;
int main(){
for (int i = 0; i < 36; i++) Hash[(1ll << i) % 37] = i;
while(cin >> n , n){
while(n > 0) {
cout << Hash[(n & -n) % 37] << ' ';
n -= n & -n;
}
cout << endl;
}
return 0;
}
五.成对变换
对于非负整数n:
当 n 为偶数时,n xor 1 等于 n + 1;
当 n 为奇数时,n xor 1 等于 n - 1;
六.例题
本题不难,建议自己想想怎么做。
AC代码
#include<bits/stdc++.h>
using namespace std;
long long pre[100];
int x,y,ans;
int BitCount(long long n)
{
int c =0 ;
while (n >0)
{
if((n &1) ==1)
++c ;
n >>=1 ;
}
return c ;
}
void dfs(int p,long long sum,long long fsum,int f){
if(p>15) return;
if(sum>=fsum)ans=min(ans,BitCount(sum-fsum)+f);
dfs(p+1,sum,fsum+pre[p],f+1);
dfs(p+1,sum,fsum,f);
}
int main(){
int t;
cin>>t;
pre[1]=1;
for(int i=2;pre[i]<=1e13;i++){
pre[i]=pre[i-1]*i;
}
while(t--){
ans=INT_MAX;
long long n;
cin>>n;
dfs(3,n,0,0);
if(ans==INT_MAX) cout<<-1<<endl;
else cout<<ans<<endl;
}
}