[八省联考2018]林克卡特树
题目大意:给定一棵有负权边的树,现在必须恰好删去kkk条边,并加上恰好kkk条权值为000的边,要求最大化它的直径长度。
首先考虑删去KKK条边的效果:把整棵树变成k+1k+1k+1个连通块
然后用0权变把这几个连通块连起来,当k+1k+1k+1个连通块已经确定后有个显然的结论:用kkk条边把他们连接后的最大直径长度就是k+1k+1k+1个连通块的直径代数求和。
方法是将k+1k+1k+1条直径首位依次相连即可,显然不可能有比它更大的直径。这个方案引导我们转化问题:寻找k+1k+1k+1条互不相交的链使得链的长度之和最大化
于是可设计树形dp
状态表示:fu,j,0/1/2f_{u,j,0/1/2}fu,j,0/1/2考虑以uuu为根的子树,当前选择了jjj条互不相交且完整的链,并且uuu节点的度数为0/1/20/1/20/1/2的最优解。
状态转移:
首先说明答案就是max(f1,k+1,0,f1,k,1,f1,k+1,2)\max(f_{1,k+1,0},f_{1,k,1},f_{1,k+1,2})max(f1,k+1,0,f1,k,1,f1,k+1,2),注意f1,k,1f_{1,k,1}f1,k,1这个值,我们jjj表示完整的链,而还有一条以节点111为端点的一条链我们不算它为完整的链,因为他可以和父亲节点合并合成新的链,也就是f1,k,1f_{1,k,1}f1,k,1实际上有k+1k+1k+1条链:kkk条完整,1条以节点111为端点的链。
首先我们让每课子树结束后fv,j,0=max(fv,j,0,fv,j−1,1,fv,j,2)f_{v,j,0}=\max(f_{v,j,0},f_{v,j-1,1},f_{v,j,2})fv,j,0=max(fv,j,0,fv,j−1,1,fv,j,2)表示在子树uuu中选择jjj条链的最优解。于是有以下更新:
考虑uuu节点以及它的一个儿子节点vvv
- fu,j+k,0=fu,j,0+fv,k,0f_{u,j+k,0}=f_{u,j,0}+f_{v,k,0}fu,j+k,0=fu,j,0+fv,k,0
- fu,j+k,1=max(fu,j,1+fv,k,0,fu,k,0+fv,k,1+wu→v)f_{u,j+k,1}=\max(f_{u,j,1}+f_{v,k,0},f_{u,k,0}+f_{v,k,1}+w_{u\to v})fu,j+k,1=max(fu,j,1+fv,k,0,fu,k,0+fv,k,1+wu→v)
- fu,j+k,2=fu,j,2+fv,k,0;fu,j+k+1,2=fu,j,1+fv,k,1+wu→vf_{u,j+k,2}=f_{u,j,2}+f_{v,k,0};f_{u,j+k+1,2}=f_{u,j,1}+f_{v,k,1}+w_{u\to v}fu,j+k,2=fu,j,2+fv,k,0;fu,j+k+1,2=fu,j,1+fv,k,1+wu→v
sz优化以及边界优化后时间复杂度O(nk)O(nk)O(nk)
#include<map>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
using pii=pair<int,int>;
using pli=pair<long long,int>;
constexpr int N=300010,M=600010;
constexpr ll INF=0x3f3f3f3f3f3f3f3f;
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;}
int n,m,sz[N];
ll f[N][105][3],g[105][3];
void dfs(int u,int fa)
{f[u][0][0]=0;f[u][0][1]=0;f[u][1][2]=0; //一个点看成自环sz[u]=1;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa) continue;dfs(v,u);for(int j=0;j<=sz[u]+sz[v]&&j<=m;j++) g[j][0]=g[j][1]=g[j][2]=-INF;for(int j=0;j<=sz[u]&&j<=m;j++)for(int k=0;k<=sz[v]&&k+j<=m;k++){g[j+k][0]=max(g[j+k][0],f[u][j][0]+f[v][k][0]);g[j+k][1]=max(g[j+k][1],max(f[u][j][1]+f[v][k][0],f[u][j][0]+f[v][k][1]+w[i]));g[j+k][2]=max(g[j+k][2],f[u][j][2]+f[v][k][0]);if(j+k+1<=m) g[j+k+1][2]=max(g[j+k+1][2],f[u][j][1]+f[v][k][1]+w[i]);}for(int j=0;j<=sz[u]+sz[v]&&j<=m;j++)f[u][j][0]=g[j][0],f[u][j][1]=g[j][1],f[u][j][2]=g[j][2];sz[u]+=sz[v];}for(int j=0;j<=sz[u]&&j<=m;j++){f[u][j][0]=max(f[u][j][0],f[u][j][2]);if(j) f[u][j][0]=max(f[u][j][0],f[u][j-1][1]);}
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>m; ++m;memset(h,-1,sizeof h);memset(f,-0x3f,sizeof f);for(int i=1;i<n;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c),add(b,a,c);}dfs(1,0);cout<<f[1][m][0]<<'\n';return 0;
}
wqs二分优化待补
upd:2021/2/28
打表观察可知:fu,j,0/1/2(j)f_{u,j,0/1/2}(j)fu,j,0/1/2(j)这个函数也就是在坐标轴上的点(j,f(j))(j,f(j))(j,f(j))形成的图像是凸的,于是可以采用wqs二分的优化技巧去掉dp数组第二维选择个数的限制。
由于wqs二分的一些细节,需要用一个结构体记录,分别表示值和选择的个数,值相同我们尽量选择个数少的方案。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
using pii=pair<int,int>;
using pli=pair<long long,int>;
constexpr int N=300010,M=600010;
constexpr ll INF=0x3f3f3f3f3f3f3f3f;
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;}
int n,m;
struct node
{ll v;int cnt;bool operator<(const node& o)const{return v<o.v||v==o.v&&cnt>o.cnt;}bool operator>(const node& o)const{return v>o.v||v==o.v&&cnt<o.cnt;}node operator+(const node& o)const{return (node){v+o.v,cnt+o.cnt};}
}f[N][3];
void dfs(int u,int fa,ll delta)
{f[u][0]=(node){0,0};f[u][1]=(node){0,0};f[u][2]=(node){-delta,1};for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa) continue;dfs(v,u,delta); //这里没用备份数组 注意顺序一定不能改f[u][2]=max(f[u][2],max(f[u][1]+f[v][1]+(node){w[i]-delta,1},f[u][2]+f[v][0]));f[u][1]=max(f[u][1],max(f[u][1]+f[v][0],f[u][0]+f[v][1]+(node){w[i],0}));f[u][0]=max(f[u][0],f[u][0]+f[v][0]);}f[u][0]=max(max(f[u][0],f[u][2]),(node){f[u][1].v-delta,f[u][1].cnt+1});
}
bool check(ll x)
{dfs(1,0,x);return f[1][0].cnt<=m;
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>m; ++m;memset(h,-1,sizeof h);memset(f,-0x3f,sizeof f);for(int i=1;i<n;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c),add(b,a,c);}ll l=-1e12,r=1e12;while(l<r){ll mid=l+r>>1;if(check(mid)) r=mid;else l=mid+1;}check(l);cout<<1ll*m*l+f[1][0].v<<'\n';return 0;
}
第一道洛谷黑题,明天开学起飞
要加油哦~