专项训练5
搜索专题
1. CF1491E Fib-tree
link:https://www.luogu.com.cn/problem/CF1491E
一开始想dp,发现不可做,瞪了半天只看出一个性质:每个f[n]的子树只会被分为f[n-1]和f[n-2]这一种分割情况。然后这两部分在分别分下去,都有同样的性质。其实思路是正确的。然后我觉得复杂度很高,我要找到分割的边,还要两边不断递归,于是就没有往深想了。题解就是这么写的,在当前子树u下割一个f[n-1]或f[n-2],找到割的点p,然后继续在p和u里分别递归。注意这个递归的根节点是不用管的。复杂度的话我还不是很会证T^T,先放个题解说的吧。
这个也很好证明,我们把递归过程当成一棵树,首先这是一棵二叉树(尽管这个性质没用),接着我们在每一个节点都执行了一个递归操作时间复杂度为 O(siz) ,而由于我们的每一层节点之和加起来就是一个原树大小,那么我们一层的时间复杂度就是 O(n) ,斐波那契在 2×10^5 以内拆下来最多 26 层,时间复杂度得证。
2. CF1423L Light switches
link:https://www.luogu.com.cn/problem/CF1423L
折半搜索很显然。但是写着写着就感觉不对劲了。主要有两个问题:
- 复杂度。如果直接平均分两半搜的话,复杂度会炸。考虑到只有下半段搜的时候要多带一个d,因此分成前2/3,后1/3这样搜就可以。
- 异或哈希。我一开始想的是像状压存储下每种状态,然后异或,但有1000位显然不可行。当时我就想那hash存一下,再异或(但显然这样不行)。正确的思路应当是每一位赋一个哈希值,一位一位异或下来,如果一个同样的数异或了两次,就相当于没异或过这个数,这样就能准确找到对应的上一半状态及其操作数了。
3. CF995E Number Clicker
link:https://www.luogu.com.cn/problem/CF995E
双向bfs搜索。虽然这样看起来一定会T,但发现逆元这个操作也没有什么特别特殊的性质,求个逆元相当于随机整了个数了,因此3操作相等于随机取个数。因此根据生日悖论,大概搜\(O(\sqrt p)\)次就能搜到。然后就没了。
至于怎么写嘛,我写得特别丑((,就是把两个普通的bfs合并起来,一次解决两个数,记录前驱后继,找到之后就break。
点击查看代码
q1.push(u);
q2.push(v);
bool flag=0;
mp[u]=0, mp1[v]=0;
while(!flag)
{
while(q1.size()&&q2.size())
{
int x=q1.front(), y=q2.front();
q1.pop(), q2.pop();
int num=(x+1)%p;
if(mp.find(num)==mp.end())
{
mp[num]=x;
q1.push(num);
}
num=(x-1+p)%p;
if(mp.find(num)==mp.end())
{
mp[num]=x;
q1.push(num);
}
num=qpow(x, p-2)%p;
if(mp.find(num)==mp.end())
{
mp[num]=x;
q1.push(num);
}
num=(y-1+p)%p;
if(mp1.find(num)==mp1.end())
{
mp1[num]=y;
q2.push(num);
}
if(mp.find(num)!=mp.end())
{
flag=1;
ans=num;
break;
}
num=(y+1)%p;
if(mp1.find(num)==mp1.end())
{
mp1[num]=y;
q2.push(num);
}
if(mp.find(num)!=mp.end())
{
flag=1;
ans=num;
break;
}
num=qpow(y, p-2)%p;
if(mp1.find(num)==mp1.end())
{
mp1[num]=y;
q2.push(num);
}
if(mp.find(num)!=mp.end())
{
flag=1;
ans=num;
break;
}
}
}
线段树专题
1. 小白逛公园
link:https://www.luogu.com.cn/problem/P4513
太典了。我第一次交还WA了。原因竟是询问时只合并了ans。警钟长鸣。
2. 区间加区间 sin 和
link:https://www.luogu.com.cn/problem/P6327
竟然扯到了和差角公式。完全忘了。。。
然后维护一个区间sin和 和 区间cos和 即可。记得修改lazy是+=!!
3. [USACO08FEB] Hotel G
link:https://www.luogu.com.cn/problem/P2894
怎么说呢,这题做得不好,想太复杂了。深刻反思。当时一直想不出怎么样处理询问(如果这题不放线段树题单里我可能压根儿不会往这方面想)
考虑怎么处理询问。如果我们知道了每个区间的最长连续空房个数,就可以随着线段树不断分治下去,找到连续段最左边的那个位置。分的时候无非就三种情况:
- 最长连续段在左区间
- 最长连续段在右区间
- 横跨左右区间,可通过维护的suf,pre直接锁定位置
上面那个suf和pre就是用来辅助求出最长连续段,跟最大子段和差不多。注意总共有三种懒标记。一种无懒标记,一种全改为空,一种全改为有人住。要注意及时更新。还有如果输出值为0后就不需要入住房间了,否则能收获17pts的好成绩=)
总的来说呢,写线段树一定要明确目的是什么,再来想怎么通过维护的东西来求出想要的答案。不能因为题面里的某些话就认为应该套路地做,别想偏了。
4. CF438D The Child and Sequence
link:https://www.luogu.com.cn/problem/CF438D
感觉这种题还是得做过类似的才会。就是对于x,每mod一次都会变为比x/2更小的数。最多mod log (x) 次就mod完了。 因此我们可以暴力的给这些数取模,最多nlogn次。为了减少不必要的取模,考虑维护区间最大值,这样子就保证了一个比p小的数不会被取模,只让那些能取的才取。保证了复杂度。整体来说是不难的。
5. CF1557D Ezzat and Grid
link:https://www.luogu.com.cn/problem/P4198
早上没睡醒。想了个dp。不会用线段树优化。不知道自己卡在哪里了。看了眼题解。傻逼。好像忘了离散化就觉得不能线段树了。哈哈哈哈哈哈笑了。
所以像这种跟相连1的个数无关的直接离散化就好了。然后这题就没难度了。直接在每一行上的区间求最大值+更改。哦记得线段树节点要多记一个行数。pushup的时候要更新上!!!。
6. 楼房重建
link:https://www.luogu.com.cn/problem/P4198
傻子题。没想到直接拿线段树维护答案,以为是通过线段树查询最大值辅助求出答案>︿<
然后点开题解瞬间就会了,直接维护最大值和长度,pushup时左右区间递归合并就行了。
有两个坑:
- 能看到这栋楼是必须比前一栋楼高,也就是<=是不合法的,必须>
- pushup的递归中,当左区间比要找的高度完全高时,右区间的长度不是tr[rid].len!!是tr[id].len-tr[lid].len!!!tr[rid].len是只针对右区间的,前面的一部分可能比左区间小。
int query(int id,double val)
{
if(tr[id].l==tr[id].r)
{
if(tr[id].maxx<=val) return 0;
return 1;
}
if(tr[lid].maxx>val) return tr[id].len-tr[lid].len+query(lid, val);//就是这里
else return query(rid, val);
}
7. CF833B The Bakery
link:https://www.luogu.com.cn/problem/CF833B
感觉这个题见了好多遍了,但找不到了(如果有谁找到了请告诉我谢谢)。就是能非常显然地推出这个dp,然后非常显然地知道这是线段树优化。然后不知道该咋办了。T^T
首先列出dp式子:\(dp[i][j]=\max(dp[k][j-1]+col(k+1,j))\),注意到k的范围很小,所以n*k的两层循环是可写的。(k在外面,n在里面)。线段树维护的就是\(dp[k][j-1]+col(k+1,j)\)这堆东西。这样子就能直接查询dp[i]的值。考虑怎么维护,对于当前a[i],考虑它对dp值区间的贡献。能看出来这个区间应该是[pre[i]+1,i](其中pre[i]是上一个a[i]的位置),但注意pre[i]+1这个点对应的是dp[pre[i]][j-1]的dp值!!!修改后直接查询即可。每层重构一遍线段树即可。
然后这个题就属于那种典型的「维护决策值」的线段树优化dp。其实还是比较简单的TT但我怎么就是想不出来啊啊啊
8. CF600E Lomsat gelral
link:https://www.luogu.com.cn/problem/CF600E
树上启发式合并。写过原题。结果忘开long long了。。。
dp专题
1. CF868F Yet Another Minimization Problem
link:https://www.luogu.com.cn/problem/CF868F
先去学决策单调性。然后列出此题dp式子:\(dp[i][k]=\min(dp[j][k-1]+cal(j+1,i))\),然后发现cal(k+1,i)是满足四边形不等式的,所以说这个dp式子是满足决策单调性的。因为每次是从j-1层过来的,我们就可以通过整体二分一样的分治思路来处理dp值。然后考虑怎么处理dp[mid][k],也就是怎么计算cal(k+1,i)。这个可以像莫队一样一点点移,复杂度是可以保证的。
坑的点:
- 决策单调性是决策点单调不减,因此[mid+1,r]的决策区间包含mid的决策点。
- 莫队一开始l=1。调了好久55555
2. 「KDOI-11」飞船
link:https://www.luogu.com.cn/problem/P11290
dp式子是相对好列的。设dp[i][j]表示到第i个加油站,速度为j的最小时间。但这个速度如果直接枚举显然是不可能的。注意到x只有{1,2,3,4}这四种可能,考虑枚举每一个的个数。(因为总共的距离不超过1e9,这四个数的次方超过30就没用了),注意到4=2*2,因此直接将4归为2即可。写的时候刷表法更好写一点。加点优化就可以过了。
我当时自己推了个dp,但转移的稍有问题。而且此题最好的写法是将询问和加油站混在一起排序,把询问当作加油站处理。记得dp数组滚掉一维。
3.『MdOI R2』Resurrection
link:https://www.luogu.com.cn/problem/P6383
根本想不出来dp状态。我感觉这种树上dp除了典题我一直掌握的不好。一定要记住:树形dp的状态dp[u]是指u子树的答案!!!从v转移过来,但需要用到u的限制!!!以及还要关注u本身,是什么会影响到u,写进状态里。
然后跟着hint自动机做的,至于那个不能交叉的性质,emm……我大抵是能自己想出来吧。设dp[u][i]表示u节点上面有i个可选的祖先。枚举u限制了几个祖先,根据不能交叉的性质和乘法原理,把儿子的方案数叠加。会发现可以用前缀和优化到O(n^2)。没了。
图论专题
1. CF2057E2 Another Exercise on Graphs (hard version)
link:https://www.luogu.com.cn/problem/CF2057E2
经典trick:二分后,将比答案大的边权设为1,否则设为0,判断最短路是否小于k(是就合法)。常用于第k大/小的问题。
但最短路如果在二分里求复杂度是过不去的,所以我们可以提前预处理出来每种边权为mid时所有点之间的最短路。注意到n很小,考虑用floyd。(题外话)floyd本质是一个dp,本来是有三维的,f[i][j][k]表示中间点为1~i,j到k之间的最短路。因此i一定要在最外层。边权排序后,从前往后扫,每次只会将一条边的边权由1变为0,n2就可以处理完。总复杂度是O(n3+qlogn)。
点击查看代码
#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=405;
int T, n, m, q, dis[maxn], vis[maxn], ans[maxn];
int f[maxn][maxn][maxn];
struct edge{
int u, v, w, id;
}a[300005];
map<int,int> mp;
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
signed main()
{
cin>>T;
while(T--)
{
cin>>n>>m>>q;
int maxx=0, tot=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++) f[0][i][j]=1e9;
}
for(int i=1;i<=n;i++) f[0][i][i]=0;
for(int i=1;i<=m;i++)
{
int u, v, w;
cin>>u>>v>>w;
a[i].u=u, a[i].v=v, a[i].w=w;
a[i].id=i;
f[0][u][v]=f[0][v][u]=1;
}
sort(a+1, a+m+1, cmp);
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
f[0][i][j]=min(f[0][i][j], f[0][i][k]+f[0][k][j]);
}
}
}
int cnt=0;
for(int i=1;i<=m;i++)
{
if(f[cnt][a[i].u][a[i].v]==0) continue;
cnt++;
ans[cnt]=a[i].w;
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
f[cnt][j][k]=f[cnt-1][j][k];
}
}
f[cnt][a[i].u][a[i].v]=0;
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
f[cnt][j][k]=min(f[cnt][j][k], f[cnt][j][a[i].u]+f[cnt][a[i].v][k]);
f[cnt][j][k]=min(f[cnt][j][k], f[cnt][j][a[i].v]+f[cnt][a[i].u][k]);
}
}
}
while(q--)
{
int x, y, z;
cin>>x>>y>>z;
int l=1, r=cnt;
while(l<r)
{
int mid=(l+r)>>1;
if(f[mid][x][y]<z) r=mid;
else l=mid+1;
}
cout<<ans[l]<<"\n";
}
}
return 0;
}
2. [JSOI2010] 满汉全席
link:https://www.luogu.com.cn/problem/P4171
如果你没学过2-SAT的话,你可能想破头都想不出来;如果你学过2-SAT,这个题就没没有任何难度了。
3. [USACO05JAN] Muddy Fields G
link:https://www.luogu.com.cn/problem/P6062
太久没做二分图相关题了,所以根本没往那方面想。然后翻以前做的题,嗯,真的长得几乎一模一样。
就是非常经典的套路,二分图中行、列转点,点转边。每块泥地都有横/纵两种覆盖方式,要找到最少的木板使得每条边都被选上。然后又因为二分图最小点覆盖=最大匹配数。直接跑匈牙利算法就可以了。
4. CF547D Mike and Fish
link:https://www.luogu.com.cn/problem/CF547D
建图倒是挺容易想的,但是关键我真没学过欧拉回路这个东西啊。好啊,这个图论提单能学很多小知识点的。欧拉路径&欧拉回路
然后建完图,就是给边定向,让所有点的出度入度之差<=1。考虑把这张图变为欧拉回路。会发现一开始行列建完双向变后,就已经可以知道哪些点是奇点了(奇=奇-偶/偶-奇,奇+偶=奇),偶点一定是满足要求的。那我们就把奇点两两配对(奇点正好有偶数个),这样子就形成欧拉回路了。我们先默认初始每条边都是从行连向列,也就是都为红色鱼。dfs时只需将跑过的边打上标记。如果这个这条从行连向列的边没有打过标记,就意味着它走的是反向边,相当于它是蓝色鱼。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n, cur[maxn], d[maxn], maxx, vis[maxn], ans[maxn], fl[maxn], pos[maxn];
struct fish{
int x, y;
}a[maxn];
struct edge{
int nxt, to;
}edge[maxn<<1];
int head[maxn], edgenum=1;
void add(int from,int to)
{
edge[++edgenum].nxt=head[from];
edge[edgenum].to=to;
head[from]=edgenum;
}
void dfs(int u)
{
fl[u]=1;
for(int &i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!vis[i])
{
ans[i]=1;
vis[i]=vis[i^1]=1;
dfs(v);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].x>>a[i].y;
maxx=max(maxx, max(a[i].x, a[i].y));
}
for(int i=1;i<=n;i++)
{
d[a[i].x]++, d[maxx+a[i].y]++;
add(a[i].x, maxx+a[i].y);
pos[i]=edgenum;
add(maxx+a[i].y, a[i].x);
}
int lst=0;
for(int i=1;i<=2*maxx;i++)
{
if(d[i]%2)
{
if(lst) add(lst, i), add(i, lst), lst=0;
else lst=i;
}
}
for(int i=1;i<=2*maxx;i++)
{
if(!fl[i]) dfs(i);
}
for(int i=1;i<=n;i++)
{
if(ans[pos[i]]) cout<<"r";
else cout<<"b";
}
return 0;
}
5. CF888G Xor-MST
link:https://www.luogu.com.cn/problem/CF888G
自然是没有什么头猪啦。最多想到01-trie啦。
但还是不会处理最小生成树啊。然后题解有个非常巧妙的做法。就是你把所有数加到trie树中,那么每个数的结尾都是trie树的叶节点,考虑最小的边权应当是Trie中,LCA深度最大的两个数,两个数越接近,它们的lca才可能最深,在trie树上也会趋近于相邻。那么直接dfs,到当前节点,如果两个儿子都有,找出两个儿子所在子树之间的最小边权进行合并,那么最终形成的一定是最小生成树,不重不漏。考虑怎么样找出「两个儿子所在子树之间的最小边权」。我们可以通过预处理的排序知道左子树所对应的数的范围(一定是连续的),然后分别对于每个数,利用01-trie找出右子树中异或最小的那个。复杂度可过。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int n, a[maxn], trie[maxn*30][2], idx, l[maxn*30], r[maxn*30];
void insert(int x,int id)
{
int p=0;
// cout<<x<<" "<<id<<endl;
for(int i=30;i>=0;i--)
{
int u=x>>i&1;
if(!trie[p][u])
{
trie[p][u]=++idx;
}
p=trie[p][u];
if(!l[p]) l[p]=id;
r[p]=id;
}
// cout<<"============="<<endl;
}
int query(int x,int p,int dep)
{
if(dep<0) return 0;
int res=0;
for(int i=dep;i>=0;i--)
{
int u=x>>i&1;
if(trie[p][u])
{
p=trie[p][u];
res=res*2;
}
else
{
p=trie[p][!u];
res=res*2+1;
}
}
return res;
}
int dfs(int x,int dep)
{
if(dep<0) return 0;
if(trie[x][0]&&trie[x][1])
{
int res=1e18;
for(int i=l[trie[x][0]];i<=r[trie[x][0]];i++)
{
res=min(res, query(a[i], trie[x][1], dep-1)+(1<<dep));
}
return dfs(trie[x][0], dep-1)+dfs(trie[x][1], dep-1)+res;
}
if(trie[x][0]) return dfs(trie[x][0], dep-1);
if(trie[x][1]) return dfs(trie[x][1], dep-1);
return 0;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1, a+n+1);
int rt=0;
for(int i=1;i<=n;i++)
{
insert(a[i], i);
}
cout<<dfs(0, 30);
return 0;
}
6. CF527E Data Center Drama
link:https://www.luogu.com.cn/problem/CF527E
会发现这个题最后的图每个点出度、入度都为偶数,那么它一定有一种定向使得它有欧拉回路(充要非必要)。又因为原图一定有偶数个奇点(出度+入度=奇的点,这个手玩一下就能出来),那就考虑像前面第四题一样让奇点两两相连,就可以形成一个欧拉回路了。但光有欧拉回路也是不行的,比如四条边的简单环,还必须满足每个点出入度均为偶数。我们发现只要在跑欧拉回路时隔一个反一下边的方向就可以做到,就像A→B←C→D←……但这个前提是总边数是偶数,那我们对于奇数边就任选一个点添加一个自环即可。
(题外话)以及,欧拉路径要倒序输出。
构造题
发现非常困难想不到一点。
1. CF2127E Ancient Tree
link:https://www.luogu.com.cn/problem/CF2127E
构造长这样子???
会发现每个点只需选其子树内的颜色就能做到最优。(因为儿子之间有>=2对同色就一定产生花费,只有一对就选那一对,没有的话就随便选一个子树内部的颜色)。那么它是不会影响它的祖先的。也就是说,初始时的颜色就已经把答案固定了。要做的就是模拟这个过程即可。用启发式合并优化复杂度。用set维护每个点的子树颜色和儿子间的同色。(至于为啥不是树上启发式合并的写法————我也想知道啊我调了好久不知道哪儿错了写起来还麻烦)。然后如果该点的子树全为0就先不考虑,最后颜色同父亲即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int T, n, K, a[maxn], ans, col[maxn];
struct edge{
int nxt, to;
}edge[maxn<<1];
int head[maxn], edgenum;
void add(int from,int to)
{
edge[++edgenum].nxt=head[from];
edge[edgenum].to=to;
head[from]=edgenum;
}
set<int> s[maxn];
void dfs1(int u,int fa)
{
set<int> tmp;
int p=0;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==fa) continue;
dfs1(v, u);
if(s[u].size()<s[v].size()) swap(s[u], s[v]);
for(int j:s[v])
{
if(s[u].find(j)!=s[u].end())
{
p=j;
tmp.insert(j);
}
s[u].insert(j);
}
}
if(col[u])
s[u].insert(col[u]);
if(tmp.size()>=2)
{
ans+=a[u];
if(!col[u]) col[u]=p;
}
else if(tmp.size()==1)
{
if(col[u]&&col[u]!=*tmp.begin()) ans+=a[u];
else if(!col[u]) col[u]=*tmp.begin();
}
else
{
if(!col[u]&&p) col[u]=p;
}
}
void dfs2(int u,int fa)
{
if(!col[u]) col[u]=col[fa];
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==fa) continue;
dfs2(v, u);
}
}
signed main()
{
cin>>T;
while(T--)
{
cin>>n>>K;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>col[i];
}
for(int i=1;i<n;i++)
{
int u, v;
cin>>u>>v;
add(u, v), add(v, u);
}
dfs1(1, 0);
col[0]=1;
dfs2(1, 0);
cout<<ans<<endl;
for(int i=1;i<=n;i++) cout<<col[i]<<" ";
cout<<endl;
edgenum=ans=0;
for(int i=0;i<=n;i++) s[i].clear(), head[i]==0;
}
return 0;
}
//我他妈要困得晕厥过去了=_=
/*
1
3 3
5875 2601 1258
0 0 1
2 1
3 1
3 2
*/
2. Arpa’s overnight party and Mehrdad’s silent entering
link:https://www.luogu.com.cn/problem/CF741C
这个图还怪吓人的。
比较adhoc。感觉就是先构造答案然后证明答案是对的(?首先就是男女朋友之间连边(注意这个边代表相邻两点吃的不一样),而连续三个的限制很不好做。考虑强制给2i-1和2i连边,这样子就一定是满足答案的充要条件。分析连边后一定会产生一堆偶环。(总共2n个点,每个点只会连出去两条边,最终必定是若干个不交的环;又因为可以看作一开始将所有点两两分组,后增的边相当于将 两个偶数链(当然如果这是一条链另说)合并,最终一定是偶环)然后考虑不合法情况一定是有奇环存在。所以一定没有不合法情况。
3. Doremy's Experimental Tree
link:https://www.luogu.com.cn/problem/CF1764F
乍一看觉得没什么思路。然后经过一系列推式子会发现u和v之间的距离刚好是(f[u][u]+f[v][v]-2*f[u][v])/n,然后我们就可以得到n^2条边。考虑像最小生成树一样用并查集将这棵树建起来。加点优化就能过。生成树,也就是构造一棵树。
这种题的技巧就是先关注特殊值,比如f[i][i],然后考虑点与点之间的关系,尝试转化成比较简单的式子。
4. [ICPC 2025 Xi'an R] Imagined Holly
link:https://www.luogu.com.cn/problem/P14455
来看一个和上一道很像的题。TT但是我还没有什么头绪TT
首先关注特殊值,a[i][i]显然是没有什么用的。它大概率是选择任意两点i和j,从a矩阵进行计算得出某些关于这两点的信息,很容易想到找到另一点k,然后利用a[i][j],a[i][k],a[k][j]进行计算。但这样的话不仅不清楚k的位置(也就是不知道k是在i到j的路径上,还是在i或j的子树内),还需要n^3的复杂度。考虑改进一下这个思路,找一个公共点,使得我们能确定它的位置。那就是根节点1了。这样子你就会发现可以求出任意两点的lca。
然后考虑构造这棵树。我们要想办法求出每个点的父亲。知道lca后,就可以求出每个点的所有祖先,同时也能求出每个点的子树大小siz。会发现每个点的父亲就是它的祖先中siz最小的那个。所以就做完了。
5. [THUPC 2026 决赛] 流光解密
link:https://www.luogu.com.cn/problem/P16536
说是通信题,但其实跟传统题没啥区别。考虑用每个点记录s二进制下每一位的信息。发现Q=1必须是在线的,证明我们只需通过两点的编号便能确定。那么我们就让边的方向记录两点是否相同,若相同,编号较小点向编号较大点连,反之亦然。那么求解的时候只需dfs一遍就好了。考虑只有n-1条边,也就是起始点的信息(s的第n位)我们是不知道的,但因为s只有一种可能是第n位为1,而s恰好不可能是0,那么我们就把2^(n-1)看作0来处理。那么第n位就一定是0。把n作为根节点跑就可以了。
6. CF1943C Tree Compass
link:https://www.luogu.com.cn/problem/CF1943C
每次染色至多染两个点。考虑尽量让每次操作都能给两个点染色。于是就能想到在直径中点上进行操作。这样子就要分类讨论直径(直径点数)为偶数或奇数的情况。若为奇数,就刚好可以平分。若为偶数,还要再分两种情况:4的倍数或者模4余2。因为前者可以通过两个中点的交替操作减少次数,而后者只能一个一个给没染上的染色。
7. CF1391E Pairs of Pairs
link:https://www.luogu.com.cn/problem/CF1391E
我以为要找出图上最长的路径。根本会不了一点。发现只需建dfs树找深度最大的即可,因为这样能证明一定存在第二种的方案。
第一种就不细讲了。如果第一种不行就证明,深度最大不超过\(\left\lceil\dfrac{n}{2}\right\rceil\)第二种只需在dfs树的每一层两个两个的配对,直至该层只剩1/0个。这样子至多有不到\(\left\lceil\dfrac{n}{2}\right\rceil\)个点未被选上,也就是满足第二种的要求。同时因为同层之间没有连边,任意两对至多有两条边(两个父亲,两个儿子)。记得数组得开1e6。
Climb these hills I'm reaching for the heights
And chasing all the lights that shine✨

浙公网安备 33010602011771号