感觉codeforces也是来到了能做D题的水平。
在解决完子区间的查询问题之后,感觉C题能做的就多了。
codeforces的D题多是一些C题的加强版,这里往往涉及到某些规律,例如二进制位。
同样D题往往也容易出现一些简单的树和图的问题,尽管我目前的水平仍然做不出来,但是可以知道的是D题并不是太不可做的树图题。
在两天的专研中也是找到了一个适合自己的树图做题法,就是邻接表。
用邻接表进行存图,需要剪枝用tsize数组来看枝叶的大小,剩下的就是遍历+dfs+dp。
不需要存图的时候,直接遍历+dfs+dp。
对于树形dp的dp问题,单拎出来感觉并不是很难写的dp。主要的问题可能还是在于分析树形dp。
1.上司的舞会。不需要剪去枝叶。而是在邻近节点中进行选择,因此不需要进行tsize记录枝叶大小。f[n][2]也是我比较擅长的dp方式,用于使用或否。
对于dp的递推也并不是很难,主要在于邻近节点的比较问题。
2.二叉苹果树。需要剪去枝叶。因此我们可以采用tsize数组来表示枝叶大小。通过有依赖的背包问题分析,配合tsize进行剪枝,从而得到我们所想要留下的苹果数。
3.选课问题,类似苹果数,不需要双向建图,需要对0处进行修改。
4.加分二叉树。不需要剪枝,选择保留与否,类似上司的舞会。只需要开线性dp,因为我们考虑的只是大小问题,不存在中间节点没有,其他节点存在的情况。只需要对正数子树进行相加即可。
无需剪枝叶code:
void dfs(int u){ f[u][0] = 0; f[u][1] = happy[u]; for(auto i:ve[u]){ dfs(i); f[u][1] += f[i][0]; f[u][0] += max(f[i][0],f[i][1]); } } int main() { //cin.ignore(numeric_limits<streamsize>::max(),'\n') int n; cin>>n; for(int i=1;i<=n;i++){ cin>>happy[i]; } int a,b; for(int i=1;i<n;i++){ cin>>a>>b; ve[b].push_back(a); sign[a] = true; } int root = -1; for(int i=1;i<=n;i++) if(!sign[i]) {root=i;break;} dfs(root); cout<<max(f[root][0],f[root][1])<<endl;; }
需要剪掉枝叶,存在依赖关系,有依赖的背包问题code:
void dfs(int u,int fa){ tsize[u] = 1;//记录枝叶的大小。 for(auto i:ve[u]){ int v = i.v,w = i.w; if(v==fa) continue; dfs(v,u); tsize[u]+=tsize[v]; for(int j=min(tsize[u]-1,q);j;j--){//倒序递推可以参考有依赖的背包问题。用到之前子节点对应的值 j-k-1; for(int k=min(tsize[v]-1,j-1);k>=0;k--){//遍历子节点的子树所以正序倒序都可以 f[u][j] = max(f[u][j],f[u][j-k-1]+f[v][k]+w); //状态转移方程可以参考有依赖的背包问题。 } } } } int main() { //cin.ignore(numeric_limits<streamsize>::max(),'\n') cin>>n>>q; int a,b,c; for(int i=1;i<n;i++){ cin>>a>>b>>c; ve[a].push_back({b,c}); ve[b].push_back({a,c});//双向存边 } dfs(1,1); cout<<f[1][q]<<endl;//f[i][j] 表示以i为节点保留q个枝叶时的最大值 }