24.5.19(换根dp)

星期一:

补19陕西 C 0689                                                   cf传送门

思路:先不看69,若翻转的区间端点sl==sr,则翻转的结果等于翻转 sl+1至sr-1的结果,所以翻转区间只需考虑左右端点会变化的,即除去00,88,69,96

作一个前缀和,枚举sr,可得知有多少可翻转的sl

注意若s存在0,8,69,96子串任一,答案需加上原字符串

代码如下:

ll n;
string s;
void solve(){
	cin >> s;
	n=s.size(); s=" "+s;
	int cnt[11]={0},if1=0;
	ll ans=0;
	for(int i=1;i<=n;i++){
		cnt[s[i]-'0']++;
		if(s[i]=='0') ans+=i-cnt[0],if1|=3;
		else if(s[i]=='8') ans+=i-cnt[8],if1|=3;
		else if(s[i]=='6') ans+=i-cnt[9],if1|=1;
		else ans+=i-cnt[6],if1|=2;
	}
	cout << ans+(if1==3) << "\n";
}

补山东理工校赛  C 01背包                                      牛客传送门

思路:和上星期二队内个人赛的F一个思路,方案数使用01背包转移出来

dp【i】【{x,y}】表示考虑前 i步,走到x,y的方案数

对每个操作,有走和不走两种选择,若不走,所有坐标的方案数不变,若走,枚举所有坐标,操作后到达的坐标加上原坐标的方案数,注意判断有无障碍

代码如下:

const int mod=998244353;
ll n;
map<PII,int>dp[110];
void solve(){
	int m,x,y; cin >> n >> m >> x >> y;
	string s; cin >> s; s=" "+s;
	set<PII>st;
	while(m--){
		int a,b; cin >> a >> b;
		st.insert({a,b});
	}
	dp[0][{x,y}]=1;
	for(int i=1;i<=n;i++){
		int dx=0,dy=0;
		if(s[i]=='U') dy=1;
		if(s[i]=='D') dy=-1;
		if(s[i]=='L') dx=-1;
		if(s[i]=='R') dx=1;
		for(auto [xy,sum]:dp[i-1]) dp[i][xy]=sum;     //不走这步
		for(auto [xy,sum]:dp[i-1]){                   //走这步
			int xx=xy.first+dx,yy=xy.second+dy;
			if(st.find({xx,yy})==st.end()) dp[i][{xx,yy}]+=sum,dp[i][{xx,yy}]%=mod;
			else dp[i][xy]+=sum,dp[i][xy]%=mod;
		}
	}
	cout << dp[n][{x,y}];
}

补山东理工校赛 E                                                   牛客传送门

