dp_2.(大多是树形dp)

P6076 [JSOI2015]染色问题,容斥dp,一般来说,容斥其实就是给你一些条件,叫你求在范围内的方案数,但是这个方案你发现很难直接求出来。选择学习一下别人的说法。在这里插入图片描述
对于这题,我们拆开考虑:
在这里插入图片描述

#include<bits/stdc++.h>
#define int long long  
using namespace std;
int n,m,c,mod=1e9+7,ans=0;
int fc[100001],inv[100001],f[10001];
int power(int x,int k)
{
	int rt=1;
	while(k)
	{
		if(k&1) rt=(rt*x)%mod;
		x=x*x%mod;k>>=1;
	}
	return rt;
}
void init()
{
	fc[0]=1;
	for(int i=1;i<=4001;i++) fc[i]=fc[i-1]*i%mod;
	inv[4000]=power(fc[4000],mod-2);
	for(int i=3999;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
	return ;
}
int C(int x,int y) 
{
	return fc[x]*inv[x-y]%mod*inv[y]%mod;
}
main()
{
	scanf("%lld%lld%lld",&n,&m,&c);init();
	for(int i=0;i<=c;i++)
	{
		for(int j=m,opt=1;j>=0;j--,opt*=-1) f[i]=(f[i]+opt*C(m,j)*power(power(i+1,j)-1,n)%mod+mod)%mod;
	}
	for(int i=c,opt=1;i>=0;i--,opt*=-1) ans=(ans+opt*C(c,i)%mod*f[i]%mod+mod)%mod;
	printf("%lld",ans);
	return 0;
}

说一个神奇dpP5888 传球游戏,实现起来还是蛮麻烦的。采用正反则难的思想,那么计算那些走不了的路就行,不过细节挺多难受啊。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,k,last[200001],mod=998244353;
int f[3][200001],cnt=0,len=0,c[200001];
struct pp
{
	int x,y,next;
};pp a[82001],p[82001];
void ins(int x,int y)
{
	int now=++cnt;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
bool cmp(const int &x,const int &y)
{
	return x<y;
}
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld%lld%lld",&n,&m,&k);c[++cnt]=1;
	for(int i=1;i<=k;i++)
	{
		int x,y;scanf("%lld%lld",&a[i].x,&a[i].y);
		c[++cnt]=a[i].x,c[++cnt]=a[i].y;	 	
	}
	sort(c+1,c+cnt+1,cmp);len=unique(c+1,c+cnt+1)-c-1;cnt=0;//printf(">>%lld ",len);
	for(int i=1;i<=k;i++)
	{
		int x=lower_bound(c+1,c+len+1,a[i].x)-c,y=lower_bound(c+1,c+len+1,a[i].y)-c;
		ins(x,y);//printf("%lld %lld\n",x,y);
	}
	int tmp=0,el=0;f[0][1]=1;
	while(m--)
	{
		int sum=0,now=el*(n-len-1)%mod;tmp^=1;
		for(int i=1;i<=len;i++)
		{
			sum=(sum+f[tmp^1][i])%mod;
			f[tmp][i]=0;
		}
		sum%=mod;(now+=sum)%=mod;
		(sum+=el*(n-len)%mod)%=mod;
		for(int i=1;i<=len;i++)
		{
			f[tmp][i]=(sum-f[tmp^1][i]+mod)%mod;
			for(int j=last[i];j!=-1;j=p[j].next)
			{
				int y=p[j].y;if(y==i) continue ;
				f[tmp][i]+=-f[tmp^1][y]+mod;
			}
			f[tmp][i]%=mod;	
		}	
		el=now;
	}
	printf("%lld",f[tmp][1]);
	return 0;
} 

P6477 [NOI Online #2 提高组] 子序列问题其实严格来说,这一题不算dp,至少不应该写在这。呃其实考虑dp也行,假设我们目前计算至第i-1位,考虑其贡献,那么就是上一个a[i]到这一个a[i]的位置才会有贡献,所以维护一个区间的乘积和就行啦!考虑对于每一个a[i]而言,到一个a[i]之间的距离每一位都会有贡献,也就使last[ a[ i ] ]+1 – i 的所有位置全部加一。那么考虑如何统计,我写不好呜呜呜放一下别人的。在这里插入图片描述
具体来说啦,我们可以先维护一个区间和,然后再维护乘积就行啦,然后前面那一坨a^2什么是不用管的,因为这是已经计算好了的。还有,每一个点贡献其实是全局的,感觉我的理解有些问题,我想的是若两个相同的处在同一个集里,那么就无法造成贡献,但其实是若是两个相同的处于一个集,它们两个分别造成贡献!靠去重之后离散有点问题,下次注意

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,root,mod=1e9+7,ans=0;
int a[2000001],key[2000001],last[2000001];
struct node
{
	int l,r,lc,rc,sum1,sum2,lazy;//sum1维护区间和,sum2维护的是区间平方和 
};node e[4000001];
bool cmp(const int &x,const int &y)
{
	return x<y;
}
int bt(int x,int y)
{
	int now=++len;
	int l=x,r=y,lc=-1,rc=-1;
	if(l==r) ;
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
	} 
	e[now]={l,r,lc,rc,0,0,0}; 
	return now;
}
void pushdown(int now)
{
	if(e[now].lazy==0) return ;
	int k=e[now].lazy,lc=e[now].lc,rc=e[now].rc;e[now].lazy=0;
	e[lc].sum2=(e[lc].sum2+e[lc].sum1*2*k+(e[lc].r-e[lc].l+1)*k*k)%mod;
	e[rc].sum2=(e[rc].sum2+e[rc].sum1*2*k+(e[rc].r-e[rc].l+1)*k*k)%mod;
	e[lc].sum1=(e[lc].sum1+k*(e[lc].r-e[lc].l+1))%mod;
	e[rc].sum1=(e[rc].sum1+k*(e[rc].r-e[rc].l+1))%mod;
	e[lc].lazy=(e[lc].lazy+k)%mod;e[rc].lazy=(e[rc].lazy+k)%mod;
	return ;
}
void cg(int now,int x,int y,int v)
{
	int l=e[now].l,r=e[now].r;
	if(l==x&&y==r) 
	{
		e[now].sum2=(e[now].sum2+e[now].sum1*2*v+(r-l+1)*v*v)%mod;
		e[now].sum1=(e[now].sum1+v*(r-l+1))%mod;
		e[now].lazy=(e[now].lazy+v)%mod;
	}
	else 
	{
		int lc=e[now].lc,rc=e[now].rc,mid=(l+r)/2;pushdown(now);
		if(x>=mid+1) cg(rc,x,y,v);
		else if(y<=mid) cg(lc,x,y,v);
		else cg(lc,x,mid,v),cg(rc,mid+1,y,v);
		e[now].sum1=(e[lc].sum1+e[rc].sum1)%mod;
		e[now].sum2=(e[lc].sum2+e[rc].sum2)%mod; 
	}
	return ;
}
signed main()
{
	memset(last,0,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),key[i]=a[i];
	sort(key+1,key+n+1,cmp);int cnt=unique(key+1,key+n+1)-(key+1);//printf("%lld %lld\n",cnt,n);
	root=bt(1,n+1);
	for(int i=1;i<=n;i++)
	{
		int x=lower_bound(key+1,key+cnt+1,a[i])-key;
		cg(root,last[x]+1,i,1);last[x]=i;
		ans=(ans+e[root].sum2)%mod;
	}
	printf("%lld",ans);
	return 0;
}

有一道树形dp你肯定感兴趣P8595 「KDOI-02」一个网的路,呃看起来很难搞。事实上也确实,考虑先将森林炸成数条链,假设我们炸了x条边,那么操作二的数量就是n-1-m+x,使炸掉的边数(x)+炸掉的点数(操作 1 的次数)最小,那么我们考虑进行dp,随便找一个根,令f[i][1/2/3] 表示 i 的子树已经被炸成若干链,而i这个点
1:被炸了
2:这个点没有被炸,它的儿子有 1 个连着它
3:这个点没有被炸,它的儿子有 2 个连着它
那么讨论一下,对于f[ i ][ 1 ],它肯定被炸了,那么对于它每一个儿子的贡献,肯定是儿子爆炸,或者为3情况的时候,贡献是最小的,为什么没有2情况呢,因为对于一个点的转移,若是炸成了2,也就会比3情况多炸一条边,那么这一说的话其实是不优的,然后考虑,自己爆炸后,那么连着自己的所有点都要重新找地儿链,而且自己还有1的操作贡献,那么这个情况就推好了。考虑2情况,如何呢,只有一个儿子的话那么就找所有儿子爆炸后有一个点与自己成链的最小值,就是,找一个点转移它f[y][1],然后其他点都是f[y][0],可以理解吧,完了现在神智不清。然后3的话其实也和二差不多,只不过找的点变成了两个而已。然后最终的答案就是枚举那个最小值啦取和啦~
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,last[2000051];
int f[2000051][4],cnt[2000051];
bool v[2000051]; 
struct pp
{
	int x,y,next;
};pp p[8000051];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}

