.
好久没有往blog搬运题解了,稍微总结一下
1.基环树
一个非常套路的题目类型,都是可以直接复制代码的那种
基本操作就是,dfs找环,让后拉出来倍长,最后在上面做dp即可
CodeForces 835F Roads in the Kingdom
大概就是要找删掉一条边后使得树最小的直径
先dp环旁边的数,找到子树的到根最长链,设为
f
f
f
倍长后令
F
i
=
S
i
+
f
i
,
G
i
=
f
i
−
S
i
F_i=S_i+f_i,G_i=f_i-S_i
Fi=Si+fi,Gi=fi−Si这里
S
S
S是环上
1
1
1号点到i的距离
线段树做区间最大值和次大值查询即可(
G
m
a
x
+
F
m
a
x
)
G_{max}+F_{max})
Gmax+Fmax)
感觉这个做法可能有些复杂,但是我不确定直接用后面的单调队列做法是不是对的
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 200010
#define LL long long
#define mid (l+r>>1)
#define ls l,mid,x<<1
#define rs mid+1,r,x<<1|1
using namespace std;
struct pr{ int x,y; } S[N<<3][2];
struct edge{ int v,c,nt; } G[N<<1];
int n,m,cnt,h[N],vis[N],w[N<<1];
LL g[N],f[N],A,B=1ll<<62,t=0,s[N<<1],F[N<<1][2];
inline bool c0(int i,int j){ return F[i][0]>F[j][0]; }
inline bool c1(int i,int j){ return F[i][1]>F[j][1]; }
inline pr m0(pr a,pr b){
int t[4]={a.x,a.y,b.x,b.y};
sort(t,t+4,c0); return (pr){t[0],t[1]};
}
inline pr m1(pr a,pr b){
int t[4]={a.x,a.y,b.x,b.y};
sort(t,t+4,c1); return (pr){t[0],t[1]};
}
inline void ps(int x){
int l=x<<1,r=x<<1|1;
S[x][0]=m0(S[l][0],S[r][0]);
S[x][1]=m1(S[l][1],S[r][1]);
}
inline void build(int l,int r,int x){
if(l==r){ S[x][0]=S[x][1]=(pr){l,0}; return; }
build(ls); build(rs); ps(x);
}
inline pr query1(int l,int r,int x,int L,int R){
if(L<=l && r<=R) return S[x][0];
pr ans={0,0};
if(L<=mid) ans=query1(ls,L,R);
if(mid<R) ans=m0(ans,query1(rs,L,R));
return ans;
}
inline pr query2(int l,int r,int x,int L,int R){
if(L<=l && r<=R) return S[x][1];
pr ans={0,0};
if(L<=mid) ans=query2(ls,L,R);
if(mid<R) ans=m1(ans,query2(rs,L,R));
return ans;
}
inline void adj(int x,int y,int c){
G[++cnt]=(edge){y,c,h[x]}; h[x]=cnt;
G[++cnt]=(edge){x,c,h[y]}; h[y]=cnt;
}
inline int fc(int x,int p){
if(vis[x]){ w[++t]=x; return 1; }
vis[x]=1;
for(int v,i=h[x];i;i=G[i].nt)
if((v=G[i].v)!=p && fc(v,x)){ w[++t]=x; return x!=w[1]; }
vis[x]=0; return 0;
}
inline void dfs(int x,int p){
f[x]=g[x]=0;
for(int v,i=h[x],t[4];i;i=G[i].nt)
if(!vis[v=G[i].v] && v!=p){
dfs(v,x);
if(f[v]+G[i].c>f[x]){
g[x]=f[x];
f[x]=G[i].c+f[v];
} else if(f[v]+G[i].c>g[x]) g[x]=f[v]+G[i].c;
}
}
int main(){
scanf("%d",&n);
for(int x,y,c,i=1;i<=n;++i){
scanf("%d%d%d",&x,&y,&c);
adj(x,y,c);
}
fc(1,0);
for(int i=1;i<=n;++i) if(vis[i]) dfs(i,0);
for(int i=1;i<=n;++i) A=max(A,f[i]+g[i]); --t;
for(int i=1;i<=t;++i){
for(int j=h[w[i]];j;j=G[j].nt)
if(G[j].v==w[i+1]) s[i+1]=s[i]+G[j].c;
w[i+t]=w[i];
}
for(int i=2;i<=t;++i) s[i+t]=s[i]+s[t+1]; F[0][0]=F[0][1]=-1e17;
for(int i=1;i<=t<<1;++i) F[i][0]=f[w[i]]+s[i],F[i][1]=f[w[i]]-s[i];
build(1,m=t<<1,1);
for(int i=1;i<=t;++i){
pr d[2]={query1(1,m,1,i,i+t-1),query2(1,m,1,i,i+t-1)};
if(d[0].x!=d[1].x) B=min(B,F[d[0].x][0]+F[d[1].x][1]);
else B=min(B,max(F[d[0].x][0]+F[d[1].y][1],F[d[0].y][0]+F[d[1].x][1]));
}
printf("%lld\n",max(A,B));
}
CodeForces 875F Royal Questions
这个题和上面画风不太一样
有点类似于带权的二分图匹配,但是明显会超时
考虑一种奇怪的做法,对于每个公主,我们看做一条边
(
a
i
,
b
i
)
(a_i,b_i)
(ai,bi)
那么我们选出一部分的边,使得整个图里面每个联通块边数都不超过点数,那么就一定有一个合法方案
为了使得答案最大,将边排序之后插入图中,用并查集维护连通性即可
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct edge{ int x,y,w; } graphs[200010];
int n,m,f[200010],g[200010],A;
inline int gf(int x){ return x==f[x]?x:f[x]=gf(f[x]); }
inline bool c1(edge a,edge b){ return a.w>b.w; }
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d%d%d",&graphs[i].x,&graphs[i].y,&graphs[i].w);
sort(graphs+1,graphs+1+m,c1);
for(int i=1;i<=n;++i) f[i]=i;
for(int i=1;i<=m;++i){
int x=gf(graphs[i].x),y=gf(graphs[i].y);
if(x!=y){
if(g[x]&g[y]) continue;
f[y]=x; g[x]|=g[y]; A+=graphs[i].w;
} else if(!g[x]){ g[x]=1; A+=graphs[i].w; }
}
printf("%d\n",A);
}
Bzoj 1040 骑士
这个题就和第一题一样套路了,先做环外子树的答案,让后环上分取不取1号点做2次dp即可,可以把这类题目当做一个模板
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1000010
using namespace std;
struct edge{ int v,nt; } G[N<<1];
int n,m,cnt=1,h[N],w[N],vis[N],t;
long long f[N],g[N],F[N],d[N];
inline void adj(int x,int y){
G[++cnt]=(edge){y,h[x]}; h[x]=cnt;
G[++cnt]=(edge){x,h[y]}; h[y]=cnt;
}
inline int fc(int x,int p){
if(vis[x]){ w[++t]=x; return 1; }
vis[x]=1;
for(int v,i=h[x];i;i=G[i].nt)
if(i!=p && fc(v=G[i].v,i^1)){ w[++t]=x; return x!=w[1]; }
return vis[x]=0;
}
inline void dfs(int x,int p){
f[x]=d[x]; g[x]=0;
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v]&&i!=p){
dfs(v,i^1);
f[x]+=g[v];
g[x]+=max(g[v],f[v]);
}
}
inline long long D(int x){
static long long G[N]; t=0;
fc(x,0); memset(G,0,t+5<<3);memset(F,0,t+5<<3); --t;
for(int i=1;i<=t;++i) dfs(w[i],0);
for(int i=1;i<t;++i){
F[i]=G[i-1]+f[w[i]]; G[i]=max(G[i-1],F[i-1])+g[w[i]];
}
G[t]=max(G[t-1],F[t-1])+g[w[t]];
long long ans=max(F[t-1],G[t]);
memset(F,0,t+3<<2); memset(G,0,t+3<<2);
for(int i=t;i>1;--i){
F[i]=G[i+1]+f[w[i]]; G[i]=max(G[i+1],F[i+1])+g[w[i]];
}
G[1]=max(G[2],F[2])+g[w[1]];
return max(ans,G[1]);
}
int main(){
scanf("%d",&n);
for(int x,i=1;i<=n;++i){
scanf("%d%d",d+i,&x);
adj(x,i);
}
long long ans=0;
for(int i=1;i<=n;++i)
if(!f[i]) ans+=D(i);
printf("%lld\n",ans);
}
Bzoj 2878迷失游乐园
我最不擅长的期望dp题,首先还是先用上刚刚的模板,让后考虑怎么dp
先定义一些变量
d
i
,
f
i
,
d
i
s
(
i
,
j
)
d_i,f_i,dis(i,j)
di,fi,dis(i,j)分别表示
i
i
i的儿子个数,向下走到叶子节点的期望步数和
i
,
j
i,j
i,j之间的距离
那么可以用dp求出
f
x
=
∑
v
∈
s
o
n
x
f
v
+
d
i
s
(
x
,
v
)
d
x
f_x=\frac{\sum_{v\in son_x}f_v+dis(x,v)}{d_x}
fx=dx∑v∈sonxfv+dis(x,v)
让后定义
g
x
g_x
gx表示
x
x
x往上走走到其他叶子节点的期望长度
g
x
=
d
i
s
(
p
,
x
)
+
f
p
d
p
−
f
x
−
d
i
s
(
p
,
x
)
+
g
p
d
p
g_x=dis(p,x)+\frac{f_pd_p-f_x-dis(p,x)+g_p}{d_p}
gx=dis(p,x)+dpfpdp−fx−dis(p,x)+gp这个式子的意思就是说,走上父亲节点后两种情况,要么是往除了
i
i
i以外的节点走去(
f
p
d
p
−
f
x
−
d
i
s
(
p
,
x
)
f_pd_p-f_x-dis(p,x)
fpdp−fx−dis(p,x))或者是继续往父亲走(
g
p
g_p
gp) 让后除以度数
d
p
d_p
dp
这个是树上的部分,现在继续考虑环
这个时候
g
g
g的定义就有所改变,我们考虑一下在环上走的方案
有两个方向,所以要分开考虑
我们将环上的节点的父亲,定义为环上和他相连的两个节点
是的,我们需要修改一下刚刚的式子
g
x
=
d
i
s
(
p
,
x
)
+
f
p
d
p
−
f
x
−
d
i
s
(
p
,
x
)
+
c
f
p
g
p
d
p
+
c
f
p
g_x=dis(p,x)+\frac{f_pd_p-f_x-dis(p,x)+cf_pg_p}{d_p+cf_p}
gx=dis(p,x)+dp+cfpfpdp−fx−dis(p,x)+cfpgp这里,
c
f
x
cf_x
cfx表示x的父亲个数,只能为
1
1
1或者
2
2
2
那么只要算出了环上节点的
g
g
g,就可以计算外面子树的答案了
考虑枚举每一个环上的节点作为出发点,假设为
s
s
s
那么考虑s往左走的所有节点(右边也做一次类似的)对
g
s
g_s
gs的贡献
写成转移式大概是这样的(这里
s
+
i
s+i
s+i是环上从s往下数i个的那个节点)
g
s
=
∑
i
=
1
∣
C
i
r
c
l
e
∣
(
∏
j
=
1
i
−
1
1
d
j
+
s
+
1
)
(
d
i
s
(
s
,
i
+
s
)
+
f
i
+
s
d
i
+
s
d
i
+
s
+
1
)
g_s=\sum_{i=1}^{|Circle|}(\prod_{j=1}^{i-1}\frac{1}{d_{j+s}+1})(dis(s,i+s)+\frac{f_{i+s}d_{i+s}}{d_{i+s}+1})
gs=i=1∑∣Circle∣(j=1∏i−1dj+s+11)(dis(s,i+s)+di+s+1fi+sdi+s)让后自然反方向走也会有一个类似的式子,改成
s
−
i
s-i
s−i即可
最后的答案就是
A
n
s
(
n
)
=
(
f
n
d
n
+
g
n
c
f
n
)
/
(
d
n
+
c
f
n
)
Ans(n)=(f_nd_n+g_ncf_n)/(d_n+cf_n)
Ans(n)=(fndn+gncfn)/(dn+cfn)
真的令人迷失心智,几乎就是把别人的式子抄了一次
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100010
#define db double
using namespace std;
struct edge{ int v,c,nt; } G[N<<1];
db f[N],g[N]; bool vis[N];
int n,m,cnt,h[N],d[N],son[N],w[N],t,cf[N],s[N];
inline void adj(int x,int y,int c){
G[++cnt]=(edge){y,c,h[x]}; h[x]=cnt;
G[++cnt]=(edge){x,c,h[y]}; h[y]=cnt;
}
inline int fc(int x,int p){
if(vis[x]){ w[++t]=x; return 1; }
vis[x]=1;
for(int v,i=h[x];i;i=G[i].nt)
if((v=G[i].v)!=p && fc(v,x)){ w[++t]=x; s[t]=s[t-1]+G[i].c; return x!=w[1]; }
return vis[x]=0;
}
inline void dfs(int x,int p){
f[x]=0;
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v]&&v!=p){
cf[v]=1; son[x]++;
dfs(v,x); f[x]+=f[v]+G[i].c;
}
if(son[x]) f[x]/=son[x];
}
inline void dgs(int x,int p){
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v]&&(v=G[i].v)!=p){
g[v]=G[i].c;
if(cf[x]+son[x]-1) g[v]+=(cf[x]*g[x]+son[x]*f[x]-f[v]-G[i].c)/(cf[x]+son[x]-1);
dgs(v,x);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int c,x,y,i=1;i<=m;++i){
scanf("%d%d%d",&x,&y,&c);
adj(x,y,c);
}
if(m<n){
dfs(1,0);
dgs(1,0);
} else {
fc(1,0); t--; db pk;
for(int i=1;i<=t;++i)
dfs(w[i],0),cf[w[i]]=2;
for(int i=1;i<=t;++i) w[i+t]=w[i],s[i+t]=s[i]+s[1+t];
for(int i=1;i<=t;++i){
pk=1.;
for(int j=1;j<t;++j){
if(j<t-1) g[w[i]]+=pk*(s[i+j]-s[i+j-1]+f[w[i+j]]*son[w[i+j]]/(son[w[i+j]]+1));
else g[w[i]]+=pk*(s[i+j]-s[i+j-1]+f[w[i+j]]);
pk/=(son[w[i+j]]+1);
}
}
for(int i=t<<1;i>t;--i){
pk=1.;
for(int j=1;j<t;++j){
if(j<t-1) g[w[i]]+=pk*(s[i-j+1]-s[i-j]+f[w[i-j]]*son[w[i-j]]/(son[w[i-j]]+1));
else g[w[i]]+=pk*(s[i-j+1]-s[i-j]+f[w[i-j]]);
pk/=(son[w[i-j]]+1);
}
}
for(int i=1;i<=t;++i){
g[w[i]]/=2.; dgs(w[i],0);
}
}
db A=0;
for(int i=1;i<=n;++i)
A+=(son[i]*f[i]+g[i]*cf[i])/(son[i]+cf[i]);
printf("%.5lf\n",A/n);
}
Bzoj1791 Island 岛屿
和第一个题一样的,直接套用即可,不过要稍加修改,因为整个图并不是联通的,所以需要对每个联通块分别统计答案,而且这里统计的是最大值,所以其实可以直接用单调队列在环上做dp,不过既然已经有成品了就跳过吧
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1000010
#define LL long long
#define mid (l+r>>1)
#define ls l,mid,x<<1
#define rs mid+1,r,x<<1|1
using namespace std;
struct pr{ int x,y; } S[N<<1][2];
struct edge{ int v,c,nt; } G[N<<1];
int n,m,cnt=1,h[N],w[N<<1]; bool cd[N],vis[N];
LL g[N],f[N],A,B=1ll<<62,t=0,s[N<<1],F[N<<1][2];
inline bool c0(int i,int j){ return F[i][0]>F[j][0]; }
inline bool c1(int i,int j){ return F[i][1]>F[j][1]; }
inline pr m0(pr a,pr b){
if(F[a.x][0]<F[b.x][0]) swap(a,b);
if(F[a.y][0]<F[b.x][0]) a.y=b.x; return a;
}
inline pr m1(pr a,pr b){
if(F[a.x][1]<F[b.x][1]) swap(a,b);
if(F[a.y][1]<F[b.x][1]) a.y=b.x; return a;
}
inline void ps(int x){
int l=x<<1,r=x<<1|1;
S[x][0]=m0(S[l][0],S[r][0]);
S[x][1]=m1(S[l][1],S[r][1]);
}
inline void build(int l,int r,int x){
if(l==r){ S[x][0]=S[x][1]=(pr){l,0}; return; }
build(ls); build(rs); ps(x);
}
inline pr query1(int l,int r,int x,int L,int R){
if(L<=l && r<=R) return S[x][0];
pr ans={0,0};
if(L<=mid) ans=query1(ls,L,R);
if(mid<R) ans=m0(ans,query1(rs,L,R));
return ans;
}
inline pr query2(int l,int r,int x,int L,int R){
if(L<=l && r<=R) return S[x][1];
pr ans={0,0};
if(L<=mid) ans=query2(ls,L,R);
if(mid<R) ans=m1(ans,query2(rs,L,R));
return ans;
}
inline void adj(int x,int y,int c){
G[++cnt]=(edge){y,c,h[x]}; h[x]=cnt;
G[++cnt]=(edge){x,c,h[y]}; h[y]=cnt;
}
inline int col(int x){
cd[x]=1;
for(int i=h[x];i;i=G[i].nt)
if(!cd[G[i].v]) col(G[i].v);
}
inline int fc(int x,int p){
if(vis[x]){ w[++t]=x; return 1; }
vis[x]=1;
for(int v,i=h[x];i;i=G[i].nt)
if(i!=p && fc(v=G[i].v,i^1)){ w[++t]=x; s[t]=s[t-1]+G[i].c; return x!=w[1]; }
vis[x]=0; return 0;
}
inline void dfs(int x,int p){
f[x]=g[x]=0;
for(int v,i=h[x],t[4];i;i=G[i].nt)
if(!vis[v=G[i].v] && i!=p){
dfs(v,i^1);
if(f[v]+G[i].c>f[x]){
g[x]=f[x];
f[x]=G[i].c+f[v];
} else if(f[v]+G[i].c>g[x]) g[x]=f[v]+G[i].c;
}
A=max(A,f[x]+g[x]);
}
inline LL D(int x){
t=0; col(x);
fc(x,0); A=B=0; --t;
for(int i=1;i<=t;++i) dfs(w[i],0);
for(int i=1;i<=t;++i){ w[i+t]=w[i]; s[i+t]=s[i]+s[t+1]; }
F[0][0]=F[0][1]=-1e17;
for(int i=1;i<=t<<1;++i) F[i][0]=f[w[i]]+s[i],F[i][1]=f[w[i]]-s[i];
build(1,m=t<<1,1);
for(int i=1;i<=t;++i){
pr d[2]={query1(1,m,1,i,i+t-1),query2(1,m,1,i,i+t-1)};
if(d[0].x!=d[1].x) B=max(B,F[d[0].x][0]+F[d[1].x][1]);
else B=max(B,max(F[d[0].x][0]+F[d[1].y][1],F[d[0].y][0]+F[d[1].x][1]));
}
return max(A,B);
}
int main(){
scanf("%d",&n);
for(int x,y,c,i=1;i<=n;++i){
scanf("%d%d",&x,&c);
adj(x,i,c);
}
LL ans=0;
for(int i=1;i<=n;++i)
if(!cd[i]){ ans+=D(i); }
printf("%lld\n",ans);
}