2025牛客多校第五场 K.完美旅程 J.最快覆盖问题 E.神秘异或操作 个人题解

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\),这实际上就是莫比乌斯反演:

\[\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();
}
相关推荐
Always_away2 天前
26考研|数学分析:重积分
笔记·学习·考研·数学
屈臣3 天前
AtCoder Beginner Contest 417 (A-E题解)
动态规划·dfs·二分
Tisfy3 天前
LeetCode 2411.按位或最大的最小子数组长度:一次倒序遍历
数据结构·算法·leetcode·题解·位运算·遍历
Espresso Macchiato6 天前
Leetcode 3629. Minimum Jumps to Reach End via Prime Teleportation
bfs·广度优先遍历·leetcode medium·leetcode 3629·leetcode周赛460·质数求解·质因素分解
老马啸西风13 天前
java 位运算转换 bit operator convert
java·开发语言·算法·leetcode·面试·力扣·位运算
adam_life13 天前
2023年CSP入门级第二轮第四题——旅游巴士
旅游·dijkstra·优先队列·宽搜
一只小蒟蒻15 天前
DFS 迷宫问题 难度:★★★★☆
算法·深度优先·dfs·最短路·迷宫问题·找过程
w909518 天前
【9】斯特林数学习笔记
数学·学习笔记
墨风如雪19 天前
8B 模型吊打 671B?数学证明界“卷王”Goedel-Prover-V2 来了!
数学·aigc