void dfs(int x)
{
	v[x]=true;
	int maxx1=0,maxx2=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(v[y]) continue ;
		dfs(y);
		int kk=f[y][0]-f[y][1];
		if(kk>maxx1) maxx2=maxx1,maxx1=kk;
		else if(kk>maxx2) maxx2=kk;
		f[x][0]+=min(f[y][0]-1,f[y][2]);
		f[x][1]+=f[y][0];
	}
	f[x][0]+=cnt[x]+1;
	f[x][1]-=maxx1;
	f[x][2]=f[x][1]-maxx2;
	return ;
} 
int main()
{
	memset(last,-1,sizeof(last));//memset(v,false,sizeof(v));
	scanf("%d%d",&n,&m);int ans=(n-1)-m;
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);cnt[x]++,cnt[y]++;
	}
	for(int i=1;i<=n;i++) 
	{
		if(!v[i]) dfs(i),ans+=min(f[i][0],f[i][2]);
	}
	printf("%d",ans);
	return 0;
}

Choosing Capital for Treeland呃换根的话其实看出来了再简单推一下就好了,考虑直接建出双向边但是有边权,然后第一遍找出一个点的,然后直接转移。具体转移看代码。太久没有实现了,居然花一会。

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,last[400000],f[400000];
int minn,tot=0,ans[400000];
struct pp
{
	int x,y,c,next;
};pp p[500000];
bool cmp(const int &x,const int &y)
{
	return x<y;
}
void ins(int x,int y,int c)
{
	int now=++len;
	p[now]={x,y,c,last[x]};last[x]=now;
	return ;
}
int dfs1(int x,int fa)
{
	int rt=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		rt+=p[i].c;rt+=dfs1(y,x);
	}//printf("*%d %d\n",x,rt);
	return rt;
}
void dfs2(int x,int fa)
{
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		if(p[i].c==1) f[y]=f[x]-1;
		else f[y]=f[x]+1; 
		dfs2(y,x);
	}
	if(minn==f[x]) ans[++tot]=x;
	if(minn>f[x]) minn=f[x],tot=0,ans[++tot]=x;
	return ;
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		memset(last,-1,sizeof(last));f[1]=0;len=0;
		for(int i=1;i<=n-1;i++)
		{
			int x,y;scanf("%d%d",&x,&y);
			ins(x,y,0),ins(y,x,1);
		}
		f[1]=dfs1(1,1);minn=1e9;dfs2(1,1);
