提高的题目

1771:仓库选址

https://blog.csdn.net/qyxpsx7/article/details/104003550
https://blog.csdn.net/QTY2001/article/details/78208026?locationNum=7&fps=1
首先想想m=0的情况:完全不用^,那么两遍dfs就解决问题了。主题思路就是一边算儿子传到父亲的,一遍算父亲传到儿子的。
再考虑m=1的情况,只要知道有多少个点到自己的距离是奇数,多少是偶数就行。也就是两遍dfs时多传个参,再根据边权是奇是偶讨论一下就好了。
我们延续上面的思路,m<=15,也就是说只会改变后四位的值,同理,我们记录后四位的值就好了,传递时,只要把后四位的值+边权,再取后四位即可。

https://www.cnblogs.com/HLAUV/p/9890073.html
既然是棵树,又要快速地求每个点的值,那一定是树形DP加上换根的操作啦~

但是异或m要怎么处理呢?可以观察数据规模,发现m最大最大也就15,换成二进制数也就是 1111,所以发现异或m最多只会对数字的后面4位造成影响(异或0甚至无法造成什么影响)

于是愉快地写出DP数组 f[i]和sz[i][0~15]

f表示此时以i为根的子树到i节点的距离之和(减去后缀后的和)

sz表示此时距离以j为后缀的共有几个

每一次向根节点方向转移时会加上一条边的长度,此时不同后缀距离的后缀会发生相应改变,然后更新父亲相应后缀的sz值。

然后f里统计的距离总和是抹掉所有后缀后的总和,即不考虑后缀的贡献。如有一个距离是 10111(2),抹去长度为2的后缀后就只剩下10100,然后将这个结果加到f数组里,到最后根节点统计最终答案时再考虑每个后缀的贡献,此时的sz数组就派上用场了(具体看代码)

还有一点,就是最后要把答案减去m,因为统计后缀贡献时,多加了自己到自己的距离(本来为0,xor m 后变成了m)。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;

int n,m;
int tot;
struct node{
	int to,nex,w;
}ed[maxn<<1];
int head[maxn];
LL dp[maxn],path[maxn][16],sch[16];
//path表示i到其他点,后四位为j的路径条数
//sche后四位状态为j的向上的路径条数
void adde(int u,int v,int w){
	ed[++tot].nex=head[u];
	ed[tot].to=v;
	ed[tot].w=w;
	head[u]=tot;
}
void inti(){
	scanf("%d %d",&n,&m);
	int a,b,c;
	for(int i=1;i<=n-1;i++){
		scanf("%d %d %d",&a,&b,&c);
		adde(a,b,c);
		adde(b,a,c);
	}
}
void dfs1(int x,int fa){  // 
	path[x][0]=1;
	for(int i=head[x];i;i=ed[i].nex){
		int tt=ed[i].to;
		int ww=ed[i].w;
		if(tt==fa) continue;
		dfs1(tt,x);
		dp[x]+=dp[tt];
		for(int j=0;j<16;j++){
			path[x][(j+ww)%16]+=path[tt][j];  //路径条数 
			dp[x]+=1LL*path[tt][j]*ww;  //jieguo
		}
	}
}
//换根
void dfs2(int x,int fa){
	for(int i=head[x];i;i=ed[i].nex){
		int tt=ed[i].to;
		int ww=ed[i].w;
		if(tt==fa) continue;
		int sz=0;
		memset(sch,0,sizeof(sch));
		for(int j=0;j<16;j++){
			sch[(ww+j)%16]+=path[x][j]-path[tt][((j-ww)%16+16)%16];
			//都是路径条数 
			sz+=path[tt][j];
		}
		dp[tt]=dp[x]+(n-sz*2)*ww;
		for(int j=0;j<16;j++) path[tt][j]+=sch[j];
		dfs2(tt,x);
	}
} 
void calc(){
	for(int i=1;i<=n;i++){
		--path[i][0];
		for(int j=0;j<16;j++){
			int val=(j^m)-j;
			dp[i]+=1LL*path[i][j]*val;
		}
	}
} 
void work(){
	dfs1(1,0);
	dfs2(1,0);
	calc();
}
int main(){
   inti();
    work(); 
    for(int i=1;i<=n;i++) printf("%d\n",dp[i]);
return 0;
}

  还是不是很懂

1772:动漫排序

现需要把两个约束转化为一个约束(通过建树),然后运用插板法(排列组合)

