Bitset常用操作:
bitsets; //定义一个大小为size的bitset s.count(); //统计s中1的个数 s.set(); //将s的所有位变成1 s.set(p); //将s的第p位变成1 s.reset(); //将s的所有位变成0 s.reset(p); //将s的第p位变成0 s.flip(); //将s的所有位取反 s.flip(p); //将s的第p位取反 s.to_string(); //将s转换成string
两个$bitset$运算的时间复杂度大概是$O(\frac{n}{32})$,所以能卡进去的话可以不写这个东西。
例题:
1.HDU5313-Bipartite Graph
有若干个二分图,现在你要添加一些边形成一个完全二分图,求最多可以添加多少边。
考虑对于每个二分图统计两部分的节点数$a_{i},b_i$,问题变为有两个集合,对于每个i,将$a_i$或$b_i$加入集合,使得两个集合的和尽量接近。
只需要模拟退火做一个背包,令$dp[i]$表示i是否能凑出来,最后取最接近$n/2$的能凑出来的数作为答案。用$bitset$优化该dp即可。
#include#include #include #include #include #define maxn 10005 #define maxm 500005 #define inf 0x7fffffff #define ll long long using namespace std; int N,M,hd[maxn],to[maxm],nxt[maxm],cnt; int l[maxn],r[maxn],tot; bool vis[maxn]; bitset dp; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void addedge(int u,int v){to[++cnt]=v,nxt[cnt]=hd[u],hd[u]=cnt;} inline void dfs(int u,bool f){ vis[u]=1; if(!f) l[tot]++; else r[tot]++; for(int i=hd[u];i;i=nxt[i]){ int v=to[i]; if(!vis[v]) dfs(v,(f^1)); } return; } int main(){ int T=read(); while(T--){ N=read(),M=read(); tot=0;cnt=0; memset(hd,0,sizeof(hd)); memset(vis,0,sizeof(vis)); memset(l,0,sizeof(l)); memset(r,0,sizeof(r)); for(int i=1;i<=M;i++){ int u=read(),v=read(); addedge(u,v),addedge(v,u); } for(int i=1;i<=N;i++) if(!vis[i]) tot++,dfs(i,0); dp.reset(); dp.set(0,1); for(int i=1;i<=tot;i++) dp=(dp< r[i]); int ans=0; for(int i=1;i<=N;i++) if(dp[i]) ans=max(ans,i*(N-i)-M); printf("%d\n",ans); } return 0; }
2.BZOJ2208-[Jsoi2010]连通数
给你一个有向图,求图中可达顶点对的个数。
考虑Floyd求最短路的过程是枚举中转点k后用k更新每对i,j,现在不需要求最短路而只需要判断能不能到达。
所以设$dp[k]$表示k能到达的点集的二进制表示,枚举k,i后用$dp[k]$更新$dp[i]$即可。
#include#include #include #include #include #define maxn 2005 #define maxm 500005 #define inf 0x7fffffff #define ll long long using namespace std; int N; char str[maxn][maxn]; bitset dis[maxn]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int main(){ N=read(); for(int i=1;i<=N;i++){ scanf("%s",str[i]+1); for(int j=1;j<=N;j++) if(str[i][j]-'0'||i==j) dis[i].set(j); } for(int k=1;k<=N;k++) for(int i=1;i<=N;i++) if(dis[i][k]) dis[i]|=dis[k]; int ans=0; for(int i=1;i<=N;i++) ans+=dis[i].count(); printf("%d\n",ans); return 0; }
3.Hihocoder1236-Scores
在线求五维偏序的对数。
考虑可以对于每一维分别计算出满足要求的人的二进制表示后把5个二进制数与起来即为答案。
但这样空间时间两爆炸,所以考虑分块bitset,令$dp[k][i]$表示第k维前i个块的人的二进制表示。
对于每个询问的每一维二分查找出它所在的块,即可求出答案。
切忌在多重循环内使用两个$bitset$相互运算。
#include#include #include #include #include #include #define maxn 50005 #define maxm 305 #define inf 0x7fffffff #define ll long long using namespace std; bitset dp[5][maxm],tp,ans; int N,M,bel[maxn],db[5][maxn]; struct node{int val,ind;}h[5][maxn]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline bool cmp(node a,node b){return a.val<b.val;} inline int upb(int k,int x){ int l=1,r=N,aans=0; while(l<=r){ int mid=l+r>>1; if(db[k][mid]<=x) l=mid+1,aans=mid; else r=mid-1; } return aans; } int main(){ //freopen("1.in","r",stdin); //freopen("1.txt","w",stdout); int T=read(); while(T--){ N=read(),M=read(); int len=sqrt(N); memset(bel,0,sizeof(bel)); memset(db,0,sizeof(db)); for(int i=1;i<=N;i++) for(int j=0;j<5;j++) h[j][i].val=read(),h[j][i].ind=i; for(int i=1;i<=N;i++) bel[i]=(i-1)/len+1; for(int k=0;k<5;k++){ tp.reset(); sort(h[k]+1,h[k]+1+N,cmp); for(int i=1;i<=N;i++) db[k][i]=h[k][i].val; for(int i=1;i<=N;i++){ tp.set(h[k][i].ind); if(bel[i]!=bel[i+1]) dp[k][bel[i]]=tp; } } int Q=read(),lasans=0; for(int nu=1;nu<=Q;nu++){ for(int k=0;k<5;k++){ int x=read()^lasans,pos=upb(k,x),id=bel[pos]; if(pos==0) {ans.reset();continue;} tp.reset(); if(id>=2) tp|=dp[k][id-1]; for(int i=(id-1)*len+1;i<=pos;i++) tp.set(h[k][i].ind); if(k==0) ans=tp; else ans&=tp; } lasans=ans.count(); printf("%d\n",lasans); } } return 0; }