//		for(int i=1;i<=n;i++) printf("%d ",f[i]);
		printf("%d\n",minn);
		sort(ans+1,ans+tot+1,cmp);
		for(int i=1;i<=tot;i++) printf("%d ",ans[i]);	
		printf("\n");
	}
// 为了赶上他们皆是值得。
	return 0;
}

P1272 重建道路
考虑树形dp,背包,那么发现对于一个点,若是想从儿子转移上来,那么令f[ i ][ j ]记录的是由i为根的树中删的只剩下j个点的最小价值(明显可以证明一定成立只要不超过节点数量)。考虑一个三位的背包(可以滚)相较于上面会多一个k来记录已经计算过了多少个子节点。

#include<bits/stdc++.h>
using namespace std;
int n,m,k,root,len=0,last[1000001];
int f[200][200],siz[100001],ans=0;
struct pp
{
	int x,y,next;
};pp p[100001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{
	siz[x]=1,f[x][1]=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);siz[x]+=siz[y];
		for(int s=siz[x];s>=1;s--)
		{
			f[x][s]+=1;//滚动一下k,为何这样?考虑对于一个之前的子树,明显可以通过删掉当前的那个来得到s 
			for(int v=0;v<=min(s-1,siz[y]);v++) f[x][s]=min(f[x][s],f[x][s-v]+f[y][v]);
		} 
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));memset(f,63,sizeof(f));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);
	}
	dfs(1,1);ans=f[1][k];
	for(int i=2;i<=n;i++) ans=min(ans,f[i][k]+1);
	printf("%d",ans);
	return 0;
}

