题目链接1
题目思路
显然是一个树形dp,设dp[i][j]为第i棵树下有j个子节点的最大值,再利用背包来更新。注意的细节有点多,还有要逆向枚举。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e2+5;
int m,n,head[maxn],cnt,dp[maxn][maxn],v[maxn],ans,sz[maxn];
struct node{
int to,next;
}e[maxn];
void add(int u,int v){
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int son,int fa){
for(int i=head[son];i;i=e[i].next){
if(e[i].to!=fa){
sz[e[i].to]=1;
dfs(e[i].to,son);
sz[son]+=sz[e[i].to];
for(int j=min(m,sz[son]);j>=1;j--){//注意要逆向枚举,要不然自己会更新自己
for(int k=1;k<=min(j,sz[e[i].to])&&(j-k>=1||son==0);k++){//判断条件有点多,要仔细思考
dp[son][j]=max(dp[son][j],dp[e[i].to][k]+dp[son][j-k]);
}
}
}
}
}
void init(){
ans=0,cnt=0;
memset(dp,0,sizeof(dp));
memset(head,0,sizeof(head));
memset(v,0,sizeof(v));
memset(e,0,sizeof(e));
memset(sz,0,sizeof(sz));
}
signed main(){
while(scanf("%d %d",&n,&m)!=-1&&n){
init();
for(int i=1,to;i<=n;i++){
scanf("%d %d",&to,&v[i]);
add(to,i);
dp[i][1]=v[i];//i选一个节点,即是自己
}
dfs(0,0);
printf("%d\n",dp[0][m]);
}
return 0;
}
题目链接2
题目大意
给你一棵树,定义了结点距离为任意结点对(i,j)在最短路径上的边的条数。让你求出每个结点的结点距离小于等于K的结点个数,然后求异或值。
题目思路
首先,这是树结构。
然后,我们尝试简化问题。
如果求的,不是对于一个节点,所有距离在[1,K]的节点数,而是限制在子树内的距离在[1,K]的节点数。
那么,这道题,我们直接一个dfs就可以搞定。
就是从叶子节点开始,距离这个节点距离为[1,K]的节点数。
然后f[x][i]=∑f[son][i-1],i∈[1,K]
f[x][0]=1
然而,我们还要求与非子树内的节点,怎么办呢?
我们做完之前的预处理之后,只需要从父节点寻求转移即可。
我们假设,我们已经知道了距离父节点x距离为0~K的所有点的点数。我们现在要向子节点y转移。
显然,f[y][i]+=f[x][i-1]-f[y][i-2],2<=i<=K。
++f[y][1];
意思是,距离父节点x为i-1的节点,转移到节点y的时候,距离就变成了i。
然而, 并非所有的节点都能做转移。y子树内的,距离为y为i-2的节点,距离父节点的距离也是i-1,但是距离y的距离并非为i。
所以我们把这些节点剔除。
一个转移从子节点转移而来,另外一个转移从父节点转移而来。
参考链接:https://blog.csdn.net/snowy_smile/article/details/50214429
代码
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=5e5+5;
int t,n,k,a,b,cnt,head[maxn],ans,f[maxn][20];
struct node{
int to,next;
}e[maxn<<1];//双向边
void add(int u,int v){
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void init(){
cnt=0,ans=0;
memset(head,0,sizeof(head));
memset(f,0,sizeof(f));
memset(e,0,sizeof(e));
}
void dfs1(int son,int fa){
f[son][0]=1;//初始化,就是自己
for(int i=head[son];i;i=e[i].next){
if(e[i].to!=fa){
dfs1(e[i].to,son);
for(int j=1;j<=k;j++){
f[son][j]+=f[e[i].to][j-1];
}
}
}
}
void dfs2(int son,int fa){
for(int i=head[son];i;i=e[i].next){
if(e[i].to!=fa){
for(int j=k;j>=2;j--){//逆向枚举!!!!!!!!!!!!!!
f[e[i].to][j]+=f[son][j-1]-f[e[i].to][j-2];
}
f[e[i].to][1]++;//e[i].to的父亲
dfs2(e[i].to,son);
}
}
int sum=0;
for(int i=0;i<=k;i++){//注意从0开始
sum+=f[son][i];
}
ans=ans^sum;
}
int main(){
scanf("%d",&t);
while(t--){
init();
scanf("%d %d %d %d",&n,&k,&a,&b);
for(int i=2;i<=n;i++){
int to=(1ll*a*i+b)%(i-1)+1;//注意ll
add(i,to),add(to,i);
}
dfs1(1,1);//两次dfs
dfs2(1,1);
printf("%d\n",ans);
}
return 0;
}
题目链接3
题目大意
给出n个节点的一棵树,每条边有权值。现在要求切段其中的一些边,使得任意一个叶子没有走到祖先的路。
要求切断的边的总权值不能超过m。求所有方案中切断的最大的边权的最小值。
题目思路
看到这个要求,显然是二分。
两种情况讨论一下
枚举i的子节点j。
第一种:直接切掉(i, j)这条边,花费是w(i, j)
第二种:子树j中的叶节点已经与j断开,花费是f[j]
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
const int inf=0x3f3f3f3f;
int n,m,cnt,head[maxn];
ll dp[maxn];//这里要用ll
struct node{
int to,next,w;
}e[maxn<<1];//双向边
void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].next=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void init(){
cnt=0;
memset(e,0,sizeof(e));
memset(head,0,sizeof(head));
}
void dfs(int son,int fa,int ma){
bool flag=0;
for(int i=head[son];i;i=e[i].next){
if(e[i].to!=fa){
flag=1;
dfs(e[i].to,son,ma);
if(e[i].w<=ma){
dp[son]+=min(dp[e[i].to],1ll*e[i].w);
}else{
dp[son]+=dp[e[i].to];
}
}
}
if(!flag){//表明这是叶子节点
dp[son]=inf;
}
}
int main(){
while(scanf("%d%d",&n,&m)!=-1&&n+m){
init();
for(int i=1,u,v,w;i<=n-1;i++){
scanf("%d %d %d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
int l=1,r=1000,ans=-1;
while(l<=r){
int mid=(l+r)/2;
memset(dp,0,sizeof(dp));
dfs(1,1,mid);
if(dp[1]<=m){
ans=mid;
r=mid-1;
}else{
l=mid+1;
}
}
printf("%d\n",ans);
}
return 0;
}