思维题练习

思维题题解合集

Posted by TH911 on September 27, 2025

本文选取题目源于此处

CF2071C Trapmigiano Reggiano

以终点 $t$ 为树的根节点。

那么求排列等价于求一种移动顺序,使得老鼠最后停留在根节点

移动可以分两种:向上和向下。显然向上是更优的。

设老鼠当前位于节点 $p$,如果在 $x$ 处生成一个奶酪:

  • 若 $x$ 是 $p$ 的祖先节点,则这是的。
  • 若 $x$ 是 $p$ 的子树内节点,则这是的。
  • 否则 $x$ 仍然是的,因为 $p$ 会走向 $\operatorname{lca}(x,p)$,会向上

考虑先将子树内的走完,因此可以贪心:先走深度大的节点

详细题解参见此处

参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; constexpr const int N=1e5; int n,s,t,depth[N+1]; vectorg[N+1],node[N+1]; void dfs(int x,int fx){ depth[x]=depth[fx]+1; node[depth[x]].push_back(x); for(int i:g[x]){ if(i==fx){ continue; } dfs(i,x); } } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ for(int i=1;i<=n;i++){ g[i].resize(0); node[i].resize(0); } cin>>n>>s>>t; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } dfs(t,0); for(int i=n;i>=1;i--){ for(int j:node[i]){ cout<<j<<' '; } } cout<<'\n'; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [「LAOI-9」Update](https://www.luogu.com.cn/problem/P11894) 首先,$a_i$ 之间**相互独立**,因此可以简单地通过线段树维护出每一个 $a_i$ 的操作次数 $f_i$。 考虑 $a_i\leftarrow a_i+\lfloor\log_2 a_i\rfloor$ 操作 $f_i$ 次,如何快速得到。$\lfloor\log_2 a_i\rfloor$ 的增长显然是极其**缓慢**的,会有很多次连续的操作中,$\lfloor\log_2 a_i\rfloor$ 都是一样的。因此设法计算出这个次数,直接加上即可。 记 $x=2^{\lfloor\log_2a_i\rfloor+1}$ 为大于 $a_i$ 的最小的 $2$ 的幂。显然,当 $a_i\geq x$ 时,$\lfloor\log_2a_i\rfloor$ 会增加。 因此,可以计算得出操作次数为 $\left\lceil\dfrac{2^{\lfloor\log_2a_i\rfloor+1}-a_i}{\lfloor\log_2a_i\rfloor}\right\rceil$。注意 $\lfloor\log_2a_i\rfloor=0$ 的时候无需操作,因为 $a_i$ 并不会增长。 时间复杂度:$n,V$ 同阶,$\mathcal O(m\log n)$。其中,$V$ 表示值域。 *** 查看题解可以发现,其实本题无需线段树,仅仅使用差分即可;但时间复杂度不变。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; constexpr const int N=1e5; struct segTree{ struct node{ int l,r; int sum,tag; int size(){ return r-l+1; } }t[N<<2|1]; void up(int p){ t[p].sum=t[p<<1].sum+t[p<<1|1].sum; } void build(int p,int l,int r){ t[p]={l,r}; if(l==r){ return; } int mid=l+r>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); } void down(int p){ t[p<<1].sum+=t[p<<1].size()*t[p].tag; t[p<<1].tag+=t[p].tag; t[p<<1|1].sum+=t[p<<1|1].size()*t[p].tag; t[p<<1|1].tag+=t[p].tag; t[p].tag=0; } void add(int p,int l,int r,int k){ if(l<=t[p].l&&t[p].r<=r){ t[p].sum+=t[p].size()*k; t[p].tag+=k; return; } down(p); if(l<=t[p<<1].r){ add(p<<1,l,r,k); } if(t[p<<1|1].l<=r){ add(p<<1|1,l,r,k); } up(p); } int query(int p,int x){ if(t[p].l==t[p].r){ return t[p].sum; } down(p); if(x<=t[p<<1].r){ return query(p<<1,x); }else{ return query(p<<1|1,x); } } }t; int n,a[N+1]; int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int m; cin>>n>>m; for(int i=1;i<=n;i++){ cin>>a[i]; } t.build(1,1,n); while(m--){ int l,r; cin>>l>>r; t.add(1,l,r,1); } for(int i=1;i<=n;i++){ int f=t.query(1,i); if(!__lg(a[i])){ continue; } while(f){ int x=ceil(1.0*((1<<__lg(a[i])+1)-a[i])/__lg(a[i])); x=min(x,f); f-=x; a[i]+=x*__lg(a[i]); } } for(int i=1;i<=n;i++){ cout<<a[i]<<' '; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[ABC412E] LCM Sequence](https://www.luogu.com.cn/problem/AT_abc412_e) 记 $a_i=\operatorname*{lcm}\limits_{j=1}^ij$,求 $a_l,a_{l+1},a_{l+2},\cdots,a_r$ 中不同的数的个数。 显然 $a_{i-1},a_i$ 不同当且仅当 $a_i$ 的某一个质因子的指数大于 $1\sim i-1$ 的该质因子的最大指数。而 $a_i$ 又需要最小,因此这个 $a_i$ **一定是质数的幂**。 $a_l$ 单独算作一个不同的整数,因此找 $[l+1,r]$ 内的质数幂即可。 *** 对于二次幂及以上,可以通过埃式筛筛出 $\left[2,\sqrt r\right]$ 内的质数 $\textit{prime}$ 之后暴力枚举 $\textit{prime}$ 的幂次判断是否在 $[l+1,r]$ 内。 这样的复杂度是 $\mathcal O\left(\sqrt r\log\log\sqrt r+\dfrac{\sqrt r}{\ln\sqrt r}\log r\right)$ 的,可以接受。 对于 $[l+1,r]$ 内的质数,可以在之前 $\left[2,\sqrt r\right]$ 的质数的基础上,使用埃式筛筛出 $[l+1,r]$ 内的合数,剩下即为质数。(当然,你使用 Miller-Rabin 素性测试也可以,但是我背不下来。) *** 时间复杂度 $\mathcal O\left(\sqrt r\log\log\sqrt r+\dfrac{\sqrt r}{\ln\sqrt r}\log r+(r-l)\ln\sqrt r\right)$。三项分别为埃式筛筛质数、暴力判断幂次、埃式筛筛合数。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define int ll typedef long long ll; constexpr const int N=1e7; int query(int l,int r){ static int prime[N+1],vis[N+1],size; int ans=1; l++; int t=sqrt(r); for(int i=2;i<=t;i++){ if(!vis[i]){ vis[i]=i; prime[++size]=i; for(ll j=i*i;j<=t;j+=i){ vis[j]=i; } } } static bool flag[N+1]; for(int i=1;i<=size;i++){ ll powPrime=prime[i]; for(int k=1;powPrime<=r;k++,powPrime*=prime[i]){ if(l<=powPrime){ ans++; flag[powPrime-l]=true; } } } for(int i=1;i<=size;i++){ for(int j=(l+prime[i]-1)/prime[i]*prime[i];j<=r;j+=prime[i]){ flag[j-l]=true; } } for(int i=l;i<=r;i++){ ans+=!flag[i-l]; } return ans; } main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int l,r; cin>>l>>r; cout<<query(l,r)<<'\n'; cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[FJCPC 2025] 构造大师贝贝](https://www.luogu.com.cn/problem/P13098) 注意到 $T\leq1000$,但是 $n\leq10^{12}$。那么从时间复杂度的角度考虑,应当为一个类似于 $\mathcal O(T\log n)$ 的复杂度。 但是这道题的思维难度也就在这里了,将 $n$ 变为完全平方数,每次加上约数 $x$,很不好想到有什么带 log 的做法。 本题为 Special Judge,只需要输出任意一种构造方案。经过一些~~胡乱尝试~~猜测,想到也许可以想办法将 $n$ 构造为形如 $x^{2k}$ 的完全平方数。 那么考虑将其构造为 $2^{2k}$,每次只需要加上 $\operatorname{lowbit}(n)$ 即可,$\operatorname{lowbit}(n)$ 定义同树状数组。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; ll lowbit(ll n){ return n&-n; } bool check(ll n){ ll x=sqrt(n); return x*x==n; } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ ll n; cin>>n; vectorans; while(!check(n)){ ans.push_back(lowbit(n)); n+=lowbit(n); } cout<<ans.size()<<'\n'; for(ll i:ans){ cout<<i<<' '; } if(ans.size()){ cout<<'\n'; } } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[ARC197B] Greater Than Average](https://www.luogu.com.cn/problem/AT_arc197_b) 显然,子序列的得分与顺序无关,因此不妨令 $a$ 单调不降。 考虑以 $a_i$ 结尾的最优子序列。如果加入 $a_{i-1}$,那么**平均数一定不增**,因此**得分不降**。那么可以得到以 $a_i$ 结尾的最优子序列即 $a_1,a_2,\cdots,a_i$ 的**前缀**。 记 $\textit{pre}_i=\textit{pre}_{i-1}+a_i$。那么考虑依次加入 $a_1,a_2,\cdots,a_n$,平均数单调不增,可以单调双端队列维护大于平均数的 $a_i$,统计个数即可。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; constexpr const int N=2e5,V=1e9; int n,a[N+1]; ll pre[N+1]; int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } sort(a+1,a+n+1); for(int i=1;i<=n;i++){ pre[i]=pre[i-1]+a[i]; } dequeq; int ans=-2147483648; for(int i=1;i<=n;i++){ q.push_back(a[i]); while(q.size()&&1ll*q.front()*i<=pre[i]){ q.pop_front(); } ans=max(ans,(int)q.size()); } cout<<ans<<'\n'; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[PA 2025] 水族馆 / Akwarium](https://www.luogu.com.cn/problem/P11919) 即枚举整数 $a,b,h,k$,使得 $a^2+b^2+h^2=k^2$,且 $a\leq b$。 朴素枚举 $(a,b,h)$ 是 $\mathcal O\left(n^3\right)$ 的,无法接受。 *** 注意到 $1\leq n\leq 5000$,$\mathcal O(n^2)$ 是可以接受的,因此考虑能否 $\mathcal O\left(n^2\right)$ 完成。 因此可以转化为枚举 $a^2+b^2=k^2-h^2$,预处理 $x=a^2+b^2$ 或者 $x=k^2-h^2$ 的个数 $\textit{cnt}_x$,枚举另一个统计答案即可。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; constexpr const int N=5000; int cnt[N*N+1]; int f(int n){ for(int a=1;a<=n;a++){ for(int b=a;b<=n&&a*a+b*b<=n*n;b++){ cnt[a*a+b*b]++; } } int ans=0; for(int h=1;h<=n;h++){ for(int k=h;k<=n;k++){ ans+=cnt[k*k-h*h]; } } return ans; } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n; cin>>n; cout<<f(n)<<'\n'; cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[ARC198B] Rivalry](https://www.luogu.com.cn/problem/AT_arc198_b) $x$ 个 $0$,$y$ 个 $1$,$z$ 个 $2$,排成一个环,要求 $2$ 旁边为两个 $1$ 或 $0$,$1$ 旁边一个 $0$。 容易发现,$0$ 是最好的,放在哪里都合法。 考虑插入所有 $0$,再考虑 $1,2$。则两个 $0$ 中间存在两个 $1$ 或一个 $2$。 考虑最终是一个环,因此若有解,则有有 $2x\geq y,x\geq z$。 并且,若 $y\bmod 2=1$ 且 $z=0$ 的时候无解;因为此时会有两个 $0$ 中间只有一个 $1$,放一个 $2$ 进去就好了。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; bool check(int x,int y,int z){ return x*2>=y&&x>=z&&(y%2==0||z>0); } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ int x,y,z; cin>>x>>y>>z; cout<<(check(x,y,z)?"Yes":"No")<<'\n'; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[常州市赛 2023] ABC 字符串](https://www.luogu.com.cn/problem/B4218) $s$ 仅包含 `A`、`B`、`C`,每次可以将子串 `ABC` 换为 `BCA`。不难发现就是将 `A` **移到了最后**。 那么考虑 `ABC` 两边是什么。`ABC` 后面是若干个 `BC`,则可以将 `A` 换到后面之后继续操作。同时 `ABC` 前面的若干个 `A`,在 `BC` 换到前面后也可以继续操作。记 `ABC` 前面(包含本身)**连续** `A` 的个数为 $\textit{cntA}$,后面**连续** `BC` 的个数为 $\textit{cntBC}$,则有这一个 `ABC` 子串的贡献为 $\textit{cntA}\cdot\textit{cntBC}$。 可以简单地 $\mathcal O(n)$ 维护这个过程。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; constexpr const int N=2e5; int n,cntA[N+1],cntBC[N+1]; char s[N+1]; int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>(s+1); n=strlen(s+1); ll ans=0; int cntA=0; for(int i=1;i<=n-1;){ if(s[i]=='A'){ cntA++; i++; }else if(s[i]=='B'&&s[i+1]=='C'){ int cntBC=0; for(;i+1<=n;i+=2){ if(s[i]=='B'&&s[i+1]=='C'){ cntBC++; }else{ break; } } ans+=1ll*cntA*cntBC; }else{ cntA=0; i++; } } cout<<ans<<'\n'; cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[JSOI2015] 最大公约数](https://www.luogu.com.cn/problem/P5502) 给定长度为 $n$ 的序列 $a_1,a_2,\cdots,a_n$,定义 $a_l,a_{l+1},\cdots,a_r$ 的权值为 $(r-l+1)\gcd(a_l,a_{l+1},\cdots,a_r)$。 求最大权值,$1\leq a_i\leq10^{12},1\leq n\leq10^5$。 若 $l<l'\leq r$,则有 $\gcd(a_l,a_{l+1},\cdots,a_r)\geq\gcd(a_{l'},a_{l'+1},\cdots,a_r)$。原因显然。 而当 $\gcd$ 一定的时候,显然区间长度越长越好。因此对于确定的左端点 $l$,不断通过二分找到当前 $\gcd$ 的右端点,更新答案即可。 这样的端点至多只有 $\mathcal O(\log V)$ 个,因为 $\gcd$ 减少至少会减少一半。 实现上可以通过 ST 表/线段树等数据结构来维护区间 $\gcd$。 时间复杂度:$\mathcal O(n\log n\log V)$。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define int ll typedef long long ll; constexpr const int N=100000; template T gcd(T a,T b){ while(b){ T tmp=a; a=b; b=tmp%b; } return a; } int n; ll a[N+1]; struct St{ ll st[N+1][__lg(N+1)+1]; void build(ll a[],int n){ for(int i=1;i<=n;i++){ st[i][0]=a[i]; } for(int i=1;(1<<i)<=n;i++){ for(int x=1;x+(1<<i)-1<=n;x++){ st[x][i]=gcd(st[x][i-1],st[x+(1<<i-1)][i-1]); } } } ll query(int l,int r){ int s=__lg(r-l+1); return gcd(st[l][s],st[r-(1<<s)+1][s]); } }st; main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } st.build(a,n); ll ans=-1; for(int l=1;l<=n;l++){ int p=l; while(p<=n){ int L=p,R=n; int gcdP=st.query(l,p); while(L<R){ int mid=L+R+1>>1; if(st.query(l,mid)<gcdP){ R=mid-1; }else{ L=mid; } } ans=max(ans,(L-l+1)*st.query(l,L)); p=L+1; } } cout<<ans<<'\n'; cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } /* 5 3 6 2 2 2 8 */ ``` </details> # [「FAOI-R9」函数大师](https://www.luogu.com.cn/problem/P12397) 分析 $S_k(x)$ 的性质,显然等价于 $\underbrace{s(s(\cdots s(x)\cdots))}_{k\text{ 个 }s}$,有 $s(S_k(x))=S_k(s(x))=S_{k+1}(x)$。 考虑 $f_k(x)$: $$ \begin{aligned} f_k(x)&=\sum_{i=0}^kS_i(x)\\ &=x+\sum_{i=0}^{k-1}s(S_i(x))\\ &=x+\sum_{i=0}^{k-1}S_i(s(x))\\ &=x+f_{k-1}(s(x)) \end{aligned} $$ 则 $f_k(x)=m$ 等价于 $x+f_{k-1}(s(x))=m$,有 $x=m-f_{k-1}(s(x))$,显然 $1\leq x\leq m$。 但是 $s(x)$ 在 $x\leq10^{18}$ 的时候,可以在 $x=10^{18}-1$ 的时候取到最大值 $s(x)=18\times9=162$。 然而,$k\leq10^9$,无法接受暴力计算的复杂度。 考虑进一步优化,有 $s(x)=s(m-f_{k-1}(s(x)))$,这样答案至多为 $162$。
关于答案正确性

若 $s(x)=s(m-f_{k-1}(s(x)))$ 成立,则有且仅有一个合法的 $x$

假设 $\exist x_1\neq x_2,s(x_1)=s(x_2)$ 满足上述条件。

那么,$m-f_{k-1}(s(x))$ 在 $x=x_1,x_2$ 时相同,然而 $x_1\neq x_2$,与此矛盾。

故,答案一一对应。

因此考虑预处理所有的 $f_{k-1}(s(x))$,这样似乎不好处理,因为暴力计算是 $\mathcal O(k)$ 的,无法接受。 但是有: $$ \begin{aligned} s\left(10^{18}-1\right)&=162\\ s(162)&=9\\ s(9)&=9\\ &\cdots \end{aligned} $$ 可以发现,对于 $\forall k\geq 4$,有 $S_k(x)=S_{k-1}(x)$。 因此有: $$ \begin{aligned} f_k(s(x))&=x+s(x)+(k-1)\cdot s(s(x)) \end{aligned} $$
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; constexpr const int V=162; int k; int s(ll x){ int ans=0; while(x){ ans+=x%10; x/=10; } return ans; } ll f[V+1]; void pre(){ if(!k){ return; } k--; for(int i=1;i<=V;i++){ if(k==0){ f[i]=i; }else if(k==1){ f[i]=i+s(i); }else{ f[i]=i+s(i)+(k-1ll)*s(s(i)); } } k++; } int query(ll m){ if(!k){ return 1; } int ans=0; for(int i=1;i<=V;i++){ if(i==s(m-f[i])){ ans++; } } return ans; } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T>>k; pre(); while(T--){ ll m; cin>>m; cout<<query(m)<<'\n'; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[ARC203B] Swap If Equal Sum](https://www.luogu.com.cn/problem/AT_arc203_b) 若 $a,b$ 总和不同,显然无解。 诈骗题。第一眼可能会想到神秘 DP,但是又无从下手。 操作等价于: 1. 交换 `0`、`00`。 2. 交换 `1`、`01`。 3. 交换 `1`、`10`。 对于其他所有的交换,都可以通过这 $3$ 种操作实现。 操作可逆,因此考虑一种方式,让 $a,b$ 均单调不降。 *** 假设存在至少两个 `1`,和若干个 `0`,那么就可以使其**单调不降**。 因为操作 1 可以任意调整两个 `1` 左边、右边的 `0`;对于 `1` 中间的 `0`,可以通过操作 1 变成 `101`,此时通过操作 2 变为 `011` 即可。 不管最右边的 `1`,又可以将前面所有的 `1` 换到最后。 *** 仅仅只有一个 `1` 是简单地,只要不在开头/结尾,就可以通过操作 1 **任意调动**。也正因此,当且仅当存在 `1` 在开头/结尾且不是同时在 $a,b$ 的开头/结尾时**无解**。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; constexpr const int N=2e5; int n,a[N+1],b[N+1]; int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ cin>>n; int cntA=0,cntB=0; for(int i=1;i<=n;i++){ cin>>a[i]; cntA+=a[i]; } for(int i=1;i<=n;i++){ cin>>b[i]; cntB+=b[i]; } if(cntA!=cntB||cntA==1&&(a[1]!=b[1]||a[n]!=b[n])){ cout<<"No\n"; }else{ cout<<"Yes\n"; } } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details> # [[GDCPC 2024] 图](https://www.luogu.com.cn/problem/P13356) 从原图中找出 $k=\left\lceil\dfrac m{n-1}\right\rceil$ 条两点之间的边不相交路径。 考虑将原图划分为 $k$ 张图 $T_1,T_2,\cdots,T_k$,每一张图维护一条路径。$k$ 的值可以启发我们维护一棵类似于树的图,实际上 $T_i$ 中有环是不优的,可以放到其他图上产生新的路径。 最终答案即找 $u\sim v$ 的路径,使得 $T_1,T_2,\cdots,T_k$ 中均连通。维护前缀连通,在 $T_k$ 中随便找两个连通的点,在 $T_1\sim T_{k-1}$ 中暴力 DFS 找即可。 但其实 $T_i$ 应当是一棵森林。 [详细题解参见此处](./-/P13356)。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; constexpr const int N=2e5,M=2e5,K=M; int n,m; //vectorg[N+1]; vector<vector<vector > >tree; int k; struct dsu{ vectorf,size; // int f[N+1],size[N+1]; int find(int x){ if(f[x]==x){ return x; } return f[x]=find(f[x]); } void build(int n){ f.resize(n+1); size.resize(n+1); for(int i=1;i<=n;i++){ f[i]=i; size[i]=1; } } void merge(int x,int y){ x=find(x),y=find(y); if(size[x]<size[y]){ f[x]=y; size[y]+=size[x]; }else{ f[y]=x; size[x]+=size[y]; } } }; vectordsu; void resize(int n,int k){ dsu.resize(k+1); for(int i=1;i<=k;i++){ dsu[i].build(n+1); } tree.resize(k+1); for(int id=1;id<=k;id++){ tree[id].resize(n+1); for(int i=1;i<=n;i++){ tree[id][i].resize(0); } } } void addEdge(int u,int v){ /*for(int i=1;i<=k;i++){ if(dsu[i].find(u)!=dsu[i].find(v)){ tree[i][u].push_back(v); tree[i][v].push_back(u); dsu[i].merge(u,v); break; } }*/ int l=1,r=k,ans=-1; while(l<=r){ int mid=l+r>>1; if(dsu[mid].find(u)!=dsu[mid].find(v)){ ans=mid; r=mid-1; }else{ l=mid+1; } } if(ans!=-1){ tree[ans][u].push_back(v); tree[ans][v].push_back(u); dsu[ans].merge(u,v); } } bool dfs(int x,int fx,int to,int id,vector&ans){ ans.push_back(x); if(x==to){ return true; } for(int i:tree[id][x]){ if(i==fx){ continue; } if(dfs(i,x,to,id,ans)){ return true; } } ans.pop_back(); return false; } namespace debug{ void dfs1(int x,int fx,int id){ cerr<<x<<" "; for(int i:tree[id][x]){ if(i==fx){ continue; } dfs1(i,x,id); } } } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int T; cin>>T; while(T--){ cin>>n>>m; k=(m+n-2)/(n-1); resize(n,k); while(m--){ int u,v; cin>>u>>v; addEdge(u,v); } bool flag=true; for(int i=1;flag&&i<=n;i++){ for(int j:tree[k][i]){ flag=false; cout<<i<<' '<<j<<'\n'; for(int id=1;id<=k;id++){ vectorans; dfs(i,0,j,id,ans); cout<<ans.size()<<' '; for(int x:ans){ cout<<x<<' '; } cout<<'\n'; } break; } } if(flag){ cout<<"-1\n"; } } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } /* 1 3 1 1 3 1 3 2 1 3 */ /* 1 4 7 1 2 2 3 3 4 4 1 1 3 2 4 1 4 1 4 4 1 2 3 4 2 1 4 2 1 4 */ /* 3 3 1 1 3 4 7 1 2 2 3 3 4 4 1 1 3 2 4 1 4 5 5 1 2 2 3 3 4 4 5 3 5 1 3 2 1 3 1 4 4 1 2 3 4 2 1 4 2 1 4 3 5 3 3 4 5 2 3 5 */ ``` </details> # [CF773D Perishable Roads](https://www.luogu.com.cn/problem/CF773D) 给定一张 $n$ 个点的完全无向图,$(i,j)$ 的权值为 $w_{i,j}$。 给定根节点 $t$,求生成树,使得 $\displaystyle\sum_{i=1}^ns(i,t)$ 最小,$s(i,t)$ 为 $i\sim t$ 的最小权值,输出和;求 $t=1,2,3,\cdots,n$ 的答案。 最终生成树一定包含权值最小边,否则不优。 那么便是从 $t$ 出发,用一条链到达最小边,之后再出来一些子树。最小边出来后的贡献均为最小边权,这最优。 注意到最小边端点 $x\sim t$ 链上边权除开最后一条边,是**单调不降**的,否则可以将其放到 $x$ 之后的树上更优。 因此链上每一条边的贡献都是自己的 $1$ 倍,跑最短路即可。 考虑链上两点 $i,s$,若以 $j$ 为中转站更优,其答案为 $2w_{i,j}$。 将所有边权均减去最小边权 $w$,在新图上跑 $x\sim t$ 的最短路,答案加上 $(n-1)w$ 即可得到。 [详细题解参见此处](./-/CF773D)。
参考代码 ```cpp //#include<bits/stdc++.h> #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; constexpr const int N=2000,W=1e9; constexpr const ll inf=0x3f3f3f3f3f3f3f3f; int n,w[N+1][N+1]; ll dis[N+1]; void Dijkstra(int s){ static bool vis[N+1]; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); vis[s]=true; for(int i=1;i<=n;i++){ dis[i]=w[s][i]; for(int j=1;j<=n;j++){ if(i==j){ continue; } dis[i]=min(dis[i],2ll*w[i][j]); } } for(int i=1;i<n;i++){ ll Min=inf; int x=-1; for(int j=1;j<=n;j++){ if(vis[j]){ continue; } if(dis[j]<Min){ Min=dis[j]; x=j; } } vis[x]=true; for(int j=1;j<=n;j++){ if(vis[j]){ continue; } dis[j]=min(dis[j],dis[x]+w[x][j]); } } } int main(){ /*freopen("test.in","r",stdin); freopen("test.out","w",stdout);*/ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n; for(int i=1;i<n;i++){ for(int j=1;j<=n-i;j++){ cin>>w[i][i+j]; w[i+j][i]=w[i][i+j]; } } ll Min=inf,s=-1; for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ if(w[i][j]<Min){ Min=w[i][j]; s=i; } } } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==j){ continue; } w[i][j]-=Min; } } Dijkstra(s); for(int i=1;i<=n;i++){ cout<<dis[i]+(n-1ll)*Min<<'\n'; } cout.flush(); /*fclose(stdin); fclose(stdout);*/ return 0; } ``` </details>