题解 神必的集合
首先发现这种集合就是一个线性空间
于是问题变为本质不同线性基计数
- 对于一个数查询其是线性基内排名:
将线性基转为最小表示,将每个主元位置在要查询的数的二进制表示下对应的位置单独拎出来组成一个二进制数,即为排名
证明考虑这样的实际意义即可
将 \(\{y_1\cdots y_m\}\) 做线性基,发现要求的线性基一定是其母集
那现在我们需要考虑的就是在满足题设条件的前提下再确定一些主元的方案数
考虑 DP,令 \(f_{i, j}\) 为从低到高考虑到第 \(i\) 位,已经选定了 \(j\) 个主元的方案数
若这个位置已经是主元了,只有一种转移
否则可以考虑这个位置是不是主元
分类讨论可知一个位置可以是主元的条件是每个 \(x_i-1\) (这里 -1 是因为 x 是从 1 开始的)在这一位上与 \(y_i\) 相同
我说我不想写一遍转移方程了可以嘛
于是复杂度(我写的)\(O(n^2m)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define fir first
#define sec second
#define pb push_back
#define ll long long
#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n, m;
int x[N], y[N];
bool any_useful;
pair<int, int> g[N];
const ll mod=998244353;
namespace force{
bool vis[N], is_need[N];
int cnt[N], need[N], max_need;
vector<set<int>> uni;
void calc(vector<int> tem) {
memset(vis, 0, sizeof(vis));
vector<int> ans;
for (auto it:tem) vis[it]=1;
for (int i=0; i<tem.size(); ++i) {
for (auto it:ans) if (!vis[tem[i]^it]) tem.pb(tem[i]^it), vis[tem[i]^it]=1;
ans.pb(tem[i]);
}
ans.pb(0);
set<int> t;
for (auto it:ans) t.insert(it);
uni.pb(t);
}
void check(set<int> s) {
vector<int> tem;
for (auto it:s) tem.pb(it);
for (int i=0; i<tem.size(); ++i)
for (int j=0; j<tem.size(); ++j)
assert(s.find(tem[i]^tem[j])!=s.end());
}
void solve() {
int lim=1<<(1<<n);
for (int s=0; s<lim; ++s) {
vector<int> tem;
for (int i=0; i<(1<<n); ++i) if (s&(1<<i)) tem.pb(i);
calc(tem);
}
sort(uni.begin(), uni.end());
uni.erase(unique(uni.begin(), uni.end()), uni.end());
// cout<<"---uni---"<<endl;
// for (auto s:uni) {
// ++cnt[s.size()];
// cout<<"("<<s.size()<<") "; for (auto it:s) cout<<setw(3)<<it<<' '; cout<<endl;
// }
// for (int i=1; i<=1<<(1<<n); i<<=1) cout<<"siz: "<<i<<' '<<cnt[i]<<endl;
int ans=0;
for (int i=1; i<=m; ++i) is_need[g[i].fir]=1, need[g[i].fir]=g[i].sec, max_need=max(max_need, g[i].fir);
for (auto& s:uni) {
int now=0;
if (s.size()<max_need) goto jump;
for (auto it:s) if (is_need[++now] && it!=need[now]) goto jump;
++ans;
jump: ;
}
if (!any_useful) ++ans;
cout<<ans<<endl;
}
}
namespace task{
ll base[N], pw[110], f[110][110], ans;
void insert(int t) {
for (int i=63; ~i; --i) if (t&(1ll<<i)) {
if (!base[i]) {base[i]=t; return ;}
else t^=base[i];
}
}
void solve() {
pw[0]=1;
for (int i=1; i<=65; ++i) pw[i]=pw[i-1]*2%mod;
for (int i=1; i<=m; ++i) insert(y[i]);
for (int i=0; i<64; ++i) if (base[i])
for (int j=i+1; j<64; ++j)
if (base[j] && base[j]&(1ll<<i)) base[j]^=base[i];
// for (int i=0; i<64; ++i) if (base[i]) cout<<base[i]<<endl;
f[0][0]=1;
for (int i=0; i<n; ++i) {
for (int j=0; j<=i; ++j) {
bool able=1;
for (int k=1; k<=m; ++k) if ( (((x[k]-1)&(1ll<<j))?1:0) ^ ((y[k]&(1ll<<i))?1:0) ) able=0;
// cout<<"ij: "<<i<<' '<<j<<' '<<able<<endl;
// cout<<"f: "<<f[i][j]<<endl;
if (base[i]) {
if (able) f[i+1][j+1]=(f[i+1][j+1]+f[i][j])%mod;
}
else {
f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
if (able) f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*pw[i-j])%mod;
}
}
}
int lim=0;
for (int i=1; i<=m; ++i)
for (int j=63; ~j; --j)
if ((x[i]-1)&(1ll<<j)) {lim=max(lim, j); break;}
// cout<<"lim: "<<lim<<endl;
for (int i=lim+1; i<=n; ++i) ans=(ans+f[n][i])%mod; //, cout<<"f: "<<n<<' '<<i<<' '<<f[n][i]<<endl;
if (!any_useful) ++ans;
cout<<ans<<endl;
}
}
signed main()
{
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
n=read(); m=read();
// for (int i=1; i<=m; ++i) g[i].fir=read(), g[i].sec=read();
for (int i=1; i<=m; ++i) {
x[i]=read(), y[i]=read();
if (!y[i] && x[i]!=1) {puts("0"); return 0;}
if (y[i]) any_useful=1;
}
// force::solve();
task::solve();
return 0;
}