对于限制2,因为大儿子一定比小儿子先遍历,那么我们可以将小儿子当做前一个比它大的儿子的儿子。

现在,限制就只剩下“先父亲后儿子”,即求遍历一棵树,当父亲被走过才可以走儿子的方案数。
显然,这是一棵二叉树。
设f[x]表示,遍历以x为根的子树的方案数。
转移:
设两个儿子分别为i,j(只有一个儿子的话,f[x]就等于儿子的f值),以i为根的子树大小为s1,j的为s2;
f[x]=f[i]∗f[j]∗Cmin(s1,s2)s1+s2
(C组合求的是插板问题)
其实就是当前有s1个点,按顺序插入s2个点中。

记住这个插板法的使用方法

View Code

1773:消息传递

这个题如果数据范围小的话,可以用一种简单一点的写法,但是数据范围大就不行了

也是用到了换根dp

可以很方便的求出以u为根传递完它的子树的代价f[u]。
考虑如何求出传完它父亲及其上方点的代价。
使用换根dp。令g[u]表示去掉u及其子树并以u的父亲为根的全部代价。
将所有儿子的f与自己的g排序后即可求出所有儿子的g。
每个点的答案就很好计算了。

https://www.cnblogs.com/66t6/p/11622924.html
https://www.cnblogs.com/betablewaloot/p/12210779.html
另一种更更更简单的方法 !!!但是数据范围不行!因为加了一维,所以空间是不行的
加一维,空间换时间,同一颗子树在不同根进行dp的时候,如果父亲是一样的,那么结果就是一样的,可以直接调用---记忆化
拿出草稿纸,画一棵树如下: 1->2,3; 2->4,5; 3->6
这是把1当成根的情况,再画一棵树: 3->1,6; 1->2; 2->4,5
显然这是同一棵树,只是根不同,在常规解法中的作用是分别求出从1和3出发的最小值,但我们发现,在这两棵树中,dp[2]相等,因为他们都表示同一颗子树,
体现在这两棵树中2的父亲都是1,这就是突破口。

我们把状态加一维,变成dp[i][fa] (空间换时间,血赚不亏),表示以i节点为根的子树遍历完成的最小值,而fa值是根节点i的父亲,(若i是整棵树的根,那么fa=0),
那么这样在以不同根节点遍历树的时候,若以节点i为根的子树与之前计算过的以i为根节点的子树相同,即i的父亲相同,就可以直接调用。

View Code

1774:大逃杀

首先,我们这题显然是一道树形DP题。
我们可以设三个状态转移方程——
f[i,j]进入以i为根的子树并返回到i
g[i,j]进入以i为根的子树但不返回到i
h[i,j]从以i为根的子树里一点出发,经过i点并调回以i为根的子树中
用了j秒的最大武力值

然后转移方式每种类型都要考虑到

https://blog.csdn.net/weixin_30296405/article/details/96045379 这个写的很好

View Code

1775:梦中漫步

树形dp+期望
//这个期望,概率的计算就不说了,这个得可以写出式子来,然后还要回结合树形这个结构来计算
//https://www.cnblogs.com/betablewaloot/p/12247263.html
/*
首先,设d[i]为i的出度,f[i]为从i走向根节点的期望步数,g[i]为从根节点走到i的期望步数
f[i]=1/d[i]+Σ(f[i]+f[son]+1)/d[i] 简化之后:f[i]=d[i]+Σf[son]

g[i]=1/d[fa[i]]+(1+g[i]+g[fa[i]])/d[fa[i]]+Σ(son!=i)(1+g[i]+f[son])/d[fa[i]] 简化之后 ;g[i]=d[i]+g[fa[i]]+Σf[son]
然后就用dfs预处理出这两个数组
f[i]=f[i]+f[fa],g[i]=g[i]+g[fa],求到最大的祖先的期望步数
对于一组询问u,v,路径就是u到lca,和v到lca
那么ans=f[u]-f[lca]+g[v]-g[lca]

期望是具有线性结构的

View Code

 

换根dp

换根dp的通法:1.第一次扫描时,任选一个点为根,在“有根树”上执行一次树形DP,也就在回溯时发生的,自底向上的状态转移。

2.第二次扫描时,从刚才选出的根出发,对整棵树执行一次dfs,在每次递归前进行自上向下的推导,计算出换根后的解。

例题POJ3585 Accumulation Degree

View Code

 

LuoguP3478

View Code

 

 posted on 2022-08-08 23:35  shirlybabyyy  阅读(72)  评论(0)    收藏  举报