P1131 [ZJOI2007] 时态同步考虑反向做,那么从每一个节点的儿子到自己的距离应该相等,那么根开始走再从下往上维护,注意看,其维护的方式有且只有一种,而且每次都是边权加一,所以只要我们维护一下儿子的最大值然后直接让答案加上就行了。简单吧。具体看代码。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,last[1000000],sum[1000000],ans=0;
struct pp
{
	int x,y,c,next;
};pp p[1000000];
void ins(int x,int y,int c)
{
	int now=++len;
	p[now]={x,y,c,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{
	int maxx=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);maxx=max(maxx,sum[y]);
	}
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		ans+=(-1)*(sum[y]-maxx);
	}
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		if(p[i].y==fa) sum[x]=maxx+p[i].c;
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));memset(sum,0,sizeof(sum));
	int ST;scanf("%lld%lld",&n,&ST);
	for(int i=1;i<=n-1;i++) 
	{
		int x,y,c;scanf("%lld%lld%lld",&x,&y,&c);
		ins(x,y,c);ins(y,x,c); 
	}
	dfs(ST,ST);printf("%lld",ans);
	return 0;
} 

P4084 [USACO17DEC]Barn Painting G裸的简单dp,不多说:(注意我的赋值方式)

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,last[200001],mod=1e9+7;
int f[200001][4],a[200001];
struct pp
{
	int x,y,next;
};pp p[400001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{
	if(!a[x]) f[x][1]=f[x][2]=f[x][3]=1;
	else f[x][a[x]]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);
		f[x][1]=(f[x][1]*(f[y][2]+f[y][3])%mod)%mod;
		f[x][2]=(f[x][2]*(f[y][1]+f[y][3])%mod)%mod;
		f[x][3]=(f[x][3]*(f[y][1]+f[y][2])%mod)%mod;
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));memset(f,0,sizeof(f));
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y);ins(y,x);
	}
	for(int i=1;i<=m;i++)
	{
		int x,k;scanf("%lld%lld",&x,&k);
		a[x]=k;
	}
	dfs(1,1);
	printf("%lld",(f[1][1]+f[1][2]+f[1][3])%mod);
	return 0;
}

P4438 [HNOI/AHOI2018]道路这道有点意思,考虑用f[ x ][ i ][ j ]来表示在从根到第x个节点儿子中间有i个铁路,j个公路。考虑直接记忆(dp实现还麻烦些)。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,ll[20001],rr[20001];
int f[20001][41][41],a[20001],b[20001],c[20001];
int dfs(int i,int j,int k)
{
	if(i>=n) return c[i-n+1]*(a[i-n+1]+j)*(b[i-n+1]+k);
	if(f[i][j][k]!=f[0][0][0]) return f[i][j][k];
	return f[i][j][k]=min(dfs(ll[i],j,k)+dfs(rr[i],j,k+1),dfs(ll[i],j+1,k)+dfs(rr[i],j,k));
}
 main()
{
	memset(f,63,sizeof(f));
	scanf("%lld",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%lld",&ll[i]);if(ll[i]<0) ll[i]=n-ll[i]-1;
		scanf("%lld",&rr[i]);if(rr[i]<0) rr[i]=n-rr[i]-1; 
	}
	for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
	printf("%lld",dfs(1,0,0));
	return 0;
}

