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\)的十进制数即可
代码实现
#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},(y-x)_{max}
\]
由此可以算得未染色点所构成的区域的直径:
&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\)
代码实现
#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\)的子集的方案排列数
&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\)做法:
&ans=[x^m]IFWT[\ FWT(dp)·FWT(dp)·\dots·FWT(dp)\ ]=[x^m]IFWT[\ FWT^i(dp)\ ]
\end{align}
\]
- 对\(dp\)数组\(FWT\)卷积\(cnt\)次就代表选择了\(cnt\)条旅游路线
- 遍历\(cnt\)来尝试选择的数量
- 使用快速幂来计算\(FWT^{cnt}(dp)\)
- 使用\(IFWT(dp)\)来还原\(dp\)数组,\([x^{one}]IFWT(dp)\)就是当前\(cnt\)的答案
- 判断\(dp[one]\)是否非零,是则直接输出即可
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\)做法
&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$以将排列数转换为组合数
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\),转换过程如下:
&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\)+容斥
#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\)+二分
#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();
}
2025牛客多校第五场 K.完美旅程 J.最快覆盖问题 E.神秘异或操作 个人题解的更多相关文章
- 2020牛客多校第八场K题
__int128(例题:2020牛客多校第八场K题) 题意: 有n道菜,第i道菜的利润为\(a_i\),且有\(b_i\)盘.你要按照下列要求给顾客上菜. 1.每位顾客至少有一道菜 2.给顾客上菜时, ...
- 牛客多校第五场 F take
链接:https://www.nowcoder.com/acm/contest/143/F来源:牛客网 题目描述 Kanade has n boxes , the i-th box has p[i] ...
- 牛客多校第五场 J:Plan
链接:https://www.nowcoder.com/acm/contest/143/J 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言524 ...
- 牛客多校第五场-D-inv
链接:https://www.nowcoder.com/acm/contest/143/D来源:牛客网 题目描述 Kanade has an even number n and a permutati ...
- 牛客多校第五场 F take 期望转化成单独事件概率(模板) 树状数组
链接:https://www.nowcoder.com/acm/contest/143/F来源:牛客网 Kanade has n boxes , the i-th box has p[i] proba ...
- 牛客多校第五场 E room 二分图匹配 KM算法模板
链接:https://www.nowcoder.com/acm/contest/143/E来源:牛客网 Nowcoder University has 4n students and n dormit ...
- 字符串dp——牛客多校第五场G
比赛的时候脑瘫了没想出来..打多校以来最自闭的一场 显然从s中选择大于m个数组成的数必然比t大,所以只要dp求出从s中选择m个数大于t的方案数 官方题解是反着往前推,想了下反着推的确简单,因为高位的数 ...
- 【魔改】树状数组 牛客多校第五场I vcd 几何+阅读理解
https://www.nowcoder.com/acm/contest/143/I vc-dimension 题解:分三种情况,组合数学算一下,其中一种要用树状数组维护 技巧(来自UESTC):1. ...
- 2018牛客多校第五场 H.subseq
题意: 给出a数组的排列.求出字典序第k小的b数组的排列,满足1<=bi<=n,bi<bi+1,a[b[i]]<a[b[i+1]],m>0. 题解: 用树状数组倒着求出以 ...
- 2018牛客多校第五场 E.room
题意: 一共有n个宿舍,每个宿舍有4个人.给出第一年的人员分布和第二年的人员分布,问至少有多少人需要移动. 题解: 对于第一年的每个宿舍,向今年的每种组合连边.流量为1,费用为(4 - 组合中已在该宿 ...
随机推荐
- 张高兴的大模型开发实战:(六)在 LangGraph 中使用 MCP 协议
目录 什么是 MCP 协议 MCP 协议与 API 调用的区别 MCP 协议的连接方式 SSE(Server-Sent Events) stdio(标准输入输出) 在 LangGraph 中使用 MC ...
- Streamlit入门:10分钟搭建数据可视化界面
一.Streamlit简介 Streamlit是一个用Python构建数据应用的开源框架,它能让我们快速创建漂亮的数据可视化界面.本文将通过一个简单的示例,展示如何使用Streamlit构建数据可视化 ...
- HTTP请求中Content-Type的取值讲解
在Http请求中,我们每天都在使用Content-type来指定不同格式的请求信息(MediaType),下面先看看请求信息的定义,MediaType,即Internet Media Type,互 ...
- 题解:AT_arc073_d [ARC073F] Many Moves
题目链接:link. 题意已经挺简易了,直接上思路吧. 我们设 \(f_{i,j}\) 表示当前在第 \(i\) 个时刻,一个棋子在 \(x_i\) 位置,另一个棋子在 \(j\) 位置的最小代价之和 ...
- BeatifulSoup
BeatifulSoup (1)介绍 Beautiful Soup是Python库,用于解析HTML和XML文档.它提供简单而强大的工具,帮助用户从网页中提取数据.通过查找元素.遍历文档树和处理编码问 ...
- 支付宝小程序IDE版本迭代异常
前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验也挺棒的,公司项目就是主推uni-app 现公司今年准备新开一 ...
- 如何识别SQL Server中需要添加索引的查询
引言 在数据库性能优化中,索引是提升查询速度最有效的手段之一.然而,不恰当的索引会降低写操作性能并增加存储开销.作为DBA,我们经常面临这样的挑战:如何精准定位哪些查询真正需要添加索引? 本文将分享几 ...
- C# 打开edge浏览器并访问指定的url
using System.Diagnostics; // ... Process.Start("msedge", "https://www.luocore.com&quo ...
- c# 判断程序是否Debug模式还是Release模式 调试模式,开发模式,发布模式
https://blog.csdn.net/qq_37664403/article/details/118747195 1.Debug模式,Release模式#if DEBUGConsole.Writ ...
- CF1946C Tree Cutting 题解
CF1946C Tree Cutting 容易发现,如果连通块含有节点数的最小值为 \(x\),并且使用的刀数多于或等于 \(k\),那么 \(x\) 一定可以成为最后的结果.原因是我们可以通过减少一 ...