2025.2.15做题记录

前言

只有当教练让你补题的时候,你才会知道你之前多摆。
————麦塞尔夫

补题内容为:树形dp 和 区间dp

联合权值

观察题面并注意到,对于一个点只有孙子爷爷和兄弟会产生联合权值,自然想到对于一个点 \(u\) ,记录它的父亲 \(fa\) 遍历它的儿子 \(v\) 那么产生的联合权值为 \(w_{fa}\times w_v\)\(w_v\times sum\)\(sum\) 为儿子的和。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
const int N=2e5+50;
const int mod=1e4+7;
vector<int> e[N];
int n,w[N];
int mxans,ans;
void dfs(int u,int fa){
	int mx=0,sum=0;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(v==fa) continue;
		dfs(v,u);
		mxans=max(mxans,max(w[fa]*w[v],w[v]*mx));
		ans=(ans%mod+w[fa]*w[v]%mod)%mod;
		ans=(ans%mod+sum*w[v]%mod)%mod;
		mx=max(mx,w[v]);
		sum+=w[v];
	}
	return ;
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	dfs(1,0);
	cout<<mxans<<' '<<ans*2%mod;
	return 0;
}

最大子树和

读题观察到对于每一株花有保留与不保留两种情况,设 \(dp\) 数组为第 \(u\) 株花 保留/不保留(1/0) 的情况。

发现只有当第 \(u\) 株花有贡献时,才会保留(你不会保留一个负贡献的垃圾)。那么就做完了。最后发现 \(dp{_u}{_,}{_0}\) 没用,可以省略不写。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=2e4+50;
vector<int> e[N];
int w[N],n,ans=-2147483647;
int f[N][3];
void dfs(int u,int fa){
	f[u][1]=w[u];
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(v==fa) continue;
		dfs(v,u);
		f[u][0]=0;
		if(f[v][1]>0)
			f[u][1]+=f[v][1];
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	memset(f,0xcf,sizeof(f));
	dfs(1,0);
	for(int i=1;i<=n;i++){
		ans=max(ans,f[i][1]);
	}
	cout<<ans;
	return 0;
}

二叉苹果树

同上感觉比较裸。考虑定义 \(dp\) 数组为:对于第 \(u\) 个节点保留 \(j\) 根树枝的情况。依题意就可得状态转移方程:\(dp{_u}{_,}{_j}=\max(dp{_u}{_,}{_j},dp{_u}{_,}_{j-k-1}+dp{_v}{_,}{_k})\)

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=200;
struct Edge{
	int v,w;
};
vector<Edge> e[N];
int n,q,f[N][N],rt[N];
void dfs(int u,int fa){
	for(int i=0;i<=q;i++) f[u][i]=rt[u];
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i].v,w=e[u][i].w;
		if(v==fa) continue;
		rt[v]=w;
		dfs(v,u);
		for(int j=q;j>=0;j--){
			for(int k=0;k<j;k++){
				f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]);
			}
		}
	}
}
int main(){
	cin>>n>>q;
	for(int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dfs(1,0);
	cout<<f[1][q];
	return 0;
}

染色

神秘好题。考虑将 从 \(i\)\(j\) 的区间刷成目标颜色,如果 \(i\)\(j\) 相同颜色,那么可以直接从 \(i\)\(j-1\) 的状态继承(我多画一点怎么你了?)。如果 \(i\)\(j\) 不同颜色,那么考虑枚举一个断点使得左右总花费和最小。

有一种神秘想法就是把整个过程反过来,考虑一堆颜色叠在一起往上取,最终把所有颜色取完。可能对于上述过程更好理解(反正我是OvO)。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
string s;
int f[60][60];
int main(){
	cin>>s;
	s=' '+s;
	int l=s.length()-1;
	for(int i=1;i<=l;i++){
		f[i][i]=1;
	}
	for(int len=2;len<=l;len++){
		for(int i=1;i+len-1<=l;i++){
			int j=i+len-1;
			if(s[i]==s[j]) f[i][j]=f[i][j-1];
			else{
				int mid=2e9;
				for(int k=i;k<j;k++){
					mid=min(f[i][k]+f[k+1][j],mid);
				}
				f[i][j]+=mid;
			}
		}
	}
	cout<<f[1][l];
	return 0;
}

248 G

板题。枚举 \(i\)\(j\) 区间断点 \(k\) 使 \(i\)\(k\) 区间合成的最大值与 \(k+1\)\(j\) 合成的最大值相同。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=300;
int n,a[N];
int f[N][N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		f[i][i]=a[i];
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				if(f[i][k]==f[k+1][j] && f[i][k]>0){
					f[i][j]=max(f[i][j],f[i][k]+1);
				}
			}
		}
	}
	int ans=-1;
	for(int len=1;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			ans=max(ans,f[i][j]);
		}
	}
	cout<<ans;
	return 0;
}