Appleman and Tree,看了题解,我要是说懂了那是假的,不过确实厉害这一题。对于每一棵子树,一个点要么在只有一个黑点的块里,要么是所在的块一个黑点都没有。那么满足了dp的无后效性。那么考虑转移。
在这里插入图片描述

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,last[200001],c[200001],mod=1e9+7;
int f[200001][3];
struct pp
{
	int x,y,next;
};pp p[800001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{	
	f[x][c[x]]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);
		f[x][1]=(f[x][1]*((f[y][0]+f[y][1])%mod)%mod+f[x][0]*f[y][1]%mod)%mod ;
		//对于f[x][1]来说: 
		//当x所在的区域里已经有1,那么对于它的一棵子树,有1,则断开;没1,则相连
		//当x在的区域里没有1,那么对于它的一棵子树,有1则相连;没1,则不符合条件 
		f[x][0]=(f[x][0]*(f[y][0]+f[y][1]))%mod;	
		//对于f[x][0]来说:
		//当x所在的区域里已经有1,不符合条件 
		//当x所在的区域里没有1,那么对于它的一棵子树,有1则断开,没1则相连 
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld",&x);x++;
		ins(x,i+1);ins(i+1,x);
	}
	for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
	memset(f,0,sizeof(f));dfs(1,1);
	printf("%lld",f[1][1]);
	return 0;
}

随便找了一道水题刷刷:Zublicanes and Mumocrates,不算难的树形背包。草写完之后就sb了,开全局longlong一直mle,nmd服了调了好久。

#include<bits/stdc++.h>
//#define int long long 
using namespace std;
int n,m,k,len=0,last[5001],siz[5001];
int f[5001][5001][3],in[5001],g[5001][3];
struct pp
{
	int x,y,next;
};pp p[10200];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
int dfs1(int x,int fa)
{
	int rt=0; 
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		rt+=dfs1(y,x);
	}
	return rt+(in[x]==1);
}
void dfs2(int x,int fa)
{
	if(in[x]==1) f[x][0][0]=f[x][1][1]=0,siz[x]=1;
	else f[x][0][0]=f[x][0][1]=0,siz[x]=0;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs2(y,x);int up1=min(siz[x],k/2),up2=min(siz[y],k/2);
		for(int j=0;j<=up1;j++) 
		{
			for(int k=0;k<=1;k++) g[j][k]=f[x][j][k],f[x][j][k]=f[0][0][0];//防止二次改制 
		}
		for(int j=0;j<=up1;j++)
		{
			up2=min(siz[y],k/2-j);
			for(int k=0;k<=up2;k++) 
			{
				f[x][j+k][0]=min(f[x][j+k][0],g[j][0]+min(f[y][k][0],f[y][k][1]+1));
				f[x][j+k][1]=min(f[x][j+k][1],g[j][1]+min(f[y][k][1],f[y][k][0]+1));
			}
		}
		siz[x]+=siz[y];
	}
	return ;
}
main()
{
	memset(last,-1,sizeof(last));memset(f,63,sizeof(f));
	scanf("%d",&n);
	if(n==2) 
	{
		printf("1");
		return 0;
	}
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);in[x]++,in[y]++;
	}
	k=dfs1(1,1);
	int root=1;while(in[root]==1) root++;
	dfs2(root,0);
	printf("%d",min(f[root][k/2][1],f[root][k/2][0]));
	return 0;
}

P6554 Promises I Can’t Keep裸的换根dp。好吧其实也不算裸,只不过不想写啊所以我贺了。

