各种杂题合集

.
好久没有往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=fiSi这里 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=dxvsonxfv+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)+dpfpdpfxdis(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) fpdpfxdis(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+cfpfpdpfxdis(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=1Circle(j=1i1dj+s+11)(dis(s,i+s)+di+s+1fi+sdi+s)让后自然反方向走也会有一个类似的式子,改成 s − i s-i si即可
最后的答案就是 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);
}

持续更新ing

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值