1.题目:选课
题解:
f[i][j]表示以i为根选j个点的最大值,设一个虚根0,然后在树上跑01背包dp
代码:
#include <cstdio>
#include <iostream>
#define N 5000
using namespace std;
int tot,nxt[N],point[N],v[N],n,m,root,f[N][305],a[N];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
void treedp(int x)
{
for (int i=point[x];i;i=nxt[i])
{
treedp(v[i]);
for (int j=m;j>=1;j--)
for (int k=1;k<=j;k++)
f[x][j]=max(f[x][j],f[x][j-k]+f[v[i]][k]);
}
if (x) for (int j=m;j>=1;j--) f[x][j]=f[x][j-1]+a[x];
}
int main()
{
int i;
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
int ff;
scanf("%d%d",&ff,&a[i]);
addline(ff,i);
}
treedp(0);//f[i][j]表示以i为根的树选了j个点
printf("%d",f[0][m]);
}
2.题目:联合权值
题解:
这个题其实可以不用树形dp,用一个暴力就好。
ans1是:每个点作为一个中转点,如果可以产生联合权值,那一定和一个点相连的两个点,我们可以设置maxx1,maxx2表示与一个点相连的最大值和第二大值,相乘一下把所有点遍历一遍取一个最大值就好了
ans2是:要求一个和嘛,可以采用结合律,与一个点相连的有很多点,你可以先求出与每个点相连的权值总和,然后在遍历每一条边的时候,乘上 总和减去一个点 作为这条边产生的权值
代码:
#include <cstdio>
#include <iostream>
#define Mod 10007
#define N 200005
#define LL long long
using namespace std;
int a[N],x[N],y[N],maxx1[N],maxx2[N];LL sum[N];
void doit(int a,int &b,int &c)
{
if (c<a){b=c; c=a; return;}
if (b<a) b=a;
}
int main()
{
int n,i,ans=0;
scanf("%d",&n);
for (i=1;i<n;i++) scanf("%d%d",&x[i],&y[i]);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (i=1;i<n;i++)
{
int xx=x[i],yy=y[i];
sum[xx]+=a[yy]; sum[yy]+=a[xx];
doit(a[xx],maxx1[yy],maxx2[yy]);
doit(a[yy],maxx1[xx],maxx2[xx]);
}
for (i=1;i<=n;i++)
ans=max(ans,maxx1[i]*maxx2[i]);
LL ans2=0;
for (i=1;i<n;i++)
{
ans2=((LL)a[x[i]]*(LL)(sum[y[i]]-a[x[i]])%Mod+ans2)%Mod;
ans2=((LL)a[y[i]]*(LL)(sum[x[i]]-a[y[i]])%Mod+ans2)%Mod;
}
printf("%d %lld",ans,ans2);
}
3.三色二叉树
题解:
f[i][0/1/2]以i为根节点,选红/绿/蓝得到的最大绿色节点数,从底部往上转移即可
代码:
#include <cstdio>
#include <iostream>
#define N 500005
//f[i][0/1/2]以i为根节点,选红/绿/蓝得到的最大绿色节点数
using namespace std;
int sz=1,l[N],r[N],f[N][3],g[N][3];
void read(int now)
{
char c=getchar();
if (c=='0') return;
++sz; l[now]=sz; read(l[now]);
if (c=='2')
{
sz++;
r[now]=sz;
read(r[now]);
}
}
void dpmax(int i)
{
if (!i) return;
dpmax(l[i]); dpmax(r[i]);
f[i][0]=max(f[l[i]][1]+f[r[i]][2],f[l[i]][2]+f[r[i]][1]);
f[i][1]=max(f[l[i]][0]+f[r[i]][2],f[l[i]][2]+f[r[i]][0])+1;
f[i][2]=max(f[l[i]][1]+f[r[i]][0],f[l[i]][0]+f[r[i]][1]);
}
void dpmin(int i)
{
if (!i) return;
dpmin(l[i]); dpmin(r[i]);
g[i][0]=min(g[l[i]][1]+g[r[i]][2],g[l[i]][2]+g[r[i]][1]);
g[i][1]=min(g[l[i]][0]+g[r[i]][2],g[l[i]][2]+g[r[i]][0])+1;
g[i][2]=min(g[l[i]][1]+g[r[i]][0],g[l[i]][0]+g[r[i]][1]);
}
int main()
{
read(1);
dpmax(1);
printf("%d ",max(f[1][1],max(f[1][0],f[1][2])));
dpmin(1);
printf("%d",min(g[1][1],min(g[1][0],g[1][2])));
}
4.
时态同步
题意:
给定一棵有根树,每次操作使一条边的权值+1,问最少操作多少次可以使每个叶子节点到根的距离相等
题解:
这个题先用贪心的思路,可以知道对于一个点来说,它的 叶子结点到它的距离 最后要变成的值都是 一开始 所有叶子结点到它 的最大值
同时对于一个点来说,如果他可以修改靠上的边,一定比修改靠下的边优
我们设f[v]表示以v为根的树到叶子结点的最大值为多少,f可以一遍dfs出来。计算其父亲u的f[u]与f[v]的差值,那么对于这个差值修改u与v的边是最优的,从底部往上递推就ok啦
看看数据范围啊亲你要用longlong
代码:
#include <cstdio>
#include <iostream>
#define N 1000010
#define LL long long
using namespace std;
int tot,nxt[N],point[N],v[N],c[N];
LL f[N],ans;
void addline(int x,int y,int cc)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=cc;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=cc;
}
void dfs(int now,int fa)//f[v]表示以v为根的树到叶子结点的最大值为多少
{
for (int i=point[now];i;i=nxt[i])
if (v[i]!=fa)
{
dfs(v[i],now);
f[now]=max(f[now],f[v[i]]+c[i]);
}
}
void treedp(int x,int fa)
{
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
{
treedp(v[i],x);
ans+=f[x]-f[v[i]]-c[i];
}
}
int main()
{
int n,s,i;
scanf("%d%d",&n,&s);
for (i=1;i<n;i++)
{
int x,y,cc;
scanf("%d%d%d",&x,&y,&cc);
addline(x,y,cc);
}
dfs(s,0);
treedp(s,0);
printf("%lld",ans);
}