E.Mysterious XOR Operation
位运算

思路
观察两个数\(a,b\),研究二者神秘异或后第\(pos\)位对答案的贡献:
设\(pos\)位上二者的\(bit\)不同,记二者\(0\sim pos-1\)位上\(1\)的个数为\(cnt_{a},cnt_{b}\)
- 则\(a\wedge b\)在\(0\sim pos\)位上的\(1\)个数为\(cnt_{a}-k+cnt_{b}-k\),其中\(k\)为\(a,b\)在\(0\sim pos\)位上同时为\(1\)的个数
- 则\(a\wedge b\)在\(0\sim pos\)位上的\(1\)个数与\(cnt_{a}+cnt_{b}\)的奇偶性直接挂钩
创建三维数组\(cnt[28][2][2]\),其中\(cnt[pos][bit][1/0]\)表示当前遍历到第\(pos\)位,当前位为\(bit\),\(0\sim pos-1\)位上\(1\)的个数\(\&1\)后为\(1/0\)的个数
每次插入一个新的数,都更新\(cnt[pos][bit][1/0]\),让贡献加上\(cnt\)乘以当前\(pos\)的十进制数即可
代码实现
cpp
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
//#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
int cnt[28][2][2];
void eachT() {
int n;cin>>n;
ll ans=0;
rep(i,1,n){
int x;cin>>x;
int cnt1=0;
rep(pos,0,27){
int nowbit=(x>>pos)&1;
ans+=1ll*(1<<pos)*cnt[pos][nowbit^1][cnt1&1];
cnt[pos][nowbit][cnt1&1]++;
if(nowbit)cnt1++;
}
}
cout<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t = 1;
//cin >> t;
while (t--) { eachT(); }
}
J.Fastest Coverage Problem
二分 #BFS #优先队列 #曼哈顿距离

思路
要求最短时间,可以考虑二分答案判断合法性
对于每一个确定的时间\(tim\),我们可以求得当前未染色的点的曼哈顿距离最大的四个边界:
\[(x+y){max} ,(-x-y){max},(x-y){max},(y-x){max} \]
由此可以算得未染色点所构成的区域的直径:
\[\begin{align} &d_{1}=(x+y){max}+(-x-y){max}\\ \\ &d_{2}=|(x-y){max}+(y-x){max}|\\ \\ &d=max\{ d_{1},d_{2} \} \end{align} \]
\(2\times tim\)表示在\(tim=0\)时某个位置染成黑色可以染黑区域的直径,因此比较\(2\times tim\)与\(d\)即可判断当前二分的\(tim\)是否合法
至于如何求得每个时间点的四个边界值,我们可以开四个优先队列\(priority\_{queue<\!int\!>}q[4]\)进行储存
带着时间戳对进行BFS,每当当前的时间发生了变化,就去更新四个优先队列,将队列中已经被染色的\(top\)踢出
最后,需要特判全零的情况,此时的答案即为\(\left\lfloor \frac{n}{2} \right\rfloor+\left\lfloor \frac{m}{2} \right\rfloor\)
代码实现
cpp
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<bitset>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
//#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
const ll inf = 1e7 + 5;
const int N = 2e5 + 5;
int n, m;
struct node {
ll val, x, y;
bool operator<(const node& t)const {
return val < t.val;
}
};
priority_queue<node>q[4];//da根堆
struct Tim {
ll d[4];
}t[N];
unordered_map<int, unordered_map<int, bool>>vis, a;
struct node2 {
ll x, y, tim;
};
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
bool check(int tim) {
int d1 = abs(t[tim].d[0] + t[tim].d[3]);
int d2 = abs(t[tim].d[2] + t[tim].d[1]);
int dd = max(d1, d2);
if (dd > 2 * tim)return 0;
return 1;
}
void eachT() {
cin >> n >> m;
int cnt0=0;
deque<node2>dq;
rep(x, 1, n) {
rep(y, 1, m) {
a[x][y];
cin >> a[x][y];
if (!a[x][y]) {
q[0].push({ x + y,x,y });
q[1].push({ x - y,x,y });
q[2].push({ y - x,x,y });
q[3].push({ -x - y,x,y });
cnt0++;
}
else {
dq.push_back({ x,y,0 });
vis[x][y] = 1;
}
}
}
if(cnt0==n*m){
cout<<(n/2)+(m/2)<<'\n';
return;
}
rep(j, 0, 3) {
if(!q[j].empty())t[0].d[j] = q[j].top().val;
}
int nowt = 0,tmax=0;
while (!dq.empty()) {
int x = dq.front().x, y = dq.front().y, tim = dq.front().tim;
dq.pop_front();
tmax = max(tmax, tim);
if (tim > nowt) {
nowt = tim;
rep(j, 0, 3) {
if (q[j].empty())continue;
while (!q[j].empty()&&vis[q[j].top().x][q[j].top().y])q[j].pop();
if(!q[j].empty())t[nowt].d[j] = q[j].top().val;
}
}
rep(i, 0, 3) {
int nx = x + dx[i], ny = dy[i] + y;
if (nx > n || nx<1 || ny>m || ny < 1 || vis[nx][ny])continue;
vis[nx][ny] = 1;
dq.push_back({ nx,ny,nowt + 1 });
}
}
int l = -1, r =tmax+ 1;
while (l + 1 < r) {
int mid = l + r >> 1;
if (check(mid))r = mid;
else l = mid;
}
cout << r << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t = 1;
//cin >> t;
while (t--) { eachT(); }
}
K.Perfect Journey
dfs #FWT #FMT #数学
题目

