bitset
bitset前身:普通状态压缩的优化
以cf937G为例,对于邻接矩阵的由二维压缩到一维
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using i64 = long long; void solve() { int n; std::cin >> n; std::vector<std::string> g(n), w(n); for (int i = 0; i < n; i++) { std::cin >> g[i] >> w[i]; } std::vector<int> e(n); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (g[i] == g[j] || w[i] == w[j]) { e[i] |= 1 << j; } } } std::vector<int> dp(1 << n); for (int x = 0; x < n; x++) { dp[1 << x] |= 1 << x; } int ans = 0; for (int s = 1; s < (1 << n); s++) { if (dp[s]) { ans = std::max(ans, __builtin_popcount(s)); } for (int y = 0; y < n; y++) { if ((~s >> y & 1) && (dp[s] & e[y])) { dp[s | 1 << y] |= 1 << y; } } } std::cout << n - ans << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int t; std::cin >> t; while (t--) { solve(); } return 0; }
floyd求可达性传递闭包
Solution:初步做法:首先本题只需要求所有偏序关系,考虑建图的时候只建单向图。朴素floyd是O ( n 3 ) O(n^3) O ( n 3 ) 的本题n是1e3显然无法通过,考虑过程中我们只需要维护01的信息,我们考虑使用bitset的过程将与的过程优化O ( n w ) ( w = 64 ) O(\frac {n}{w})(w=64) O ( w n ) ( w = 6 4 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bitset<N>dp[N]; void solve () { cin>>n>>m; for (int i=1 ;i<=m;i++){ int u,v;cin>>u>>v; dp[u][v]=1 ; } for (int k=1 ;k<=n;k++){ for (int i=1 ;i<=n;i++){ if (dp[i][k])dp[i]|=dp[k]; } } int ans=0 ; for (int i=1 ;i<=n;i++)ans+=dp[i].count (); ans=n*(n-1 )/2 -ans; cout<<ans<<endl; }
听dmy学习记录
常用位运算函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ll n=(1LL <<40 )-1LL ; cout<<__builtin_popcountll(n)<<endl; cout<<__lg(n)<<endl; cout<<63 -__builtin_clzll(n)<<endl; cout<<__builtin_ctz(8 )<<endl; bitset<5>B=21 ; cout<<B<<endl; for (int i = (int )(B._Find_first()); i != B.size ();i = (int )(B._Find_next(i))) cout << i << " " ;
bzoj3687https://hydro.ac/d/bzoj/p/3687
题意:求所有子集和的异或和
Solution:首先我们不可能枚举所有子集,所以我们只能考虑dp。d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 考虑前i个数,子集和为j的方案,考虑最后需要全部异或起来,偶数方案数直接抵消
因此只需要维护奇偶性,所以我们考虑背包运算中异或,但是现在值域总和2e6,1000个数,显然过不去
考虑bitset优化背包,可达性01用的。由于本题只需要看%2的情况,所以同理也可以使用。时间复杂度:O ( n m / w ) O(nm/w) O ( n m / w )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bitset<M>b; void solve () { cin>>n; b[0 ]=1 ; for (int i=1 ;i<=n;i++){ int x;cin>>x; b^=(b<<x); } ll ans=0 ; for (int i=1 ;i<=1000000 ;i++){ if (b[i])ans^=i; } cout<<ans<<endl; }
题意:给定一个集合1-n。求所有子集和的乘积.(n<=200)
Solution:考虑值域和数据个数,可以直接dp。d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 表示考虑前i个数,和为j的方案数。假设先不取模,我们继续考虑下去。最后统计答案的时候的每次算乘积算的是i d p [ i ] i^{dp[i]} i d p [ i ] 。!注意注意注意。dp数组是作为指数出现的,所以我们不能直接对其mod取模。考虑费马小定理,这种指数乘积在mod是质数的时候是以mod-1为周期的,所以我们dp的时候应当对mod-1取模。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int dp[N*N];[j]int qmi (int a,int b) { int res=1LL ; while (b){ if (b&1 )res=(res*a)%mod; a=a*a%mod; b>>=1 ; } return res; } void solve () { cin>>n; dp[0 ]=1 ; for (int i=1 ;i<=n;i++){ for (int j=n*(n+1 )/2 ;j>=i;j--){ dp[j]+=dp[j-i];dp[j]%=(mod-1 ); } } int ans=1LL ; for (int i=1 ;i<=n*(n+1 )/2 ;i++) { int cnt=dp[i]; ans*=qmi (i,cnt);ans%=mod; } cout<<ans<<endl; }
DAG计数:O ( n 2 / w ) O(n^{2}/w) O ( n 2 / w ) 的解法
题意:有向无环图,输入都是小向大连边,求出每个点能到达多少点?
Solution:考虑如果u到v有边,那么v所能到达的点u也能到达,直接利用bitset的或运算就可以完成。f[u]|=f[v]
检查时间:50000*50000/64=4e7
计算空间:50000*50000/8/1024/1024=298MB(bit->byte->kb->MB)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 bitset<N>f[N]; vector<int >e[N]; void solve () { cin>>n;cin>>m; for (int i=1 ;i<=m;i++){ int u,v;cin>>u>>v; e[u].push_back (v); } for (int i=n;i>=1 ;i--){ f[i][i]=1 ; for (auto v:e[i]){f[i]|=f[v];} cerr<<i<<" " <<f[i]<<endl; } for (int i=1 ;i<=n;i++)cout<<f[i].count ()<<endl; }
bitset还可以解决解决三元环计数,求传递闭包。思路都是枚举两个点,将枚举第三点的耗时从O ( n ) O(n) O ( n ) 优化成O ( n / w ) O(n/w) O ( n / w ) 。
三元环计数详情见该专题
bitset解决动态带修字符串匹配
题意:给长串s,模式串t,t中含有?作为通配符,求s中t出现了几次,分别在哪开始?
Sol:考虑有通配符不好用kmp做,有FFT做法,待补。利用bitset的思想是维护s中每个位置作为起点是否可行。对于t中字母 t[j],对于s中所有不为这个字母的位置i,则i-j一定无法作为起点,我们利用bitset可以对于每个j花费O ( n / w ) O(n/w) O ( n / w ) 的时间完成这个处理。
实现:提前开26个bitset预处理每个字母在s的哪些位置出现过。每次遇到一个 t[j]就右移j位对应bitset
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 bitset<N>b[28 ]; bitset<N>ans; void solve () { string s,t;cin>>s>>t; n=s.size ();m=t.size (); for (int i=0 ;i+m-1 <n;i++)ans[i]=1 ; for (int i=0 ;i<n;i++)b[s[i]-'a' ][i]=1 ; for (int i=0 ;i<m;i++){ if (t[i]=='?' )continue ; ans&=b[t[i]-'a' ]>>i; } cout<<ans.count ()<<endl; for (int i=0 ;i+m-1 <n;i++){ if (ans[i]==1 )cout<<i<<endl; } }
给你一个字符串 s s s ,共有 q q q 次操作,每个都是下面两种形式的一种。
1 ≤ ∣ s ∣ ≤ 1 0 5 1\leq |s|\leq 10^5 1 ≤ ∣ s ∣ ≤ 1 0 5 ,1 ≤ q ≤ 1 0 5 1\leq q\leq 10^5 1 ≤ q ≤ 1 0 5 ,∑ ∣ y ∣ ≤ 1 0 5 \sum |y| \leq 10^5 ∑ ∣ y ∣ ≤ 1 0 5 。
Sol:考虑对于区间查询转化为后缀查询,利用bitset的移位性质,先得到L-n的子串个数,再得到r+1-n的子串个数,做差即可。我们考虑依旧采用上面的思想,维护每个字符的出现位置。利用模式串不断筛选可用的起点,最后为1的位置就是可用的起点。
时间复杂度:O ( n m / w ) O(nm/w) O ( n m / w )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 bitset<N>b[28 ]; bitset<N>ans; void solve () { string s;cin>>s; int len=s.size (); for (int i=0 ;i<len;i++)b[s[i]-'a' ][i]=1 ; cin>>m; for (int i=1 ;i<=m;i++) { int op;cin>>op; if (op==1 ){ int pos;cin>>pos;pos--; char c;cin>>c; b[s[pos]-'a' ][pos]=0 ; s[pos]=c; b[s[pos]-'a' ][pos]=1 ; } else { int l,r;cin>>l>>r;l--;r--; ans.set (); string t;cin>>t;n=t.size (); if (r-l+1 <n){ cout<<0 <<endl; continue ; } for (int i=0 ;i<n;i++){ ans&=b[t[i]-'a' ]>>i; } int cl=(ans>>l).count (),cr=(ans>>(r+2 -n)).count (); cout<<cl-cr<<endl; } } }
给你一个字符串 s ( ∣ S ∣ ≤ 1 0 5 ) s \pod {|S| \le 10^5} s ( ∣ S ∣ ≤ 1 0 5 ) ,有 n ( n ≤ 1 0 5 ) n \pod {n \le 10^5} n ( n ≤ 1 0 5 ) 个询问,第 i i i 个询问包含一个整数 k i k_i k i 和一个字符串 m i ( ∑ i ∣ m i ∣ ≤ 1 0 5 ) m_i \pod {\sum_i |m_i| \le 10^5} m i ( ∑ i ∣ m i ∣ ≤ 1 0 5 ) 。要求找到一个字符串 t t t ,使得 t t t 是 s s s 的子串并且 m i m_i m i 至少在 t t t 中出现了 k i k_i k i 次。你只需要求出 t t t 的最短长度。
保证 m i m_i m i 互不相同。
CF963D Frequency of String 题解 - 洛谷专栏 (luogu.com.cn)
Sol:首先考虑对于每个模式串预处理出在远传s中所有可行的endpos,处理手法与上面一样,上面预处理的起点,这里用终点,为了不糊涂改用1_index了,本质一样。我们考虑统计答案的复杂度,直接一想似乎是O ( n m ) O(nm) O ( n m ) 的无法通过,但注意到题目说每个模式串不同,且总长度不超过1e5,经典根号分治,不同长度的串一定不超过∑ ∣ t i ∣ \sqrt{\sum{|t_i|}} ∑ ∣ t i ∣ ,对于固定长度的串可行endpos的个数是线性的,所以统计答案复杂度是O ( n n / w ) O(n\sqrt{n}/w) O ( n n / w )
预处理: 100000*100000/64=1.5e8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 bitset<N>b[28 ]; bitset<N>ans; void solve () { cin>>s;n=s.size (); s=" " +s; for (int i=1 ;i<=n;i++)b[s[i]-'a' ][i]=1 ; int q;cin>>q; for (int i=1 ;i<=q;i++){ int k;cin>>k; ans.set (); vector<int >v; string t;cin>>t; m=t.size ();t=" " +t; for (int i=1 ;i<=m;i++){ ans&=b[t[i]-'a' ]<<(m-i); } for (int pos=ans._Find_first();pos!=N;pos=ans._Find_next(pos)) v.push_back (pos);int res=inf;for (int i=k-1 ;i<v.size ();i++){ res=min (res,m+v[i]-v[i-k+1 ]); } if (res==inf)cout<<-1 <<endl;else cout<<res<<endl; } }
总结:对于以上字符串匹配需要注意的细节:下标移动和边界的计算(可以设未知数)。不合法情况导致越界需要提前特判退出。最后一题那个找1的上界没想清楚
bitset配合莫队在数据结构方面的应用