思路:问有多少对( i, j) ( i<j 满足 a【i】*a【i】= k* a【j】,很明显是求对于a【i】后面有多少a【j】满足为a【i】*a【i】的因子( 赛时居然没反应过来

先筛出质数,再对每个数进行质因数分解,然后对因子进行暴力dfs,算出有多少a【j】为其因子,因为1e6内因子数量不大,所以dfs时间复杂度并不高

代码如下:

const int N=1e6+10,M=210;
const int mod=1e9+7;
ll n;
ll a[N],cnt[N];
int p[N],idx;
bool vi[N];
unordered_map<int,int>mp;
vector<PII>ve;
ll ans;
ll qpow(int a,int n){
	if(n==0) return 1;
	if(n==1) return a;
	ll s=qpow(a,n/2);
	s=s*s;
	if(n&1) s=s*a;
	return s;
}
void getp(int x){                                     //线性筛
	for(int i=2;i<=x;i++){
		if(!vi[i]) p[++idx]=i;
		for(int j=1;1ll*i*p[j]<=x;j++){
			vi[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
void getd(int x){
	for(int i=1;i<=idx && x/p[i]>=p[i];i++){          //分解质因数
		int cnt=0;
		while(x%p[i]==0){
			x/=p[i];
			cnt++;
		}
		if(!cnt) continue;
		mp[p[i]]+=cnt;
	}
	if(x>1) mp[x]++;
}
// void dfs(ll x,int idx){
// 	if(x>1e6) return ;
// 	if(idx==(int)ve.size()){ans+=cnt[x]; return ;}
// 	for(int i=0;i<=(int)ve[idx].second;i++)
// 		dfs(x*qpow(ve[idx].first,i),idx+1);         //和下面dfs等价,但比较笨,用到快速幂
// }
void dfs(ll x,int idx){                               //对因子进行任意组合
	if(x>1e6) return ;
	if(idx==(int)ve.size()){ans+=cnt[x]; return ;}
	ll t=1;
	for(int i=0;i<=ve[idx].second;i++){
		dfs(x*t,idx+1);
		t*=ve[idx].first;
	}
}
void solve(){
	getp(1e6);
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	cnt[a[n]]++;
	for(int i=n-1;i;i--){
		mp.clear(),ve.clear();
		getd(a[i]);
		for(auto [x,y]:mp) ve.push_back({x,y*2});   //找a[i]*a[i]的因子,所以y*=2
		dfs(1,0);
		cnt[a[i]]++;
	}
	cout << ans;
}

补上周二 队内个人赛 G                                           cf传送门

题意: 给 n个灯泡, 每个灯泡有其亮度x和周期t, 即亮 t秒后熄 t秒, 开灯时亮度为 x, 否则为 0, 问1到m秒, 每秒最亮的灯泡亮度为多少

思路:先将灯泡按亮度降序排序, 依次遍历覆盖1-m内区间, 遇到被覆盖区间则直接跳过, 被覆盖区间用并查集维护

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll ans[N],m;
struct nod{
	int t,x;
}b[N];
int fa[N];
bool vi[N];
bool cmp1(nod a,nod b){
	return a.x>b.x;
}
inline int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
inline void shine(int x,int t){
	for(int k=0;k<=m;k+=2*t){
		for(int i=k+1;i<=m && i<=k+t;i++){
			if(ans[i]){i=fnd(i); continue;}     //跳过被覆盖区间
			ans[i]=x,fa[i-1]=fa[i];
		}
	}
}
void solve(){
	int tt; cin >> tt;
	for(int ii=1;ii<=tt;ii++){
		cin >> n >> m;
		for(int i=1;i<=1e5;i++) vi[i]=0;
		for(int i=1;i<=m;i++) fa[i]=i;
		for(int i=1;i<=n;i++) cin >> b[i].t >> b[i].x;
		sort(b+1,b+n+1,cmp1);
		for(int i=1;i<=n;i++){
			if(vi[b[i].t]) continue; //周期已出现,之前有x更大且周期重复灯泡,此灯泡可跳过
			shine(b[i].x,b[i].t);
			vi[b[i].t]=1;
		}
		cout << "Case #" << ii << ": ";
		for(int i=1;i<=m;i++) cout << ans[i] << " \n"[i==m],ans[i]=0;
	}
}

星期二:

下午队内组队赛, 虽然有一些很低级的失误(os全锅), 总体来说发挥的还不错

补上周二队内个人赛 G(线段树做法                            cf传送门

思路: 线段树维护区间最大值的板子

又re了, 提两个注意的点

一还是要时刻确保 查询和修改的区间 ql一定要<=qr !!!

二要注意修改和查询的区间 一定要在 1-m以内, 不能超出范围 !!!

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		int ma,add;
	}tr[N];
	int ql,qr;
	nod merge(nod a,nod b){
		nod res={0,0,0,0};           //一定把懒标记初始化为0
		res.l=a.l,res.r=b.r;
		res.ma=max(a.ma,b.ma);
		return res;
	}
	void pushup(int p){tr[p]=merge(tr[lc],tr[rc]);}
	void pushdn(int p){
		if(!tr[p].add) return ;
		tr[lc].ma=tr[p].add;
		tr[rc].ma=tr[p].add;
		tr[lc].add=tr[p].add;
		tr[rc].add=tr[p].add;
		tr[p].add=0;
	}
	void bd(int p,int l,int r){
		tr[p]={l,r,0,0};
		if(l==r) return ;
		int m=l+r>>1;
		bd(lc,l,m);
		bd(rc,m+1,r);
		pushup(p);
	}
	void update(int p,int v){
		if(ql<=tr[p].l && qr>=tr[p].r){
			tr[p].ma=v;
			tr[p].add=v;
			return ;
		}
		int m=tr[p].l+tr[p].r>>1;
		pushdn(p);
		if(ql<=m) update(lc,v);
		if(qr>m) update(rc,v);
		pushup(p);
	}
	nod query(int p){
		if(ql<=tr[p].l && qr>=tr[p].r) return tr[p];
		int m=tr[p].l+tr[p].r>>1;
		pushdn(p);
		if(qr<=m) return query(lc);
		if(ql>m) return query(rc);
		return merge(query(lc),query(rc));
	}
	void updt(int l,int r,int v){
		ql=l;
		qr=r;
		update(1,v);
	}
	int ask(int l,int r){
		ql=l;
		qr=r;
		return query(1).ma;
	}
#undef lc
#undef rc
}t;
void solve(){
	int tt; cin >> tt;
	for(int ii=1;ii<=tt;ii++){
		int m; cin >> n >> m;
		t.bd(1,1,m);
		unordered_map<int,int>mp;
		for(int i=1;i<=n;i++){
			int t,x; cin >> t >> x;
			mp[t]=max(x,mp[t]);
		}
		vector<PII>ve;
		for(auto [t,x]:mp) ve.push_back({x,t});
		sort(ve.begin(),ve.end());                //灯泡按亮度排序
		for(auto [a,b]:ve){
			for(int k=0;k<m;k+=2*b)           //若设为k<=m,k+1即ql可能超出m
				t.updt(k+1,min(k+b,1ll*m),a);    //小心re,小心k+b超出m
		}
		cout << "Case #" << ii << ": ";
		for(int i=1;i<=m;i++) cout << t.ask(i,i) << " \n"[i==m];
	}
}

星期三:

补24河南邀请赛 A 构造                                          cf传送门

思路:题意可转化为找一个幸运数,满足num%n==0

n的范围是[1,1e8], k的范围是[1,2e10],所以幸运数的范围可在n的2e10倍之内

把幸运数构造为123456789*10+d,此时满足幸运数的条件,那么如何使其%n==0呢

在num末尾加上n的位数个0,然后num+=n - num%n,可确保num%n==0, 且 num/n在2e10内

代码如下:

ll n;
void solve(){
	int d; cin >> n >> d;
	ll num=123456789ll*10+d;
	int sz=to_string(n).size();
	while(sz--) num*=10;
	num+=n-num%n;
	cout << num/n << "\n";
}

补河南邀请赛 L  dp                                              cf传送门

赛时os出的这题,我先交了发wa 6,遂补

思路:注意到22的4次幂已经>=2e5了,所以可以用二维的dp数组,第二维只需开30

dp【i】【j】表示考虑到第 i个碧油鸡,连续往上处理 j个碧油鸡的所需时间

当时为什么会wa6呢,因为第二三重循环枚举第二维时,没将其限制在22以内,因为初始化也只初始化到了26,第二维超出26值为0,所以转移出问题了,如果数据范围再大点,就会因为三重循环n^3的复杂度T掉。。也属实是我粗糙了

代码如下:

const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
ll dp[N][30];
int a[N];
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		cin >> a[i];
		for(int j=0;j<=26;j++) dp[i][j]=1e18;      //从0开始初始化
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=min(i,22);j++)          //记得取min,将第二维限制在22以内
			for(int k=0;k<=min(i-j,22);k++)    //k从0开始,转移需要dp[0][0]
				dp[i][j]=min(dp[i-j][k]+a[i]+1ll*j*j*j*j,dp[i][j]);
	}
	ll ans=1e18;
	for(int i=1;i<=26;i++) ans=min(dp[m][i],ans);
	cout << ans;
}

星期四:

补河南邀请赛  C                                                      cf传送门

题意有点绕

思路:注意到b2==b5,那么f2==f6==f10,只能满足一个,即需付出2的代价

f1<=(f10==f6==f2)<=f8<=f9<=f4<=f5,代价为4,这里其实可以猜下结论,是求最长上升子序列,若满足f8,f9,f4,f5需>=9,则需要付出2代价,满足f4,f5同理,那若 f5后还有 f7呢,肯定选择满足f4=4,f5=5,f7=7,满足最多的 f 实际就是求LIS

这里的实现首先需要去重,然后将值相同的区间降序排序,再 nlogn求最长上升子序列即可

代码如下:

const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
void solve(){
	cin >> n;
	unordered_map<int,int>mp;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		mp[a[i]]=i;
	}
	vector<int>ve;
	set<int,greater<int>>st;
	int idx=0;
	for(int i=1;i<=n;i++){
		st.insert(a[i]);
		idx=max(mp[a[i]],idx);
		if(idx!=i) continue;
		for(int j:st) ve.push_back(j);
		st.clear();
	}
	vector<int>vv;
	vv.push_back(ve[0]);
	for(int i=1,sz=ve.size();i<sz;i++){             //求最长上升子序列
		if(ve[i]>vv.back()) vv.push_back(ve[i]);
		else *lower_bound(vv.begin(),vv.end(),ve[i])=ve[i];
	}
	cout << ve.size()-vv.size();
}

补24河南邀请赛  D

思路:可以想到两点之间应该是斜着角度越接近45°越好,推下式子后可证明此结论,且能发现,最佳情况是 |x1-x2| == |y1-y2|,则去掉绝对值符号后,可以得到 x1-y1==x2-y2和x1+y1==x2+y2,则可按x和y的差值 与 和来排序两遍,答案就在排序后相邻点间,取最大值即可

代码如下:

const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
struct nod{
	int x,y;
	int xy1,xy2;
}p[N];
bool cmp1(nod a,nod b){
	return a.xy1<b.xy1;
}
bool cmp2(nod a,nod b){
	return a.xy2<b.xy2;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> p[i].x >> p[i].y;
		p[i].xy1=p[i].x-p[i].y;
		p[i].xy2=p[i].x+p[i].y;
	}
	sort(p+1,p+n+1,cmp1);
	double ans=0;
	for(int i=1;i<n;i++){
		double tx=abs(p[i].x-p[i+1].x),ty=abs(p[i].y-p[i+1].y);
		ans=max((tx+ty)/sqrt(tx*tx+ty*ty),ans);
	}
	sort(p+1,p+n+1,cmp2);
	for(int i=1;i<n;i++){
		double tx=abs(p[i].x-p[i+1].x),ty=abs(p[i].y-p[i+1].y);
		ans=max((tx+ty)/sqrt(tx*tx+ty*ty),ans);
	}
	printf("%.11lf\n",ans);
}