思路
由于给出的\(m\leq 22\),所以很容易想到需要对覆盖哪些特定道路进行状态压缩:
第\(pos\)位为\(1\)代表第\(pos\)个特定道路已经被覆盖,反之没有被覆盖
如何获取每个简单路径的状态掩码\(mask\)呢?
可以在\(node\)结构体中开一个\(mk(mask)\)变量,代表当前点\(u\)到根节点\(root\)的简单路径上覆盖了哪些特定道路,跑dfs记录即可
那么\(u\to v\)的简单路径所覆盖的特定道路则为\(mk_{u}\wedge mk_{v}\),\(lca\to root\)这一段路上共有的特定道路会因异或运算而消去
接下来提供两种计算答案的思路,分别是\(FWT\)+容斥、\(FWT\)+二分:
- 解法一:\(FWT\)+容斥(std解法)
- 创建数组\(dp_{i}[1\!<\!<23]\),其中\(dp_{i}[mask]\)代表在选择了\(i\)条旅游路线后,状态为\(mask\)的子集的方案排列数
\[\begin{align} &FWT_{or}卷积:\ \ [x^m]FWT_{or}(f)=\sum_{i|m=m}f_{i}\\ \\ &IFWT_{or}卷积:\ \ IFWT_{or}(FWT_{or}(f))=f\\ \\ \end{align} \]
\([x^m]A\)代表多项式\(A\)中\(x^m\)的系数
- 正常\(FWT\)做法:
\[\begin{align} &ans=[x^m]IFWT[\ FWT(dp)·FWT(dp)·\dots·FWT(dp)\ ]=[x^m]IFWT[\ FWT^{cnt}(dp)\ ] \end{align} \]
- 对\(dp\)数组\(FWT\)卷积的结果累乘\(cnt\)次就代表选择了\(cnt\)条旅游路线
- 遍历\(cnt\)来尝试选择的数量
- 使用快速幂来计算\(FWT^{cnt}(dp)\)
- 使用\(IFWT(dp)\)来还原\(dp\)数组,\([x^{one}]IFWT(dp)\)就是当前\(cnt\)的答案
- 判断\(dp[one]\)是否非零,是则直接输出即可
cpp
void fwt_or(int *a, int op, int len) {
for (int i = 1; i < len; i <<= 1) {
for (int p = i << 1, j = 0; j < len; j += p) {
rep(k, 0, i - 1) {
a[i + j + k] = (a[i + j + k] + a[j + k] * op + mod) % mod;
}
}
}
}
//eachT():
fwt_or(dp, 1, len);
rep(i, 0, len - 1) dp1[i] = dp[i];
rep(cnt, 1, M) {
rep(mask, 0, len - 1) tmp[mask] = qpow(dp1[mask], cnt);
fwt_or(tmp, -1, len);
int ans = tmp[len - 1];
if (ans) {
cout << cnt << " " << ans * inv[cnt] % mod << '\n';
return;
}
}
cout << -1 << '\n';
- 但是由于每次确定一个\(i\),\(FWT\)的复杂度都是\(m·2^m\),最终复杂度为\(m^2·2^m\)会TLE ,所以需要单独对于\([x^i]\)系数进行讨论
- 容斥+\(FWT\)做法
- 由于我们只需要求\([x^{one}]IFWT(dp)\),即全集项的系数,因此没有必要每一次都对整个\(dp\)数组做\(IFWT\)变换(\(m·2^m\))
- 注意到上述正常做法实际上对于一个确定的\(cnt\)仅需要进行一次\(FWT\)与\(IFWT\),中间的\(cnt\)项为累乘,可以使用快速幂计算,因此降低时间复杂度的关键就在于如何免去\(IFWT\)的过程
- 因此这里需要对单一项进行\(IFWT\),这实际上就是莫比乌斯反演:
- 容斥+\(FWT\)做法
\[\begin{align} &f(S)=\sum_{T\in S}(-1)^{|S-T|}g(T)\\ \\ &[x^{one}]IFWT(dp)=\sum_{mask|one=one}(-1)^{popcount(mask\wedge one)}dp[mask] \end{align} \]
- 其中\(one\)对应全集\(S\),\(mask\)对应子集\(T\),\(popcount(x)\)代表二进制数\(x\)中的1个数,\(mask\wedge one\)对应全集减去子集的状态\(|S-T|\)
- 由此可以枚举\(mask\)实现莫比乌斯反演,复杂度为\(2^m\),总复杂度来到\(m·2^m\)可以接受
- 最后输出的时候需要除以\(!cnt\)以将排列数转换为组合数
cpp
while(m--){
cnt++;
int ans=0;
rep(mk,0,one){
int sign=(__builtin_popcount((mk^one))&1)?-1:1;
ans+=sign*qpow(dp[mk],cnt);
ans=(ans+mod)%mod;
}
if(ans){
cout<<cnt<<" "<<(ans*inv[cnt])%mod<<'\n';return;
}
}
cout<<-1<<'\n';
- 解法二:\(FWT\)+二分(来自\(fisher\ go\)大佬)
- 由于要求最少的路径选择数,所以可以二分这个\(cnt\)
- 对于一个确定的\(cnt\),转换过程如下:
\[\begin{align} &dp[mask]:一定能覆盖状态mask的路径数\\ \\ &dp[mask]=[x^{mask}]FWT(dp):一定能覆盖状态mask的子集的路径数\\ \\ &dp[mask]=C_{dp[mask]}^{num}:一定能覆盖状态mask的子集的路径数中选num个的组合数\\ \\ &dp[mask]=[x^{mask}]IFWT(dp):一定能覆盖状态mask的路径数中选num个 的组合数 \end{align} \]
- 最终的答案即为\(dp[one]\)
代码实现
\(FWT\)+容斥
cpp
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<map>
#include<iomanip>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
#define int ll
const int N = 2e5 + 5, mod = 998244353;
int n, m, k,tag[N];
struct node {
vector<pair<int,int>>e;//next,id
int mk;
}a[N];
void dfs(int u, int fa, int mk) {
a[u].mk = mk;
for (auto& next : a[u].e) {
int son=next.first,id=next.second;
if (son == fa)continue;
if(tag[id])dfs(son, u, mk|(1<<(tag[id]-1)));
else dfs(son, u, mk);
}
}
int dp[1 << 23], dp1[1 << 23], one;
ll qpow(ll a, ll b) {
ll res = 1;
for (; b; b >>= 1, a = a * a % mod) {
if (b & 1) res = res * a % mod;
}
return res;
}
vector<int>inv,A;
void inv0(){
inv.resize(23);A.resize(23);
inv[0]=1;A[0]=1;
rep(i,1,22){
A[i]=(A[i-1]*i)%mod;
inv[i]=qpow(A[i],mod-2);
}
}
void fwt_or(int *a,int op){
for(int i=1;i<one;i<<=1){
for(int p=i<<1,j=0;j<one;j+=p){
rep(k,0,i-1){
(a[i+j+k]+=a[j+k]*op+mod)%=mod;
}
}
}
}
void eachT() {
cin >> n >> m >> k;
one = (1 << m) - 1;
rep(mk,0,one)dp[mk]=0,dp1[mk]=0;
rep(i, 1, n) {
a[i].e.clear();
tag[i]=0;
}
rep(i, 1, n - 1) {
int u, v; cin >> u >> v;
a[u].e.push_back({v,i});
a[v].e.push_back({u,i});
}
rep(i, 1, m) {
int x; cin >> x;
tag[x]=i;
}
dfs(1, 0, 0);
rep(i, 1, k) {
int s, t; cin >> s >> t;
int mk=a[s].mk^a[t].mk;
dp[mk]++;
}
int cnt=0;
rep(i, 0, m - 1) {
rep(mk, 0, one) {
if ((1 << i) & mk) {
dp[mk] += dp[mk ^ (1 << i)];
dp[mk] %= mod;
}
}
}
while(m--){
cnt++;
int ans=0;
rep(mk,0,one){
int sign=(__builtin_popcount((mk^one))&1)?-1:1;
ans+=sign*qpow(dp[mk],cnt);
ans=(ans+mod)%mod;
}
if(ans){
cout<<cnt<<" "<<(ans*inv[cnt])%mod<<'\n';return;
}
}
cout<<-1<<'\n';
}
signed main() {
inv0();
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
//cin>>t;
while (t--)eachT();
}
\(FWT\)+二分
cpp
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<map>
#include<iomanip>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
#define int ll
const int N = 2e5 + 5, mod = 998244353;
int n, m, k,tag[N];
struct node {
vector<pair<int,int>>e;//next,id
int mk;
}a[N];
void dfs(int u, int fa, int mk) {
a[u].mk = mk;
for (auto& next : a[u].e) {
int son=next.first,id=next.second;
if (son == fa)continue;
if(tag[id])dfs(son, u, mk|(1<<(tag[id]-1)));
else dfs(son, u, mk);
}
}
ll qpow(ll a, ll b) {
a %= mod; ll res = 1;
while (b) {
if (b % 2) { res *= a; res %= mod; }
a *= a; a %= mod; b >>= 1;
}
return res % mod;
}
vector<ll>A, inv;
void inv0(ll len) {
A.resize(len + 1), inv.resize(len + 1);
A[0] = 1, inv[0] = 1;
rep(i, 1, len) {
A[i] = A[i - 1] * i % mod;
inv[i] = qpow(A[i], mod - 2);
}
}
ll C(ll n, ll m) {
if (m > n)return 0;
return A[n] * inv[m] % mod * inv[n - m] % mod;
}
int dp[1 << 23], dp1[1 << 23], one;
void see2(int x) {
while (x) {
cout << (x & 1);
x >>= 1;
}
}
pair<bool,ll>check(int num) {//judge,val
rep(mk, 0, one) {
dp1[mk] = C(dp[mk], num);
}
rep(i, 0, m - 1) {
rep(mk, 0, one) {
if ((1 << i) & mk) {
dp1[mk] -= dp1[mk ^ (1 << i)];
dp1[mk] = (dp1[mk] + mod) % mod;
}
}
}
if (dp1[one])return {1,dp1[one]};
return {0,0};
}
void eachT() {
cin >> n >> m >> k;
one = (1 << m) - 1;
rep(i, 1, n) {
a[i].e.clear();
tag[i]=0;
}
rep(i, 1, n - 1) {
int u, v; cin >> u >> v;
a[u].e.push_back({v,i});
a[v].e.push_back({u,i});
}
rep(i, 1, m) {
int x; cin >> x;
tag[x]=i;
}
dfs(1, 0, 0);
rep(i, 1, k) {
int s, t; cin >> s >> t;
int mk=a[s].mk^a[t].mk;
dp[mk]++;
}
rep(i, 0, m - 1) {
rep(mk, 0, one) {
if ((1 << i) & mk) {
dp[mk] += dp[mk ^ (1 << i)];
dp[mk] %= mod;
}
}
}
int l = 0, r = m + 1;
ll ans=0;
while (l + 1 < r) {
int mid = l + r >> 1;
pair<bool,ll>pd=check(mid);
if (pd.first)r = mid,ans=pd.second;
else l = mid;
}
if (r == m + 1) {
cout << -1 << '\n'; return;
}
cout << r << " " << ans<< '\n';
}
signed main() {
inv0(2e5 + 5);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
//cin>>t;
while (t--)eachT();
}