//这里可以提一提树上期望的处理,一般来说期望得乘上可能性
//但是我们可以计算出子树的重量,令g=期望*cnt(节点个数)那么转化为计算g就行啦 
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 5e5 + 10;
int n, m;
vector<int>e[N];
int cnt[N], num, vis[N];//cnt[u]表示u所有子树上的叶子节点总数 num表示总的叶子节点数 vis判断u点是不是叶子节点
double w[N]; //点权
double f[N], res;
int du[N]; //度,找到一个非叶子节点作为根节点
void dfs1(int u, int fa) {
	int leaf = 0;//判断u是不是叶子节点
	for (auto& v : e[u]) {
		if (v == fa)continue;
		dfs1(v, u);
		leaf = 1;
		cnt[u] += cnt[v];
		f[u] += f[v] + (cnt[v] * w[u]);
	}
	//边界处理
	if (!leaf) { 
		cnt[u] = 1;
		vis[u] = 1;
		f[u] = w[u];
		num++;
	}
}
void dfs2(int u, int fa) {

	for (auto& v : e[u]) {
		if (v == fa)continue;
		if (vis[v])
			f[v] = f[u] - f[v] - cnt[v] * w[u] + (num - cnt[v]) * w[v];
		else f[v] = f[v] + f[u] - f[v] - cnt[v] * w[u] + (num - cnt[v]) * w[v];
		dfs2(v, u);
	}
	double tmp = f[u] / (vis[u] ? num - 1 : num);
	res = max(res, tmp);
}
int main(void)
{
	cin >> n;
	int root = 0;
	for (int i = 1; i < n; ++i) {
		int a, b; cin >> a >> b;
		e[a].push_back(b);
		e[b].push_back(a);
		du[a]++; du[b]++;
		if (du[a] > 1)root = a;
		if (du[b] > 1)root = b;
	}
	for (int i = 1; i <= n; ++i)cin >> w[i];
	dfs1(root, -1);

	//一条链的情况
	if (cnt[root] == 2) {
		double sum = 0;
		for (int i = 1; i <= n; ++i) {
			sum += w[i];
			res = max(res, w[i]);
		}
		res = max(sum, (sum + res) / 2);
		printf("%.4f\n", res);
		return 0;
	}

	dfs2(root, -1);

	printf("%.4f\n", res);

	return 0;
}

P1453 城市环路呃小不水题?

//与其处理环不如直接找出那两个点然后分别算贡献。 
#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,last[200001],S,T,fa[200001];
int f[200001][3],a[200001];//0是不选,1是选择 
struct pp
{
	int x,y,next;
};pp p[400001];
int findfa(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findfa(fa[x]);
}
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{
	f[x][0]=0;f[x][1]=a[x];//printf("%d ",a[x]);
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue;
		dfs(y,x);
		f[x][1]+=f[y][0];f[x][0]+=max(f[y][0],f[y][1]);//printf("%d %d\n",f[x][1],f[x][0]);
	} 
	return ;	
} 
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);x++,y++;
		int fx=findfa(x),fy=findfa(y);
		if(fx==fy) S=x,T=y;
		else ins(x,y),ins(y,x),fa[fx]=fy;
	}
	double ans=0,k;scanf("%lf",&k);	
	dfs(S,S);ans=max(ans,(double)f[S][0]);//memset(f,0,sizeof(f));
	dfs(T,T);ans=max(ans,(double)f[T][0]);//printf("%lf %lf ",k,ans);

	printf("%.1lf",ans*k);	
	return 0;  
} 

最后的几道了!Tree Painting真的看起来很不错啊。

//简单的换根dp,发现选择了一个点之后其实后面的顺序都是确定的,而且第一个点的贡献只有一次,第二个点亦是如此
//那么考虑从一个点转移至另一个点,不妨先求出f[1],考虑若以2为根时。
//那么以1为根,除2以外所有子节点的上的所有点都会多算一次。反之以2为根的子树上的点会少算一次。
//那么转移方程就很简单了。f[y]=f[x]+(n-siz[y])-szi[y];(f是答案,siz是重量)
//说真,这题不该评蓝的呜呜呜(应该是黑的呜呜呜)。 
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,len=0,last[520001],siz[520001];
int f[520001],ans=0;
struct pp
{
	int x,y,next;
};pp p[520001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
int dfs1(int x,int fa)
{
	int rt=0;siz[x]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		rt+=dfs1(y,x);siz[x]+=siz[y];
	}
	return rt+siz[x];
}
void dfs2(int x,int fa)
{
	ans=max(f[x],ans);
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		f[y]=f[x]+n-siz[y]*2;dfs2(y,x);
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y);ins(y,x); 
	}
	f[1]=dfs1(1,1);dfs2(1,1);
	printf("%lld",ans);
	return 0;
}
//The way I still love you. 