刚写分解质因数分解时犯了一个令人发指的错误

int get(int x){
	unordered_set<int>st;
	for(int i=2;i<=idx && p[i]*p[i]<=x;i++){    
		while(x%p[i]==0){
			st.insert(p[i]);
			x/=p[i];
		}
	}
	if(x>1) st.insert(x);
	return st.size();
}

质数表的下标写为从2开始了,晕晕

入门 换根dp                                                           洛谷传送门

思路:dp【i】表示以 i为根时的答案,dep【i】表示深度,sz【i】表示 i的子树节点数量

先以 1为根dfs一遍求出dp【1】,然后第二遍dfs进行答案的转移,假设 d到点 x,其父节点为 f且dp【f】已知,则dp【x】=dp【f】- sz【x】+ n - sz【x】,因为根从 f转移到 x,x为根的子树距离都会减 1,除此之外的节点到根距离都会+1

代码如下:

const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<int>ve[N];
int sz[N],dep[N];
ll dp[N];
ll ma,idx;
void dfs1(int x,int f){
	sz[x]=1,dep[x]=dep[f]+1;
	if(ve[x].size()==1 && ve[x][0]==f) return ;
	for(int i:ve[x]){
		if(i==f) continue;
		dfs1(i,x);
		sz[x]+=sz[i];
	}
}
void dfs2(int x,int f){
	if(x!=1) dp[x]=dp[f]-sz[x]+n-sz[x];    //换根的转移
	if(dp[x]>ma) idx=x,ma=dp[x];
	for(int i:ve[x]){
		if(i==f) continue;
		dfs2(i,x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs1(1,0);
	for(int i=2;i<=n;i++) dp[1]+=dep[i]-1;     //算出dp[1]
	idx=1,ma=dp[1];
	dfs2(1,0);
	cout << idx;
}

星期五:

上一题的换皮                                                     洛谷传送门

难得敲了一次链式前向星,出错了,还是得换成vector存邻接表

思路:有一点需要注意,这题每个点和每条边的信息由题目给出,所以d到一个新点时,先需要把其信息从父节点转移过来,才能继续往子树dfs

代码如下:

const int N=2e6+10,M=210;
ll n;
int dep[N],c[N];
ll dp[N],sz[N],sum;
vector<PII>ve[N];
void dfs1(int x,int f){
	sz[x]=c[x];
	for(auto [w,v]:ve[x]){
		if(v==f){dep[x]=dep[f]+w; break;}
    }
	for(auto [w,v]:ve[x]){
		if(v==f) continue;
		dfs1(v,x);
		sz[x]+=sz[v];
	}
}
void dfs2(int x,int f){
	for(auto [w,v]:ve[x]){
		if(v==f){dp[x]=dp[f]-sz[x]*w+(sum-sz[x])*w; break;}
	}
	for(auto [w,v]:ve[x]){
		if(v==f) continue;
		dfs2(v,x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> c[i];
		sum+=c[i];
	}
	for(int i=1;i<n;i++){
		int u,v,w; cin >> u >> v >> w;
		ve[u].push_back({w,v});
		ve[v].push_back({w,u});
	}
	dfs1(1,0);
	for(int i=2;i<=n;i++) dp[1]+=1ll*dep[i]*c[i];
	dfs2(1,0);
	ll mi=1e18;
	for(int i=1;i<=n;i++) mi=min(dp[i],mi);
	cout << mi << "\n";
}

修正,链式前向星出错原因是edge数组开小了,n个点,双向建边即2*n条边,起码开2e5(2e6也行

链式前向星写法如下:

const int N=1e5+10,M=210;
ll n;
ll sz[N],dep[N],c[N];
ll dp[N],sum;
struct nod{
	ll nex,to,w;
}e[N<<1];                          //双向建边,记得开点的2倍大小
int hd[N],cnt;
void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].nex=hd[u];
	hd[u]=cnt;
}
void dfs1(int x,int f){
	sz[x]=c[x];
	for(int i=hd[x];i;i=e[i].nex){
		int v=e[i].to;
		if(v==f){dep[x]=dep[f]+e[i].w; break;}
	}
	for(int i=hd[x];i;i=e[i].nex){
		int v=e[i].to;
		if(v==f) continue;
		dfs1(v,x);
		sz[x]+=sz[v];
	}
}
void dfs2(int x,int f){
	for(int i=hd[x];i;i=e[i].nex){
		int v=e[i].to;
		if(v==f){dp[x]=dp[f]-1ll*sz[x]*e[i].w+(sum-sz[x])*e[i].w; break;}
	}
	for(int i=hd[x];i;i=e[i].nex){
		int v=e[i].to;
		if(v==f) continue;
		dfs2(v,x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> c[i];
		sum+=c[i];
	}
	for(int i=1;i<n;i++){
		int u,v,w; cin >> u >> v >> w;
		add(u,v,w),add(v,u,w);
	}
	dfs1(1,0);
	for(int i=2;i<=n;i++) dp[1]+=1ll*dep[i]*c[i];
	dfs2(1,0);
	ll mi=1e18;
	for(int i=1;i<=n;i++) mi=min(dp[i],mi);
	cout << mi << "\n";
}

继续一道换根dp  cf round927 F                               cf传送门

思路:dfs1先预处理出每个节点只考虑以自己为根的子树最大值,dfs2即可进行转移

设节点为 i,父节点为 f且dp【f】已知

若ma【i】< 0,dp【f】肯定没包含 i为根子树,若dp【f】为正,dp【i】+= dp【f】,否则不加

若ma【i】> 0,dp【f】包含 i为根子树,dp【f】- ma【i】若为正,dp【i】加上它,否则不加,实际等价于dp【i】为 ma【i】还是 dp【f】

代码如下:

const int N=2e6+10,M=210;
ll n;
int a[N],ma[N];
ll dp[N];
vector<int>ve[N];
void dfs1(int x,int f){
	ma[x]=a[x];
	for(int i:ve[x]){
		if(i==f) continue;
		dfs1(i,x);
		if(ma[i]>0) ma[x]+=ma[i];
	}
}
void dfs2(int x,int f){
	dp[x]=ma[x];
	if(dp[x]>0) dp[x]=max(dp[x]+dp[f]-dp[x],dp[x]);
	else dp[x]=max(dp[f]+dp[x],dp[x]);
	for(int i:ve[x]){
		if(i==f) continue;
		dfs2(i,x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		if(!a[i]) a[i]--;           //把为0的点处理为-1
	}
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs1(1,0);
	dp[1]=ma[1];
	dfs2(1,0);
	for(int i=1;i<=n;i++) cout << dp[i] << " \n"[i==n];
}

24河南邀请赛 K  换根dp写法

赛时和os用并查集缩点写的,用换根dp再写一遍

思路:dfs1预处理出以1为根有多少不符合条件的边

dfs2转移时,从 f到 x,只需考虑 f和x 一条边,其余边的情况不会改变,dp【f】已知

若 f->x 不符合条件,转移根后 x->f 即符合条件,dp【x】= dp【f】- 1

若 f->x符合条件,但转移根后x->f 不符合条件,dp【x】=dp【f】+ 1,ans += ( dp【x】== 0)

代码如下:

const int N=2e6+10,M=210;
ll n;
int a[N];
ll dp[N],ans;
vector<int>ve[N];
bool cmp(int x,int f){
	int fa=a[f]/2; if(a[f]&1) fa++;
	return a[x]>=fa;
}
void dfs1(int x,int f){
	if(!cmp(x,f)) dp[1]++;
	for(int i:ve[x]){
		if(i==f) continue;
		dfs1(i,x);
	}
}
void dfs2(int x,int f){
	if(f) dp[x]=dp[f];
	if(!cmp(x,f)) dp[x]--;
	if(f && !cmp(f,x)) dp[x]++;
	ans+=(dp[x]==0);
	for(int i:ve[x]){
		if(i==f) continue;
		dfs2(i,x);
	}
}
void solve(){
	dp[1]=ans=0;
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		ve[i].clear();
	}
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,0);
	cout << ans << "\n";
}

星期六:

重庆科技大学,C待补

周日:

武汉纺织大学,A待补

稍微难点的换根dp                                                      洛谷传送门

思路:dfs1预处理出 f【i】【j】表示 i节点只考虑往下距离 j以内的节点权值和

dfs2进行转移,x在向子节点dfs之前,可以先把子节点的dp【i】【j】处理出来,再dfs,免得到了子节点还得再找父节点?范围为2时,dp【i】【2】+= dp【x】【1】,但此时dp【x】【1】包含了 i节点权值,所以需要减去f【i】【0】,可推出转移方程

代码如下:

const int N=2e6+10,M=210;
ll n;
int k;
int c[N];
ll f[N][22],dp[N][22];
vector<int>ve[N];
void dfs1(int x,int fa){
	for(int i=0;i<=k;i++) f[x][i]=c[x];
	for(int i:ve[x]){
		if(i==fa) continue;
		dfs1(i,x);
		for(int j=1;j<=k;j++) f[x][j]+=f[i][j-1];
	}
}
void dfs2(int x,int fa){
	for(int i:ve[x]){
		if(i==fa) continue;
		dp[i][1]+=f[x][0];
		for(int j=2;j<=k;j++) dp[i][j]+=dp[x][j-1]-f[i][j-2];
		dfs2(i,x);
	}
}
void solve(){
	cin >> n >> k;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	for(int i=1;i<=n;i++) cin >> c[i];
	dfs1(1,0);
	for(int i=1;i<=n;i++)
		for(int j=0;j<=k;j++) dp[i][j]=f[i][j];
	dfs2(1,0);
	for(int i=1;i<=n;i++) cout << dp[i][k] << "\n";
}

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值