合唱队

另一种板题,考虑左侧右侧插入的条件,并考虑从左侧或右侧转移。

#include<iostream>

using namespace std;
int f[2010][2010][2],a[2010],n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        f[i][i][0]=1;
    }
    for(int len=2;len<=n;len++){
        for(int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            if(a[i]<a[i+1]) f[i][j][0]+=f[i+1][j][0];
            if(a[i]<a[j]) f[i][j][0]+=f[i+1][j][1];
            if(a[j]>a[i]) f[i][j][1]+=f[i][j-1][0];
            if(a[j]>a[j-1]) f[i][j][1]+=f[i][j-1][1];
            f[i][j][0]%=19650827;
            f[i][j][1]%=19650827;
        }
    }
    cout<<(f[1][n][0]+f[1][n][1])%19650827;
    return 0;
}

调整队形

观察题目发现添加人和踢掉人效果相同。(我们添加一个人使左右对称,为什么不把影响左右对称的人踢出去?)那么就只需要考虑踢人与换衣服。

对于两人衣服颜色相同,直接继承 \(i+1,j-1\)

对于两人衣服颜色不同:

对于踢人,可以从 \(i+1,j\)\(i,j-1\) 处转移。

对于换衣服,可以从\(i+1,j-1\) 处转移。(将 \(i\) 的衣服颜色换成 \(j\) 即可)。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=3e3+50;
int n,a[N];
int f[N][N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
			else{
				f[i][j]=min(f[i+1][j-1],min(f[i+1][j],f[i][j-1]))+1;
				//          改变衣服颜色    踢掉i人   踢掉j人
			}
		}
	}
	cout<<f[1][n];
	return 0;
}

加分二叉树

本题可视为区间dp的良好练手题。

因为题目给出的为中序遍历,所以对于一个由我们钦定的节点,它的左右区间就是他的左右子树。那么可以设置 \(dp\) 数组的意义为:区间 \(l\)\(r\) 的最大得分。

那么接下来遍历区间 \(l\)\(r\) ,寻找一个断点 \(k\) ,使得 \(dp{_l}{_,}{_{k-1}} \times dp_{k+1}{_,}{_{r}}+a_k\) 最大。

预处理 \(l=r\) 的情况(将其视为叶子节点)为 \(dp{_l}{_,}{_r}=a_l\)\(l=r-1\) 的情况(空子树为1)为 \(dp{_l}{_,}{_r}=1\)

接下来就是对于前序遍历,这个就很简单了。在对上述区间做 dp 时,记录 \(l\)\(r\) 区间的根节点,最后递归输出即可。

AC code:

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
int n,a[40];
int f[40][40];
int root[40][40];
void print(int l,int r){
	if(l>r) return ;
	cout<<root[l][r]<<' ';
	if(l==r) return ;
	print(l,root[l][r]-1);
	print(root[l][r]+1,r);
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		f[i][i]=a[i];
		f[i][i-1]=1;
		root[i][i]=i;
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			f[i][j]=f[i+1][j]+a[i];
			root[i][j]=i;
			for(int k=i+1;k<j;k++){
				if(f[i][j]<f[i][k-1]*f[k+1][j]+a[k]){
					f[i][j]=f[i][k-1]*f[k+1][j]+a[k];
					root[i][j]=k;
				}	
			}
		}
	}
	cout<<f[1][n]<<endl;	
	print(1,n);
	return 0;
}
posted @ 2025-02-15 21:04  Tighnari  阅读(13)  评论(0)    收藏  举报