CF练习题19

发布时间 2023-11-07 22:31:16作者: LiQXing

Paths on the Tree

贪心题,因为对于每一个儿子,经过的路径数之差少于 \(1\),所以这道题可以理解为先把所有路径均分,然后把剩下的按照权值大小依次分布给那些儿子。

那么儿子传给父亲的权值又是如何处理呢?

首先,我们需要把父亲首先传递过来的 \(k\) 条路径均分,然后把剩下的最大路径给传递上去。

int n,m;
vector<int>g[N];
int ans=0,a[N];
inline int dfs(int u,int k){
    ans+=k*a[u];  
    if(g[u].size()==0)return a[u];
    int t=k/g[u].size(),res=k-t*g[u].size();
    priority_queue<int>q;
    for(auto v:g[u])q.push(dfs(v,t));
    if(res){
        while(res--){
            ans+=q.top();
            q.pop();
        }
    }
    return q.top()+a[u];
}
inline void solve(){
    n=read();m=read();
    int x;
    up(i,2,n){
        x=read();
        g[x].push_back(i);
    }
    up(i,1,n)a[i]=read();
    dfs(1,m);
    cout<<ans<<endl;
}
inline void clear(){
    up(i,1,n)g[i].clear();
    memset(a,0,sizeof a);
    ans=0;
}
signed main(){
    int T=read();
    while(T--){
        clear();
        solve();
    }    
    return 0;
}

Three Paths on a Tree

既然要求最长的并集,那么肯定有直径,然后再 bfs 出每个点离直径的距离。

注意,有可能第三个点并不存在,那么就选择直径上任意一个非端点的点。

int n,m,d;
int p[N];
vector<int>g[N];
int idx[N],cnt,a[N];
int dis1[N],dis2[N];
bool vis[N];
inline void dfs1(int u,int from){
	dis1[u]=dis1[from]+1;
	for(auto v:g[u]){
		if(v==from)continue;
        dfs1(v,u);
	}
}
inline void dfs2(int u,int from){
    for(auto v:g[u]){
		if(v==from)continue;
        dfs2(v,u);
        if(vis[v])vis[u]=1;
    }
}
int tmp,ans;
int L,R;
queue<int>q;
signed main() {
	n=read();
    int u,v;
    up(i,1,n-1){
        u=read();v=read();
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1,0);
	up(i,1,n)if(dis1[i]>dis1[L])L=i;
	dfs1(L,0);
	up(i,1,n)if(dis1[i]>dis1[R])R=i;
	vis[L]=1;
    dfs2(R,0);
    memset(dis2,0x3f,sizeof dis2);
    up(i,1,n)if(vis[i])q.push(i),dis2[i]=0;
    while(q.size()){
        int u=q.front();q.pop();
        for(auto v:g[u]){
            if(vis[v])continue;
            dis2[v]=dis2[u]+1;
            vis[v]=1;
            q.push(v);
        }
    }
    int T=0;
    up(i,1,n){
        if(ans<dis2[i]){
            ans=dis2[i];
            T=i;
        }
    }
    if(T==0){
        T++;
        while(T==L||T==R)T++;
    } 
    cout<<ans+dis1[R]-1<<endl;
    cout<<L<<" "<<R<<" "<<T;
    return 0;
}

Work Group

这个不是树上经典问题吗。

int n;
int val[N];
vector<int>g[N];
int f[N][2];
inline void dfs(int u){
	f[u][1]=-inf;
	for(auto v:g[u]){
		dfs(v);
		int x=f[u][0],y=f[u][1];
		f[u][0]=max(f[v][0]+x,f[v][1]+y);
		f[u][1]=max(f[v][1]+x,f[v][0]+y);
	}
	f[u][1]=max(f[u][1],f[u][0]+val[u]);
}
signed main(){
	n=read();
	int u,v;
	up(i,1,n){
		u=read();v=read();
		if(u!=-1)g[u].push_back(i);
		val[i]=v;
	}
	dfs(1);
	cout<<max(f[1][0],f[1][1]);
	return 0;	
}