最后两题Centroids

//转化重心,若本身是重心便无需改变,问题在于那些有子树>n/2的点
//那么找到那颗过大的子树,不妨想想要怎么删边,当然是找到在它之中最大但又小于n/2的那个点的边
//删掉后看看,那颗过大的子树还打不打,那么就好啦 
//所以重点的操作在于找到那个点,那么我们不妨考虑记录一个最大值(<=n/2)? 
//那么考虑如何转移,发现从父亲转移过来的时候,可能自己是最大的那个点,那么怎么办呢,不妨再多记录一个次大值就行啦
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10,mod=998244353;
int h[N],n,ans[N],f[N][3],siz[N],maxson[N],maxpos[N],selectmax[N],tot=0;
/*
f[x][0/1]维护的是x的子树中次大和最大的大小<=n/2的子树的大小
maxpos维护的是x的最大值是哪一个儿子
maxson维护的是针对于这个点我们切除的最大的<=n/2的树的大小
selectmax是方便判断,当x上方的父亲节点所在的树索要切割的
"最大"的最大子树的是哪个.如果最大是x所在的树我们就算则次大值判断
反之选最大值判断
*/
struct node
{
    int to,ne;
}edge[2*N];
void add(int x,int y)
{
    edge[++tot].to=y;
    edge[tot].ne=h[x];
    h[x]=tot;
    return ;
}
void dfs1(int x,int fa)
{
    maxpos[0]=0;
    f[x][0]=f[x][1]=1;
    siz[x]=1;//初始化
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to;
        if(y==fa)
            continue;
        dfs1(y,x);
        siz[x]+=siz[y];
        int v;
        if(siz[y]>siz[maxpos[x]])
            maxpos[x]=y;//记录最大的儿子
        if(siz[y]<=n/2)
            v=siz[y];
        else if(f[y][0]<=n/2)
            v=f[y][0];
        if(v>=f[x][0])
        {
            selectmax[x]=y;//记录我们所选的f的最大是谁
            f[x][1]=f[x][0];
            f[x][0]=v;
        }
        else if(v>=f[x][1])
            f[x][1]=v;
        //更新最大次大值
    }
    return ;
}
void dfs2(int x,int fa)
{
    ans[x]=1;
    if(siz[x]>n/2&&siz[maxpos[x]]-f[maxpos[x]][0]>n/2)
        ans[x]=0;
    //此处判断的是siz[x]>n/2所以我们从x的子树中切除某棵子树的情况
    //我们减去最大儿子的某棵子树,只要减去之后剩下的权重>n/2就不成立
    else if(siz[x]<=n/2&&n-siz[x]-maxson[x]>n/2)
    //此处判断的是siz[x]<=n/2所以我们从x的上方fa切除某棵子树(不包括含x的子树)
    //当减去了含x子树和上方的需要减去的子树剩下的树权重>n/2就不成立
        ans[x]=0;
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to,num;
        if(y==fa)
            continue;
 
        if(n-siz[x]>n/2) 
            num=maxson[x];
		else 
            num=n-siz[x];
        maxson[y]=max(maxson[y],num);
        //更新最大剪去数值的大小
        if(selectmax[x]==y)
            maxson[y]=max(maxson[y],f[x][1]);
        //当fa的最大的f的值是当前这个子节点,那么要去次大值,因为最大值不能包含这个点
        else
            maxson[y]=max(maxson[y],f[x][0]);
        //反之,这个点可以选,直接去最大值即可
        dfs2(y,x);
    }
    return ;
}
void solve()
{
    int x,y;
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=1;i<n;i++)
    {   
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值