思维题练习
本文选取题目源于此处。
CF2071C Trapmigiano Reggiano
以终点 \(t\) 为树的根节点。
那么求排列等价于求一种移动顺序,使得老鼠最后停留在根节点。
移动可以分两种:向上和向下。显然向上是更优的。
设老鼠当前位于节点 \(p\),如果在 \(x\) 处生成一个奶酪:
- 若 \(x\) 是 \(p\) 的祖先节点,则这是优的。
- 若 \(x\) 是 \(p\) 的子树内节点,则这是劣的。
- 否则 \(x\) 仍然是优的,因为 \(p\) 会走向 \(\operatorname{lca}(x,p)\),会向上。
考虑先将子树内的走完,因此可以贪心:先走深度大的节点。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1e5;
int n,s,t,depth[N+1];
vector<int>g[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;
}
「LAOI-9」Update
首先,\(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\) 表示值域。
查看题解可以发现,其实本题无需线段树,仅仅使用差分即可;但时间复杂度不变。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[ABC412E] LCM Sequence
记 \(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)\)。三项分别为埃式筛筛质数、暴力判断幂次、埃式筛筛合数。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[FJCPC 2025] 构造大师贝贝
注意到 \(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)\) 定义同树状数组。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
vector<ll>ans;
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;
}
[ARC197B] Greater Than Average
显然,子序列的得分与顺序无关,因此不妨令 \(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\),统计个数即可。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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];
}
deque<int>q;
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;
}
[PA 2025] 水族馆 / Akwarium
即枚举整数 \(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\),枚举另一个统计答案即可。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[ARC198B] Rivalry
\(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\) 进去就好了。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[常州市赛 2023] ABC 字符串
\(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)\) 维护这个过程。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
「FAOI-R9」函数大师
分析 \(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)\):
则 \(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)\) 的,无法接受。
但是有:
可以发现,对于 \(\forall k\geq 4\),有 \(S_k(x)=S_{k-1}(x)\)。
因此有:
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[ARC203B] Swap If Equal Sum
若 \(a,b\) 总和不同,显然无解。
诈骗题。第一眼可能会想到神秘 DP,但是又无从下手。
操作等价于:
- 交换
0
、00
。 - 交换
1
、01
。 - 交换
1
、10
。
对于其他所有的交换,都可以通过这 \(3\) 种操作实现。
操作可逆,因此考虑一种方式,让 \(a,b\) 均单调不降。
假设存在至少两个 1
,和若干个 0
,那么就可以使其单调不降。
因为操作 1 可以任意调整两个 1
左边、右边的 0
;对于 1
中间的 0
,可以通过操作 1 变成 101
,此时通过操作 2 变为 011
即可。
不管最右边的 1
,又可以将前面所有的 1
换到最后。
仅仅只有一个 1
是简单地,只要不在开头/结尾,就可以通过操作 1 任意调动。也正因此,当且仅当存在 1
在开头/结尾且不是同时在 \(a,b\) 的开头/结尾时无解。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}
[GDCPC 2024] 图
从原图中找出 \(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\) 应当是一棵森林。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=2e5,M=2e5,K=M;
int n,m;
//vector<int>g[N+1];
vector<vector<vector<int> > >tree;
int k;
struct dsu{
vector<int>f,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];
}
}
};
vector<dsu>dsu;
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<int>&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++){
vector<int>ans;
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
*/
CF773D Perishable Roads
给定一张 \(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\) 即可得到。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
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;
}