Paint the Tree

很有意思的一道树形 dp。

本来我先想了一个贪心,优先选取最大的边,然后依次满足条件,结果发现假了。

想一想 dp,令 \(f_{i,1}\) 表示以 \(i\) 为根的子树,能够和父亲连接上,\(f_{i,0}\) 则不能连接上。

先把所有不选的情况加上,然后用一个堆来替换,有点类似贪心的思想,注意一下,一个是前 \(k\) 个,一个是前 \(k-1\) 个。

int n,m,k;
vector<pii>g[N];
int f[N][2];
inline bool cmp(int x,int y){
    return x>y;
}
inline void dfs(int u,int from){
    vector<int>d;
    for(auto i:g[u]){
        int v=i.fi,w=i.se;
        if(v==from)continue;
        dfs(v,u);
        f[u][0]+=f[v][0];
        f[u][1]+=f[v][0];
        d.push_back(f[v][1]+w-f[v][0]);
    }
    sort(d.begin(),d.end(),cmp);
    for(int i=0;i<d.size()&&i<k;i++){
        if(d[i]<=0)break;
        if(i<k-1)f[u][1]+=d[i];
        f[u][0]+=d[i];
    }
}
inline void solve(){
    n=read();k=read();
    int u,v,w;
    up(i,1,n-1){
        u=read();v=read();w=read();
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }
    dfs(1,0);
    write(f[1][0],1);
}
inline void clear(){
    up(i,1,n)g[i].clear();
    up(i,1,n){
        f[i][0]=0;
        f[i][1]=0;
    }
}
signed main(){
    int T=read();
    while(T--){
        clear();
        solve();
    }    
    return 0;
}

Minimum spanning tree for each edge

先把最小生成树求出来,然后对于询问的边,如果它在生成树上,那么直接输出最小生成树,否则,与这条路径上的最大值替换。

用树链剖分轻松实现。

int n,m;
vector<pii>g[N];
struct treelca{
    int siz[N],son[N],fa[N],dep[N],top[N];
    int a[N];
	inline void dfs1(int u,int from){
        siz[u]=1;son[u]=0;
        dep[u]=dep[from]+1;
        for(auto [v,w]:g[u]){
            if(v==from) continue;
            a[v]=w;
			fa[v]=u;dfs1(v,u);
            if(siz[v]>siz[son[u]])son[u]=v;
            siz[u]+=siz[v];
        }
    }
	int idx[N],cnt,dfn[N];
    inline void dfs2(int u,int tp){
        top[u]=tp;idx[u]=++cnt;
		dfn[cnt]=u;
        if(son[u]!=0) dfs2(son[u],tp);
        for(auto [v,w]:g[u]){
            if(v==fa[u]||v==son[u]) continue;
            dfs2(v,v);
        }
    }
    inline int lca(int x,int y){
        while(top[x]!=top[y]){
            if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
            else y=fa[top[y]];
        }
        return dep[x]<dep[y]?x:y;
    }
	int tr[N<<2];
	inline void push_up(int k){
		tr[k]=max(tr[lc],tr[rc]);
	}
	inline void build(int k=1,int l=1,int r=n){
		if(l==r){
			tr[k]=a[dfn[l]];
			return;
		}
		int mid=(l+r)>>1;
		build(lc,l,mid);
		build(rc,mid+1,r);
		push_up(k);
	}
	inline int ask(int x,int y,int k=1,int l=1,int r=n) {
    	if (x <= l && r <= y) return tr[k];
    	int mid=(l+r)>>1,res=0;
    	if (x<=mid)res=max(res,ask(x,y,lc,l,mid));
    	if (y>mid)res=max(res,ask(x,y,rc,mid+1,r));
    	return res;
	}
	inline int qmax(int x, int y) {
    	int res=0; 
    	while(top[x]^top[y]){
        	if(dep[top[x]]<dep[top[y]]) swap(x, y);
        	res=max(res,ask(idx[top[x]],idx[x]));
        	x=fa[top[x]];
    	}
    	if(dep[x]>dep[y])swap(x, y);
    	return max(res,ask(idx[x]+1,idx[y]));
	}
}T;
struct edge{
	int u,v,w,id;
}e[N<<1];
inline bool cmp(edge x,edge y){
	return x.w<y.w;
}
inline bool cmp2(edge x,edge y){
	return x.id<y.id;
}
int fa[N];
inline int find(int x){
	if(x==fa[x])return x;
	return fa[x]=find(fa[x]);
}
bool vis[N];
signed main(){
	n=read();m=read();
	int u,v,w;
	up(i,1,m){
		e[i].u=read();
		e[i].v=read();
		e[i].w=read();
		e[i].id=i;
	}
	sort(e+1,e+1+m,cmp);
	int ans=0;
	up(i,1,n)fa[i]=i;
	up(i,1,m){
		int fu=find(e[i].u),fv=find(e[i].v);
		if(fu==fv)continue;
		vis[e[i].id]=1;
		fa[fu]=fv;
		g[e[i].u].push_back({e[i].v,e[i].w});
		g[e[i].v].push_back({e[i].u,e[i].w});
		ans+=e[i].w;
	}
	T.dfs1(1,0);
	T.dfs2(1,0);
	T.build();
	sort(e+1,e+1+m,cmp2);
	up(i,1,m) {
        if(vis[i])write(ans,1);
		else write(ans+e[i].w-T.qmax(e[i].u,e[i].v),1);
    }
	return 0;
}

