【做题记录】2025刷题计划--树上问题
A. Minimum spanning tree for each edge
先建出最小生成树,对于树边答案就是最小生成树,对于非树边就从两个端点的路径上删掉权值最大的即可。
证明:在这个环中,首先强制选了这条边,然后按照从小到大的顺序选边,则一定不会选到删掉的那条边。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int n,m,fa[maxn],sz[maxn],dep[maxn];
int anc[maxn][22],mxv[maxn][22],ans[maxn];
bool vis[maxn];
struct edge{
int u,v,w,id;
il bool operator<(const edge &x)const{
return w<x.w;
}
}a[maxn];
vector<pii> e[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il void dfs(int u){
// cout<<u<<" "<<anc[u][0]<<"\n";
for(int i=1;i<=20;i++){
anc[u][i]=anc[anc[u][i-1]][i-1];
mxv[u][i]=max(mxv[u][i-1],mxv[anc[u][i-1]][i-1]);
}
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v==anc[u][0]){
continue;
}
anc[v][0]=u,mxv[v][0]=w;
dep[v]=dep[u]+1;
dfs(v);
}
}
il int query(int u,int v){
// cout<<u<<" "<<v<<"\n";
if(dep[u]<dep[v]){
swap(u,v);
}
int ddep=dep[u]-dep[v],tmp=0,res=0;
while(ddep){
if(ddep&1){
res=max(res,mxv[u][tmp]);
u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
// cout<<u<<" "<<v<<"\n";
if(u==v){
// puts("666");
return res;
}
for(int i=20;~i;i--){
if(anc[u][i]!=anc[v][i]){
res=max({res,mxv[u][i],mxv[v][i]});
u=anc[u][i],v=anc[v][i];
}
}
return max({res,mxv[u][0],mxv[v][0]});
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
a[i].id=i;
}
sort(a+1,a+m+1);
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
int res=0;
for(int i=1,u,v,w;i<=m;i++){
u=a[i].u,v=a[i].v,w=a[i].w;
if(find(u)!=find(v)){
merge(u,v);
vis[i]=1,res+=w;
e[u].pb(mp(v,w));
e[v].pb(mp(u,w));
}
}
// for(int i=1;i<=m;i++){
// if(vis[i]){
// cout<<a[i].id<<" ";
// }
// }
// puts("");
dfs(1);
// for(int u=1;u<=n;u++){
// for(int i=0;i<=3;i++){
// cout<<mxv[u][i]<<" ";
// }
// puts("");
// }
for(int i=1;i<=m;i++){
if(vis[i]){
ans[a[i].id]=res;
}
else{
// cout<<a[i].id<<" "<<query(a[i].u,a[i].v)<<"\n";
ans[a[i].id]=res-query(a[i].u,a[i].v)+a[i].w;
}
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
B. Information Reform
设 \(f_{u,i}\) 表示 \(u\) 接受 \(i\) 的信号,\(u\) 的子树内的答案。那么可以枚举 \(u\) 的儿子 \(v\) 接受信号的节点来转移。注意当 \(v\) 也枚举到 \(i\) 时要减去重复的 \(k\)。
考虑构造方案,设 \(ans_u\) 表示答案。首先可以求出 \(ans_1\)。对于 \(u\) 的每个儿子 \(v\),计算 \(u\) 是怎么从 \(v\) 转移来的即可。仍然注意当 \(v\) 枚举到 \(ans_u\) 时要减去重复的 \(k\)。
用 Floyd 计算两两之间的距离,时间复杂度 \(O(n^3)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=205,inf=0x3f3f3f3f3f3f3f3f;
int n,m,a[maxn],ans[maxn];
int dis[maxn][maxn];
int f[maxn][maxn];
vector<int> e[maxn];
il void dfs1(int u,int fa){
for(int i=1;i<=n;i++){
f[u][i]=a[dis[u][i]]+m;
}
for(int v:e[u]){
if(v==fa){
continue;
}
dfs1(v,u);
for(int i=1,tmp;i<=n;i++){
tmp=f[u][i];
f[u][i]+=f[v][i]-m;
for(int j=1;j<=n;j++){
f[u][i]=min(f[u][i],tmp+f[v][j]);
}
}
}
}
il void dfs2(int u,int fa){
for(int v:e[u]){
if(v==fa){
continue;
}
int res=inf;
for(int i=1;i<=n;i++){
if(res>f[v][i]){
res=f[v][i],ans[v]=i;
}
}
if(f[v][ans[u]]-m<f[v][ans[v]]){
ans[v]=ans[u];
}
dfs2(v,u);
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++){
cin>>a[i];
}
memset(dis,0x3f,sizeof dis);
for(int i=1;i<=n;i++){
dis[i][i]=0;
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
dis[u][v]=dis[v][u]=1;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++){
// cout<<dis[i][j]<<" ";
// }
// puts("");
// }
dfs1(1,0);
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++){
// printf("%2d ",f[i][j]);
// }
// puts("");
// }
int res=inf;
for(int i=1;i<=n;i++){
if(res>f[1][i]){
res=f[1][i],ans[1]=i;
}
}
cout<<res<<"\n";
dfs2(1,0);
for(int i=1;i<=n;i++){
cout<<ans[i]<<" ";
}
return 0;
}
}
signed main(){return asbt::main();}
C. 「JZOI-1」旅行
考虑一次询问,显然 DP,设 \(f_{u,0/1}\) 表示走路/坐船到 \(u\) 点的最小花费即可。
多次询问,考虑维护矩阵,广义矩阵乘,倍增处理询问。对每个点 \(u\) 维护 \(up_{u,i}\) 和 \(dw_{u,i}\) 表示从 \(u\) 向上走到 \(2^i\) 级祖先的矩阵和从 \(2^i\) 级祖先向下走到 \(u\) 的矩阵。时间复杂度 \(O((n+t)\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,m,q,enm,hd[maxn];
int anc[maxn][22],dep[maxn];
struct edge{
int v,nxt;
ll w,d;
bool typ;
}e[maxn<<1];
il void addedge(int u,int v,ll w,ll d,bool typ){
e[++enm]=(edge){v,hd[u],w,d,typ};
hd[u]=enm;
}
struct juz{
ll mat[2][2];
juz(){
mat[0][0]=mat[0][1]=mat[1][0]=mat[1][1]=0;
}
il ll*operator[](int x){
return mat[x];
}
il juz operator*(juz x)const{
juz res;
res[0][0]=res[0][1]=res[1][0]=res[1][1]=inf;
for(int i=0;i<=1;i++){
for(int j=0;j<=1;j++){
for(int k=0;k<=1;k++){
res[i][j]=min(res[i][j],mat[i][k]+x[k][j]);
}
}
}
return res;
}
}up[maxn][22],dw[maxn][22];
il void dfs(int u,int fa){
anc[u][0]=fa,dep[u]=dep[fa]+1;
for(int i=1;i<=20;i++){
anc[u][i]=anc[anc[u][i-1]][i-1];
up[u][i]=up[u][i-1]*up[anc[u][i-1]][i-1];
dw[u][i]=dw[anc[u][i-1]][i-1]*dw[u][i-1];
}
for(int i=hd[u],v,w,d,typ;i;i=e[i].nxt){
v=e[i].v,w=e[i].w,d=e[i].d,typ=e[i].typ;
if(v==fa){
continue;
}
up[v][0][0][0]=up[v][0][1][0]=w;
dw[v][0][0][0]=dw[v][0][1][0]=w;
if(typ){
up[v][0][0][1]=m+w+d;
up[v][0][1][1]=w+d;
dw[v][0][0][1]=m+w-d;
dw[v][0][1][1]=w-d;
}
else{
up[v][0][0][1]=m+w-d;
up[v][0][1][1]=w-d;
dw[v][0][0][1]=m+w+d;
dw[v][0][1][1]=w+d;
}
dfs(v,u);
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
for(int i=1,u,v,w,d,typ;i<n;i++){
cin>>u>>v>>w>>d>>typ;
addedge(u,v,w,d,typ);
addedge(v,u,w,d,typ^1);
}
dfs(1,0);
// for(int i=1;i<=n;i++){
// for(int j=0;j<=1;j++){
// cout<<up[i][j][0][0]<<" "<<up[i][j][0][1]<<" ";
// }
// puts("");
// for(int j=0;j<=1;j++){
// cout<<up[i][j][1][0]<<" "<<up[i][j][1][1]<<" ";
// }
// puts("");
// }
// for(int i=1;i<=n;i++){
// for(int j=0;j<=1;j++){
// cout<<dw[i][j][0][0]<<" "<<dw[i][j][0][1]<<" ";
// }
// puts("");
// for(int j=0;j<=1;j++){
// cout<<dw[i][j][1][0]<<" "<<dw[i][j][1][1]<<" ";
// }
// puts("");
// }
while(q--){
int u,v;
cin>>u>>v;
if(u==v){
cout<<"0\n";
continue;
}
juz resu,resv;
bool flu=0,flv=0;
if(dep[u]>dep[v]){
int ddep=dep[u]-dep[v],tmp=0;
while(ddep){
if(ddep&1){
if(!flu){
resu=up[u][tmp];
flu=1;
}
else{
resu=resu*up[u][tmp];
}
u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
if(u==v){
cout<<min(resu[0][0],resu[0][1])<<"\n";
continue;
}
}
else{
int ddep=dep[v]-dep[u],tmp=0;
while(ddep){
if(ddep&1){
if(!flv){
resv=dw[v][tmp];
flv=1;
}
else{
resv=dw[v][tmp]*resv;
}
v=anc[v][tmp];
}
ddep>>=1,tmp++;
}
if(u==v){
cout<<min(resv[0][0],resv[0][1])<<"\n";
continue;
}
}
// for(int i=0;i<=1;i++){
// for(int j=0;j<=1;j++){
// cout<<resu[i][j]<<" ";
// }
// puts("");
// }
// for(int i=0;i<=1;i++){
// for(int j=0;j<=1;j++){
// cout<<resv[i][j]<<" ";
// }
// puts("");
// }
for(int i=20;~i;i--){
if(anc[u][i]!=anc[v][i]){
if(!flu){
resu=up[u][i];
flu=1;
}
else{
resu=resu*up[u][i];
}
if(!flv){
resv=dw[v][i];
flv=1;
}
else{
resv=dw[v][i]*resv;
}
u=anc[u][i],v=anc[v][i];
}
}
if(!flu){
resu=up[u][0];
}
else{
resu=resu*up[u][0];
}
if(!flv){
resv=dw[v][0];
}
else{
resv=dw[v][0]*resv;
}
juz res=resu*resv;
cout<<min(res[0][0],res[0][1])<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
D. 「DBOI」Round 1 人生如树
新加的点不会影响之前的询问,所以直接离线,先把所有点都建好。
将问题转化为:用 \(b\) 数组减去 \(a\) 数组,得到的形如 \(1,2,3,\dots\) 的等差序列的最大长度。
考虑将两个序列哈希,预处理出等差数列的哈希值,二分长度即可。而在树上维护路径数组的哈希值,可以用倍增解决。
时间复杂度 \(O(m\log^2n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
const ull bas1=13331;
const ll bas2=13327,mod2=1e9+7;
int n,m,q,T,_2[22];
int a[maxn],dep[maxn];
int anc[maxn][22];
vector<int> e[maxn];
struct wnti{
int u1,v1,u2,v2;
}wt[maxn];
ull tar1[maxn],pw1[maxn];
ull up1[maxn][22],dw1[maxn][22];
ll tar2[maxn],pw2[maxn];
ll up2[maxn][22],dw2[maxn][22];
il void dfs(int u,int fa){
anc[u][0]=fa,dep[u]=dep[fa]+1;
up1[u][0]=dw1[u][0]=a[u]*pw1[1];
up2[u][0]=dw2[u][0]=a[u]*pw2[1]%mod2;
for(int i=1;i<=20;i++){
anc[u][i]=anc[anc[u][i-1]][i-1];
up1[u][i]=up1[u][i-1]+up1[anc[u][i-1]][i-1]*pw1[_2[i-1]];
dw1[u][i]=dw1[u][i-1]*pw1[_2[i-1]]+dw1[anc[u][i-1]][i-1];
up2[u][i]=(up2[u][i-1]+up2[anc[u][i-1]][i-1]*pw2[_2[i-1]])%mod2;
dw2[u][i]=(dw2[u][i-1]*pw2[_2[i-1]]+dw2[anc[u][i-1]][i-1])%mod2;
}
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
}
}
il int lca(int u,int v){
if(dep[u]<dep[v]){
swap(u,v);
}
int ddep=dep[u]-dep[v],tmp=0;
while(ddep){
if(ddep&1){
u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
if(u==v){
return u;
}
for(int i=20;~i;i--){
if(anc[u][i]!=anc[v][i]){
u=anc[u][i],v=anc[v][i];
}
}
return anc[u][0];
}
il int ganc(int u,int k){
int tmp=0;
while(k){
if(k&1){
u=anc[u][tmp];
}
k>>=1,tmp++;
}
return u;
}
il ull gha1(int u,int v,int rt,int len){
// cout<<u<<" "<<v<<"\n";
if(len<=dep[u]-dep[rt]+1){
int tmp=0,cur=0;
ull res=0;
while(len){
if(len&1){
res+=up1[u][tmp]*pw1[cur];
cur+=_2[tmp],u=anc[u][tmp];
}
len>>=1,tmp++;
}
// cout<<res<<"\n";
return res;
}
int ddep=dep[u]-dep[rt]+1;
int tmp=0,cur=0;
ull res=0;
len-=ddep;
int tdep=ddep;
while(ddep){
if(ddep&1){
res+=up1[u][tmp]*pw1[cur];
cur+=_2[tmp],u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
v=ganc(v,dep[v]-dep[rt]-len);
// cout<<v<<" "<<res<<"\n";
tmp=cur=0;
int tlen=len;
while(len){
if(len&1){
res+=dw1[v][tmp]*pw1[tdep+tlen-cur-_2[tmp]];
cur+=_2[tmp],v=anc[v][tmp];
}
len>>=1,tmp++;
}
// cout<<res<<"\n";
return res;
}
il ll gha2(int u,int v,int rt,int len){
if(len<=dep[u]-dep[rt]+1){
int tmp=0,cur=0;
ll res=0;
while(len){
if(len&1){
(res+=up2[u][tmp]*pw2[cur])%=mod2;
cur+=_2[tmp],u=anc[u][tmp];
}
len>>=1,tmp++;
}
return res;
}
int ddep=dep[u]-dep[rt]+1;
int tmp=0,cur=0;
ll res=0;
len-=ddep;
int tdep=ddep;
while(ddep){
if(ddep&1){
(res+=up2[u][tmp]*pw2[cur])%=mod2;
cur+=_2[tmp],u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
v=ganc(v,dep[v]-dep[rt]-len);
tmp=cur=0;
int tlen=len;
while(len){
if(len&1){
(res+=dw2[v][tmp]*pw2[tdep+tlen-cur-_2[tmp]])%=mod2;
cur+=_2[tmp],v=anc[v][tmp];
}
len>>=1,tmp++;
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>T;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
while(m--){
int opt;
cin>>opt;
if(opt==1){
int u1,v1,u2,v2;
cin>>u1>>v1>>u2>>v2;
wt[++q]=(wnti){u1,v1,u2,v2};
}
else{
int u,w;
cin>>u>>w;
a[++n]=w;
e[u].pb(n),e[n].pb(u);
}
}
pw1[0]=pw2[0]=1;
for(int i=1;i<=n;i++){
pw1[i]=pw1[i-1]*bas1;
pw2[i]=pw2[i-1]*bas2%mod2;
tar1[i]=tar1[i-1]+i*pw1[i];
tar2[i]=(tar2[i-1]+i*pw2[i])%mod2;
}
_2[0]=1;
for(int i=1;i<=20;i++){
_2[i]=_2[i-1]<<1;
}
dfs(1,0);
// for(int u=1;u<=n;u++){
// for(int i=0;i<=2;i++){
// cout<<anc[u][i]<<" ";
// }
// puts("");
// }
for(int i=1,u1,v1,u2,v2,rt1,rt2,l,r;i<=q;i++){
u1=wt[i].u1,v1=wt[i].v1;
u2=wt[i].u2,v2=wt[i].v2;
rt1=lca(u1,v1),rt2=lca(u2,v2);
l=0;
r=min(dep[u1]+dep[v1]-2*dep[rt1],dep[u2]+dep[v2]-2*dep[rt2])+1;
// cout<<l<<" "<<r<<"\n";
while(l<r){
int mid=(l+r+1)>>1;
// cout<<l<<" "<<r<<" "<<mid<<"\n";
if(gha1(u2,v2,rt2,mid)-gha1(u1,v1,rt1,mid)==tar1[mid]&&(gha2(u2,v2,rt2,mid)-gha2(u1,v1,rt1,mid)+mod2)%mod2==tar2[mid]){
l=mid;
}
else{
r=mid-1;
}
}
cout<<l<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
E. Mobile Phone Network
首先强制选择那 \(k\) 条边跑最小生成树。然后用非树边限制路径上的边。时间复杂度 \(O(m\log^2n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
namespace IO{
const int bufsz=1<<20;
char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
il int read(){
char ch=getchar();
while(ch<'0'||ch>'9'){
ch=getchar();
}
int x=0;
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
#undef getchar
char obuf[bufsz],*p3=obuf,s[50];
#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
il void write(ll x){
if(x<0){
putchar('-');
x=-x;
}
int top=0;
do{
s[++top]=x%10|48;
x/=10;
}while(x);
while(top){
putchar(s[top--]);
}
putchar('\n');
}
#undef putchar
class Flush{
public:
~Flush(){
flush();
}
}FL;
#undef flush
}
using IO::read;
using IO::write;
const int maxn=5e5+5;
const int inf=0x3f3f3f3f;
int n,k,m,cnt,fa[maxn],sz[maxn];
int hes[maxn],dep[maxn],top[maxn];
int dfn[maxn],idx[maxn],tofa[maxn];
int zhi[maxn<<2];
bool vis[maxn];
vector<pii> e[maxn];
struct edge{
int u,v,w;
il bool operator<(const edge &x)const{
return w<x.w;
}
}a[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il void dfs1(int u){
// cout<<u<<"\n";
// puts("666");
sz[u]=1;
int mxs=0;
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v==fa[u]){
continue;
}
fa[v]=u,tofa[v]=w;
dep[v]=dep[u]+1;
dfs1(v);
sz[u]+=sz[v];
if(mxs<sz[v]){
mxs=sz[v],hes[u]=v;
}
}
}
il void dfs2(int u){
dfn[u]=++cnt,idx[cnt]=u;
if(!top[u]){
top[u]=u;
}
if(hes[u]){
top[hes[u]]=top[u];
dfs2(hes[u]);
}
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v==fa[u]||v==hes[u]){
continue;
}
dfs2(v);
}
}
il void build(int id,int l,int r){
if(l==r){
zhi[id]=tofa[idx[l]];
return ;
}
zhi[id]=inf;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
il void upd(int id,int L,int R,int l,int r,int v){
if(l>r){
return ;
}
if(L>=l&&R<=r){
zhi[id]=min(zhi[id],v);
return ;
}
int mid=(L+R)>>1;
if(l<=mid){
upd(lid,L,mid,l,r,v);
}
if(r>mid){
upd(rid,mid+1,R,l,r,v);
}
}
il int query(int id,int l,int r,int p){
if(l==r){
return zhi[id];
}
int mid=(l+r)>>1;
if(p<=mid){
return min(zhi[id],query(lid,l,mid,p));
}
return min(zhi[id],query(rid,mid+1,r,p));
}
il void upd(int u,int v,int w){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
upd(1,1,n,dfn[top[u]],dfn[u],w);
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
upd(1,1,n,dfn[u]+1,dfn[v],w);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
// freopen("E.in","r",stdin);
n=read(),k=read(),m=read();
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
for(int i=1,u,v;i<=k;i++){
u=read(),v=read();
e[u].pb(mp(v,inf));
e[v].pb(mp(u,inf));
merge(u,v);
}
for(int i=1;i<=m;i++){
a[i].u=read(),a[i].v=read(),a[i].w=read();
}
sort(a+1,a+m+1);
for(int i=1,u,v;i<=m;i++){
u=a[i].u,v=a[i].v;
if(find(u)!=find(v)){
merge(u,v);
vis[i]=1;
e[u].pb(mp(v,0));
e[v].pb(mp(u,0));
}
}
for(int i=1;i<=n;i++){
fa[i]=0;
}
dfs1(1),dfs2(1);
// puts("666");
build(1,1,n);
for(int i=1;i<=m;i++){
if(!vis[i]){
upd(a[i].u,a[i].v,a[i].w);
}
}
ll ans=0;
for(int i=1,res;i<=n;i++){
res=query(1,1,n,i);
if(res>=inf){
write(-1);
return 0;
}
ans+=res;
}
write(ans);
return 0;
}
}
signed main(){return asbt::main();}
F. Close Vertices
点分治,对于每个子树中的点按照到重心的边权和排序,用双指针维护边权和的限制,用树状数组维护边数限制。时间复杂度 \(O(n\log^2n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int n,m,k,enm=1,hd[maxn];
int sz[maxn],mxs[maxn];
int dep[maxn],dis[maxn];
bool ban[maxn<<1];
vector<int> curd;
struct edge{
int v,nxt,w;
}e[maxn<<1];
il void addedge(int u,int v,int w){
e[++enm]=(edge){v,hd[u],w};
hd[u]=enm;
}
struct{
int tr[maxn];
il int lowbit(int x){
return x&-x;
}
il void upd(int p,int v){
for(;p<=n+1;p+=lowbit(p)){
tr[p]+=v;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
}F;
il void dfs1(int u,int fa,int tot){
sz[u]=1,mxs[u]=0;
for(int i=hd[u],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||ban[i]){
continue;
}
dfs1(v,u,tot);
sz[u]+=sz[v];
mxs[u]=max(mxs[u],sz[v]);
}
mxs[u]=max(mxs[u],tot-sz[u]);
}
il void dfs2(int u,int fa){
for(int i=hd[u],v,w;i;i=e[i].nxt){
v=e[i].v,w=e[i].w;
if(v==fa||ban[i]){
continue;
}
dep[v]=dep[u]+1,dis[v]=dis[u]+w;
dfs2(v,u);
}
}
il ll gans(){
sort(curd.begin(),curd.end(),[](const int x,const int y){return dis[x]<dis[y];});
for(int u:curd){
F.upd(dep[u]+1,1);
// cout<<u<<" "<<dep[u]<<" "<<dis[u]<<"\n";
}
int l=0,r=curd.size()-1;
ll res=0;
while(l<r){
if(dis[curd[l]]+dis[curd[r]]<=k){
F.upd(dep[curd[l]]+1,-1);
res+=F.query(max(m-dep[curd[l++]]+1,0));
}
else{
F.upd(dep[curd[r--]]+1,-1);
}
}
F.upd(dep[curd[l]]+1,-1);
return res;
}
il void dfs3(int u,int fa){
curd.pb(u);
for(int i=hd[u],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||ban[i]){
continue;
}
dfs3(v,u);
}
}
il ll solve(){
if(curd.size()==1){
return 0;
}
dfs1(curd.front(),0,curd.size());
int rt=0,mmx=inf;
for(int u:curd){
if(mmx>mxs[u]){
mmx=mxs[u],rt=u;
}
}
// cout<<rt<<"\n";
dep[rt]=dis[rt]=0;
dfs2(rt,0);
ll res=0;
res+=gans();
// cout<<res<<"\n";
for(int i=hd[rt],v;i;i=e[i].nxt){
if(ban[i]){
continue;
}
v=e[i].v;
curd.clear();
dfs3(v,rt);
res-=gans();
}
for(int i=hd[rt],v;i;i=e[i].nxt){
if(ban[i]){
continue;
}
v=e[i].v;
ban[i]=ban[i^1]=1;
curd.clear();
dfs3(v,0);
res+=solve();
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// cout<<cplx::usdmem();
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>k;
for(int i=2,u,w;i<=n;i++){
cin>>u>>w;
addedge(u,i,w);
addedge(i,u,w);
}
for(int i=1;i<=n;i++){
curd.pb(i);
}
cout<<solve()<<"\n";
return 0;
}
}
int main(){return asbt::main();}
G. Nastia Plays with a Tree
构造题,从下往上构造,将每个子树都连成一条链。
链分为两种:
- 以根为一头的链
- 跨过根的链
对于一个节点 \(u\),若它的儿子中没有或只有一个 \(1\) 号链,那就把其它儿子接上去,将 \(u\) 的子树变为 \(1\) 号链;否则就保留 \(2\) 个子树,将 \(u\) 的子树变为 \(2\) 号链。
时间复杂度 \(O(n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int T,n;
vector<int> e[maxn],curd[maxn];
struct{
int a,b,typ;
}zis[maxn];
struct node{
int a,b,c,d;
};
vector<node> ans;
il void dfs(int u,int fa){
// cout<<u<<"\n";
for(int v:e[u]){
if(v==fa){
continue;
}
// cout<<u<<" "<<v<<"\n";
dfs(v,u);
curd[u].pb(v);
}
// puts("");
zis[u].typ=1,zis[u].a=u;
sort(curd[u].begin(),curd[u].end(),[](const int &x,const int &y){return zis[x].typ<zis[y].typ;});
int cnt=0;
for(int v:curd[u]){
if(zis[v].typ==1){
cnt++;
}
else{
break;
}
}
// cout<<u<<" "<<curd.size()<<"\n";
if(!cnt){
for(int v:curd[u]){
ans.pb((node){u,v,zis[u].a,zis[v].a});
zis[u].a=zis[v].b;
}
}
else if(cnt==1){
zis[u].a=zis[curd[u][0]].a;
for(int i=1,v;i<curd[u].size();i++){
v=curd[u][i];
ans.pb((node){u,v,zis[u].a,zis[v].a});
zis[u].a=zis[v].b;
}
}else{
zis[u].typ=2;
zis[u].a=zis[curd[u][0]].a;
zis[u].b=zis[curd[u][1]].a;
for(int i=2,v;i<curd[u].size();i++){
v=curd[u][i];
if(zis[v].typ==1){
ans.pb((node){u,v,zis[u].a,v});
zis[u].a=zis[v].a;
}
else{
ans.pb((node){u,v,zis[u].a,zis[v].a});
zis[u].a=zis[v].b;
}
}
}
}
il void solve(){
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
ans.clear();
dfs(1,0);
for(int i=1;i<=n;i++){
// cout<<i<<" "<<zis[i].typ<<" "<<zis[i].a<<" "<<zis[i].b<<"\n";
}
cout<<ans.size()<<"\n";
for(node i:ans){
cout<<i.a<<" "<<i.b<<" "<<i.c<<" "<<i.d<<"\n";
}
for(int i=1;i<=n;i++){
e[i].clear();
curd[i].clear();
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
H. [ARC165E] Random Isolation
考虑将操作改为任选一个排列 \(p\),从 \(1\) 到 \(n\) 依次操作,如果 \(p_i\) 所在的连通块大小大于 \(k\),那么就删掉,否则就不删。这样的转化是正确的,因为当前 \(i-1\) 个位置相同时,当前可操作的点出现在 \(p_i\) 的概率是相等的。
考虑一个大小为 \(x\) 的连通块,它连向外面的边数为 \(y\),这个局面出现的概率就是 \(\frac{x!y!}{(x+y)!}\)。因此我们需要计数每种局面的数量。
设 \(f_{u,i,j}\) 表示 \(u\) 的子树内,强制选 \(u\),选了 \(i\) 个点,向外(除了 \(fa_u\))连了 \(j\) 条边的数量。背包转移即可。时间复杂度 \(O(n^4)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=105,mod=998244353;
int n,m,sz[maxn];
int nf[maxn][maxn];
int f[maxn][maxn][maxn];
int fac[maxn<<1],inv[maxn<<1];
vector<int> e[maxn];
il int qpow(int x,int y){
int res=1;
while(y){
if(y&1){
res=res*1ll*x%mod;
}
y>>=1,x=x*1ll*x%mod;
}
return res;
}
il void dfs(int u,int fa){
f[u][1][0]=sz[u]=1;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
for(int i=0;i<=sz[u]+sz[v];i++){
for(int j=0;j<=sz[u]+sz[v];j++){
nf[i][j]=0;
}
}
for(int i=0;i<=sz[u];i++){
for(int j=0;j<=sz[u];j++){
for(int x=0;x<=sz[v];x++){
for(int y=0;y<=sz[v];y++){
(nf[i+x][j+y]+=f[u][i][j]*1ll*f[v][x][y]%mod)%=mod;
}
}
}
}
sz[u]+=sz[v];
for(int i=0;i<=sz[u];i++){
for(int j=0;j<=sz[u];j++){
f[u][i][j]=nf[i][j];
}
}
}
f[u][0][1]=1;
}
il void init(int x){
fac[0]=1;
for(int i=1;i<=x;i++){
fac[i]=fac[i-1]*1ll*i%mod;
}
inv[x]=qpow(fac[x],mod-2);
for(int i=x;i;i--){
inv[i-1]=inv[i]*1ll*i%mod;
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
dfs(1,0),init(n<<1);
// for(int i=1;i<=n;i++){
// cout<<fac[i]<<" "<<fac[i]*1ll*inv[i]%mod<<"\n";
// }
int ans=0;
for(int u=1;u<=n;u++){
for(int i=m+1;i<=sz[u];i++){
for(int j=0,k;j<=sz[u];j++){
k=j;
if(u!=1){
k++;
}
(ans+=f[u][i][j]*1ll*fac[i]%mod*fac[k]%mod*inv[i+k]%mod)%=mod;
}
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
I. 「LNOI2014」LCA
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=5e4+5,mod=201314;
int n,m,fa[maxn],sz[maxn];
int hes[maxn],top[maxn];
int dfn[maxn],cnt,ans[maxn];
int sum[maxn<<2],tag[maxn<<2];
vector<int> e[maxn];
vector<pii> wt[maxn];
il void dfs1(int u){
sz[u]=1;
int mxs=0;
for(int v:e[u]){
dfs1(v);
sz[u]+=sz[v];
if(mxs<sz[v]){
mxs=sz[v],hes[u]=v;
}
}
}
il void dfs2(int u){
dfn[u]=++cnt;
if(!top[u]){
top[u]=u;
}
if(hes[u]){
top[hes[u]]=top[u];
dfs2(hes[u]);
}
for(int v:e[u]){
if(v==hes[u]){
continue;
}
dfs2(v);
}
}
il void pushup(int id){
sum[id]=sum[lid]+sum[rid];
}
il void pushtag(int id,int l,int r,int v){
tag[id]+=v;
sum[id]+=(r-l+1)*v;
}
il void pushdown(int id,int l,int r){
if(tag[id]){
int mid=(l+r)>>1;
pushtag(lid,l,mid,tag[id]);
pushtag(rid,mid+1,r,tag[id]);
tag[id]=0;
}
}
il void upd(int id,int L,int R,int l,int r,int v){
if(L>=l&&R<=r){
pushtag(id,L,R,v);
return ;
}
pushdown(id,L,R);
int mid=(L+R)>>1;
if(l<=mid){
upd(lid,L,mid,l,r,v);
}
if(r>mid){
upd(rid,mid+1,R,l,r,v);
}
pushup(id);
}
il int query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return sum[id];
}
pushdown(id,L,R);
int mid=(L+R)>>1,res=0;
if(l<=mid){
res+=query(lid,L,mid,l,r);
}
if(r>mid){
res+=query(rid,mid+1,R,l,r);
}
return res;
}
il void upd(int u){
while(u){
upd(1,1,n,dfn[top[u]],dfn[u],1);
u=fa[top[u]];
}
}
il int query(int u){
int res=0;
while(u){
res+=query(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=2;i<=n;i++){
cin>>fa[i];
fa[i]++;
e[fa[i]].pb(i);
}
dfs1(1),dfs2(1);
for(int i=1,l,r,u;i<=m;i++){
cin>>l>>r>>u;
r++,u++;
wt[l].pb(mp(u,-i));
wt[r].pb(mp(u,i));
}
for(int i=1;i<=n;i++){
upd(i);
for(pii j:wt[i]){
if(j.sec>0){
ans[j.sec]+=query(j.fir);
}
else{
ans[-j.sec]-=query(j.fir);
}
}
}
for(int i=1;i<=m;i++){
cout<<ans[i]%mod<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
J. Tree and Queries
考虑记录每个数量的颜色数,用树状数组查询。
对于每个子树,拍到 dfs 序上后就是一个区间。因此莫队即可。时间复杂度 \(O(n\sqrt{n}\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,a[maxn],ans[maxn];
int sz[maxn],dfn[maxn];
int cnt,idx[maxn],buc[maxn];
int blen,bel[maxn];
vector<int> e[maxn];
vector<pii> wt[maxn];
il void dfs(int u,int fa){
dfn[u]=++cnt;
idx[cnt]=a[u];
sz[u]=1;
for(int v:e[u]){
if(v!=fa){
dfs(v,u);
sz[u]+=sz[v];
}
}
}
struct wnti{
int l,r,u;
il bool operator<(const wnti &x)const{
if(bel[l]==bel[x.l]){
return bel[l]&1?r>x.r:r<x.r;
}
return l<x.l;
}
}wnt[maxn];
struct{
int tr[maxn];
il int lowbit(int x){
return x&-x;
}
il void upd(int p,int v){
for(;p<=n;p+=lowbit(p)){
tr[p]+=v;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
}F;
il void add(int x){
if(buc[x]){
F.upd(buc[x],-1);
}
F.upd(++buc[x],1);
}
il void del(int x){
F.upd(buc[x]--,-1);
if(buc[x]){
F.upd(buc[x],1);
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
for(int i=1,u,k;i<=m;i++){
cin>>u>>k;
if(k>n){
k=n+1;
}
wt[u].pb(mp(k,i));
}
dfs(1,0);
// puts("666");
for(int i=1;i<=n;i++){
wnt[i]=(wnti){dfn[i],dfn[i]+sz[i]-1,i};
}
blen=sqrt(n);
for(int i=1;i<=n;i++){
bel[i]=i/blen;
}
sort(wnt+1,wnt+n+1);
int l=1,r=0;
for(int i=1;i<=n;i++){
while(l>wnt[i].l){
add(idx[--l]);
}
while(r<wnt[i].r){
add(idx[++r]);
}
while(l<wnt[i].l){
del(idx[l++]);
}
while(r>wnt[i].r){
del(idx[r--]);
}
for(pii j:wt[wnt[i].u]){
ans[j.sec]=F.query(n)-F.query(j.fir-1);
}
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}