1. bitset 优化背包
https://loj.ac/p/515
题意:
给 n 个 <= n 的数,每个数有取值范围 a[ i ] - b[ i ],令 x 为 n 个数的平方和,求能构成的 x 的个数
样例:
5
1 2
2 3
3 4
4 5
5 6
26
思路:
背包dp,dp[ i ][ j ] 表示用前 i 个数能不能组成 j ,用 bitset 优化
用一个 bitset dp 维护 当前的数 可以组成哪几个 x ,用 bitset tmp 维护 当前的状态 可以转移到哪几个状态,滚动优化空间,复杂度为O(n^5 / 64)
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
bitset<N>dp,tmp;
void solve(){
int n;
cin>>n;
dp.set(0); //初始化
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
tmp.reset();
for(int j=a;j<=b;j++){
tmp|=(dp<<(j*j));
}
dp=tmp;
}
cout<<dp.count()<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--){
solve();
}
return 0;
}
2. bitset 优化图上问题(可达性统计问题)
https://www.acwing.com/problem/content/166/
题意:
给一个 n 个点,m 条边的 有向无环图,求每个点能到达的 点的个数
样例:
10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9
1
6
3
3
2
1
1
1
1
1
思路:
反向建图,然后利用 拓扑排序,进行图上dp,dp[ i ][ j ]表示从点 i 出发能不能到达 点 j ,用 bitset优化,若存在 u - > v 的边,则 dp[ u ] | = dp[ v ]
复杂度为 O(n^2 / 64)
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e4+5;
int head[N],cntt=0;
struct Edge{
int to,next,val;
}edge[N];
void add(int u,int v,int x){
edge[++cntt].to=v;
edge[cntt].val=x;
edge[cntt].next=head[u];
head[u]=cntt;
}
int deg[N];
bitset<N>dp[N];
void solve(){
int n,m;
cin>>n>>m;
while(m--){
int u,v;
cin>>u>>v;
add(v,u,1);
deg[u]++;
}
queue<int>q;
for(int i=1;i<=n;i++){
dp[i][i]=1;
if(deg[i]==0)q.push(i);
}
while(!q.empty()){
int tmp=q.front();
q.pop();
for(int i=head[tmp];i;i=edge[i].next){
int y=edge[i].to;
dp[y]|=dp[tmp];
deg[y]--;
if(deg[y]==0)q.push(y);
}
}
for(int i=1;i<=n;i++)cout<<dp[i].count()<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--){
solve();
}
return 0;
}
3. bitset 优化枚举
https://codeforces.com/contest/333/problem/E
题意:
给 n 个点,找 3 个点,以这三个点为圆心,画 3 个半径相同互不相交的圆,求最大的半径
可以转化为,给 n 个点,找出 最小边最大 的三角形,求最大的最小边
思路:
将所有边按边长降序排列,依次枚举当前边作为最小边,然后枚举其它点,若存在某点 和 当前的边的两个点都已经连有边(即存在以当前边为最小边的三角形),则找到答案,否则连上这条边
直接枚举的复杂度是 O ( n^3 ) ,可以用一个标记数组 vis [ i ] [ j ] 表示有没有 i - j 的边,用 bitset 优化这个数组,每次 对于 x-y 的边,通过 vis[x] & vis[y] 可以在 O ( n/64 ) 时间内快速判断有没有符合条件的点,在 O ( n^3/64 ) 复杂度下通过本题
样例:
7 2 -3 -2 -3 3 0 -3 -1 1 -2 2 -2 -1 0
1.58113883008418980000
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=3e3+5;
double x[N],y[N];
double length(int a,int b){
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
struct node{
int x,y;
double len;
bool operator<(const node a)const{
return len>a.len;
}
};
vector<node>edge;
bitset<N>vis[N];
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>x[i]>>y[i];
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
edge.push_back((node){i,j,length(i,j)});
}
}
sort(edge.begin(),edge.end());
for(auto e:edge){
int x=e.x,y=e.y;
double len=e.len;
if((vis[x]&vis[y]).any()){
cout<<fixed<<setprecision(8)<<len/2<<endl;
return;
}
vis[x].set(y);
vis[y].set(x);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--){
solve();
}
return 0;
}
4. bitset 优化 01 矩阵乘法
http://acm.hdu.edu.cn/showproblem.php?pid=7293
题意:
给一张无向图,求图中有多少个 下图形状的子图
思路:
对无向图建邻接矩阵,对邻接矩阵进行平方,新矩阵 a[ i ] [ j ] 表示 从 i 点到 j 点 长为2的路径个数,枚举子图中 度为6和4的两个点,通过组合数计算子图数量即可,复杂度为 O(n^3)
利用 bitset 优化,01矩阵的平方 可以通过 b [ i ] [ j ] = ( r [ i ] & c [ j ] ) . count () 计算,由于无向图的邻接矩阵是 对称矩阵,所以直接用 ( vis [ i ] & vis [ j ] ) . count () 表示矩阵平方,复杂度为 O(n^3/64)
样例:
1
8 10
1 2
1 3
1 4
1 5
1 6
1 7
8 4
8 5
8 6
8 7
1
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){ //快读快写
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9'){
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
inline void write(int x){
if(x>9)write(x/10);
putchar(x%10+'0');
}
const int N=1005;
const int mod=1e9+7;
int fact[N],inv[N]; //O(1)求组合数
inline int qpow(int a,int b){
int ans=1;
while(b>0){
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
void Cinit(){
fact[0]=1,inv[0]=1;
for(int i=1;i<=N-1;i++){
fact[i]=fact[i-1]*i%mod;
}
inv[N-1]=qpow(fact[N-1],mod-2)%mod;
for(int i=N-2;i>=1;i--){
inv[i]=inv[i+1]*(i+1)%mod;
}
}
inline int C(int a,int b){
if(a<0||b<0||a<b)return 0;
return fact[a]*inv[a-b]%mod*inv[b]%mod;
}
bitset<N>vis[N];
void solve(){
int n=read(),m=read();
for(int i=1;i<=n;i++)vis[i].reset();
while(m--){
int u=read(),v=read();
vis[u].set(v);
vis[v].set(u);
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int cnt1=vis[i].count(),cnt2=vis[j].count();
int cnt=(vis[i]&vis[j]).count(); // 矩阵乘
if(vis[i][j]){ //不能选两点直接相连的边
cnt1--,cnt2--;
}
ans+=C(cnt1-4,2)*C(cnt,4);
ans%=mod;
ans+=C(cnt2-4,2)*C(cnt,4);
ans%=mod;
}
}
write(ans);
putchar('\n');
return;
}
signed main(){
Cinit();
int T=read();
while(T--){
solve();
}
return 0;
}
5. bitset 优化 floyd
https://www.luogu.com.cn/problem/P4306
题意:
给一张有向图,求每个点能到达的点的个数的和
样例:
3 010 001 100
9
思路:
考虑 floyd 上 dp,dp [ i ] [ j ] 表示点 i 能到达点 j ,转移方程为 dp[ i ][ j ] |= (dp[ i ][ k ] &dp[ k ][ j ] )
用 bitset 优化转移,复杂度为 O(n^3/64)
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e3+5;
bitset<N>dp[N];
void solve(){
int n;
cin>>n;
vector<string>s(n+1);
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=' '+s[i];
dp[i].set(i);
for(int j=1;j<=n;j++){
if(s[i][j]=='1'){
dp[i].set(j);
}
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
if(dp[i][k]){
dp[i]|=dp[k];
}
// dp[i][j]|=dp[i][k]|dp[k][j];
}
}
int ans=0;
for(int i=1;i<=n;i++)ans+=dp[i].count();
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--){
solve();
}
return 0;
}