高维前缀和(SOSDP)
更新日志
概念
高维前缀和的名字已经很显然了,不做过多讲解。
思路
基本形式
我们较为熟知的二维前缀和,通常情况下使用了容斥的思想。事实上,更通常的多维前缀和形式往往长下面这样:
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=1;k<n;k++){
s[i][j][k]+=s[i][j][k-1];
}
}
}
for(int i=0;i<n;i++){
for(int j=1;j<n;j++){
for(int k=0;k<n;k++){
s[i][j][k]+=s[i][j-1][k];
}
}
}
for(int i=1;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
s[i][j][k]+=s[i-1][j][k];
}
}
}
这么写是正确的,但是很繁琐。通常情况下,SOSDP
应该是不会只有这么粗暴的解决方式的。
通常形式
一般情况下,我们都可以使用状态压缩来解决多维前缀和问题。
更具体地,这一类问题往往都与位运算有关,比如要求你找出集合内与为 \(0\) 的数对。或者用一些形象化的描述,比如每个集合内都有一些物品,让你找出选一些集合使得拥有所有类型的物品的方案数,等等。
我们以第一个例子为例,详见例题1。
我们可以对于每个状态找出他可选的一种与其与为 \(0\) 的数,也就是找出为 \((1<<m)-1\) 的子集的数(用语不太标准,意思是 \(a\&b=a\),\(a\) 为 \(b\) 子集),那么就可以考虑前缀和解决这种为什么什么的子集的问题。
具体的,\(a\) 可以具象化为每一位都小于等于\(b\) 的数,这就很符合多维前缀和的形式了。
这时候,如果还使用基本形式,那码量望而生畏,事实上,有一种更简洁的写法:
for(int i=0;i<M;i++){
for(int j=0;j<1<<M;j++){
if((j>>i&1)&&(f[j^(1<<i)])){
f[j]=f[j^(1<<i)];
}
}
}
这段代码仅适用于这道题目,但是其思路是通用的,我们可以把这么多位都压缩到同一个数中作为状态。
例题
Compatible Numbers
用作了上面的例题,这里就不多讲解了,本身也很简单。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define dprint(x) cout<<#x<<"="<<x<<"\n"
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7/*998244353*/;
const int N=1e6+5,M=22;
int n;
int a[N],f[1<<M];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
f[a[i]]=a[i];
}
for(int i=0;i<M;i++){
for(int j=0;j<1<<M;j++){
if((j>>i&1)&&(f[j^(1<<i)])){
f[j]=f[j^(1<<i)];
}
}
}
for(int i=1;i<=n;i++){
if(f[(1<<M)-1^a[i]])cout<<f[(1<<M)-1^a[i]];
else cout<<-1;
cout<<" ";
}
return 0;
}
KOŠARE
个人语言表述不够清晰,附上一份个人认为讲的不错的博客:
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define dprint(x) cout<<#x<<"="<<x<<"\n"
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7/*998244353*/;
const int N=1e6+5,M=20;
int n,m;
ll s[1<<M];
int st[N];
ll ans;
ll qpow(ll a,ll b){
ll res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
int j,k;
for(int i=1;i<=n;i++){
cin>>k;
st[i]=0;
while(k--){
cin>>j;
st[i]|=1<<j-1;
}
s[st[i]]++;
}
for(int i=0;i<m;i++){
for(int j=0;j<1<<m;j++){
if(j&1<<i)s[j]=(s[j]+s[j^1<<i])%mod;
}
}
for(int t=0;t<1<<m;t++){
s[t]=(qpow(2,s[t])-1+mod)%mod;
int pd=__builtin_popcount(t);
if((pd&1)==(m&1))ans=(ans+s[t])%mod;
else ans=(ans-s[t]+mod)%mod;
}
cout<<ans;
return 0;
}