Paint the Tree

诈骗题,你以为是一一棵树,其实是一条链。

那么就可以直接枚举前两种颜色然后暴力。

但是某个 sb 还是写了树形 dp。

int n;
int c[4][N];
vector<int>g[N];
int deg[N],s;
int f[N][4][4],co[4][N],pre[N][4][4];
int a[N],len,ans,ansx,ansy,go,res[N];
inline void dfs(int u,int from){
    a[++len]=u;
    for(auto v:g[u]){
        if(v==from)continue;
        dfs(v,u);
    }
}
signed main(){
    n=read();
    up(i,1,3){
        up(j,1,n){
            c[i][j]=read();
        }
    }
    int u,v;
    up(i,1,n-1){
        u=read();v=read();
        g[u].push_back(v);
        g[v].push_back(u);
        deg[u]++;deg[v]++;
    }
    int rt;
    up(i,1,n){
        if(deg[i]==1)rt=i;
        else if(deg[i]>=3){
            cout<<-1;
            exit(0);
        }
    }
    memset(f,0x3f,sizeof f);
    dfs(rt,0);
    up(i,1,3) {
        up(j,1,3){
            if(i==j)continue;
            f[2][i][j]=min(f[2][i][j],c[i][a[1]]+c[j][a[2]]);
        }
    }
    up(i,3,n){ 
        up(j,1,3) {
            up(k,1,3) {
                up(t,1,3) {
                    if(j==k||j==t||k==t)continue;
                    if(f[i][k][t]>f[i-1][j][k]+c[t][a[i]]){
                        f[i][k][t]=f[i-1][j][k]+c[t][a[i]];
                        pre[i][k][t]=j;
                    }
                }
            }
        }
    }
    ans=uinf;
    up(i,1,3){
        up(j,1,3){
            if(f[n][i][j] < ans) {
                ans=f[n][i][j];
                ansx=i;ansy = j;
            }
        }
    }
    res[a[n-1]]=ansx;res[a[n]]=ansy;
    for(register int i = n; i >= 3; i--) {
        int go=pre[i][ansx][ansy];
        ansy=ansx; ansx = go;
        res[a[i-2]]=go;
    }
    cout<<ans<<endl;
    up(i,1,n)cout<<res[i]<<" ";
    return 0;
}

Maximum Weight Subset