APIO2010:巡逻 树中最长K条链

这篇博客讨论了APIO2010比赛中关于寻找一棵树中不相交的最长K条链的问题。当K=1时,可通过求最长链解决;K=2时,存在贪心策略,但K>2时需要使用树形DP。博主分享了树形DP的思路和代码实现,但指出代码仅为K≤2的贪心解法,对于K条最长链的正确解法并不适用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

。。。

无语BZOJ挂了,CF也挂了。Bless All..

题目链接: (木有) APIO2010

题目描述:求一颗树里的边不相交的K条链,使得K条链经过的边数之和最大。( 本题K<=2)

首先:

http://blog.sina.com.cn/s/blog_61034ad90100ii8r.html

http://www.cnblogs.com/procedure2012/archive/2012/03/11/2390792.html


K=1,.....直接求最长链。BFS两次找直径或者树形DP。

K=2,有贪心的方法:先求最长链,再把经过的边权设为-1,再求最长链,这下要两次树形DP。(k=2正确,k>2不知道正确性)

K>2 ,树形DP可做


令F[i][j][k]表示当前在i节点的子树中找到了j条完整的链,且当前的状态是k。

k=0,表示没有能和i的父亲一起合成一条长链的一半的链。

k=1,表示有且只有一条到j的半条链,可以向上继续延伸。(最多一条,大于一条的话在就要在i到fa[i]的路上相交了)


一个儿子的所有更新不能重叠,不能直接动态用f数组来更新f数组所以要新建一个tmp数组保存上一次状态。

下面的更新都相当于做了一个背包。x表示i的儿子。


上面第一个表示从儿子中选一些现成的链来组合。 第二个表示x里的半条链和i的半条链和链接x,i的边组合成一条新链。x中已经有t条完整链了,所以是[i-t+1][1]


第一个表示从儿子x中选t个完整的链和之前的j-t个完整的链并且一条半条链合成。第二个表示i中本来的t个完整的链和x中i-t个完整的链和一条半条链与i,x的路径合成而成i的半条链。

还有K条最长链答案是:


(这道题的答案不是这个啊。)


DP代码(基本抄袭):

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
int getx(){
	char c;int x;bool pd=false;
	for (c=getchar();c!='-'&&(c<'0'||c>'9');c=getchar());
	if (c=='-') c=getchar(),pd=true;
	for (x=0;c>='0'&&c<='9';c=getchar())
		x=(x<<3)+(x<<1)+c-'0';
	return pd?-x:x;
}
bool upmax(int &a,const int &b){return a<b?a=b,1:0;}
const int MAX_N=100005,MAX_K=2+1;
int N,K;
int first[MAX_N],next[MAX_N<<1],to[MAX_N<<1];
int tal=1;
void tjb(int x,int y){
	next[++tal]=first[x];
	first[x]=tal;
	to[tal]=y;
}
int f[MAX_N][MAX_K][2];
int tmp[MAX_K][2];
void dfs(int v,int fa){
	for (int k=first[v];k;k=next[k]){
		int u=to[k];
		if (u==fa) continue;
		dfs(u,v);
		memcpy(tmp,f[v],sizeof f[v]);
		per(j,K,0) per(t,j,0){
			upmax(f[v][j][0],f[u][t][0]+tmp[j-t][0]);
			if (j-t-1>=0) upmax(f[v][j][0],f[u][t][1]+tmp[j-t-1][1]+1);
			upmax(f[v][j][1],f[u][t][0]+tmp[j-t][1]);
			upmax(f[v][j][1],f[u][t][1]+tmp[j-t][0]+1);
			}
		}
}
int main(){
	freopen("patrol.in","r",stdin);
	freopen("patrol.out","w",stdout);
	N=getx(),K=getx();
	rep(i,1,N-1){
		int x=getx(),y=getx();
		tjb(x,y);tjb(y,x);
		}
	dfs(1,0);
	printf("%d\n",2*(N-1)+K-std::max(f[1][K][0],f[1][K-1][1]));
}



这份代码是傻逼的贪心K≤2,写的相当的丑。丑到基本不能看。

#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
int getx(){
	char c;int x;bool pd=false;
	for (c=getchar();c!='-'&&(c<'0'||c>'9');c=getchar());
	if (c=='-') c=getchar(),pd=true;
	for (x=0;c>='0'&&c<='9';c=getchar())
		x=(x<<3)+(x<<1)+c-'0';
	return pd?-x:x;
}
bool upmax(int &a,const int &b){return a<b?a=b,1:0;}
const int MAX_N=100005;
int n,k;
int first[MAX_N],next[MAX_N<<1],to[MAX_N<<1];
int tal=1;
void tjb(int x,int y){
	next[++tal]=first[x];
	first[x]=tal;
	to[tal]=y;
}
const int INF=~0U>>2;
int mxlen=-INF;
struct Node{
	int l,v;
	Node(int _l=0,int _v=0):l(_l),v(_v){}
};
int a,b;
int fa[MAX_N],dep[MAX_N];
int c[MAX_N];
Node dfs(int v,int fax,int dp){
	fa[v]=fax;dep[v]=dp;
	Node mx1(0,v),mx2(-INF,v),tp;
	bool pd=true;
	for (int k=first[v];k;k=next[k]){
		int u=to[k];
		if (u==fax) continue;
		pd=false;
		tp=dfs(u,v,dp+1);
		if (tp.l>mx1.l) mx2=mx1,mx1=tp;
		else if (tp.l>mx2.l) mx2=tp;
		}
	if (upmax(mxlen,mx1.l+mx2.l))
		a=mx1.v,b=mx2.v;
	if (pd) return Node(c[v],v);
	return Node(mx1.l+c[v],mx1.v);
}
void sol(){
	if (dep[a]<dep[b]) std::swap(a,b);
	while (dep[a]!=dep[b])
		c[a]=-1,a=fa[a];
	while (a!=b){
		c[a]=c[b]=-1;
		a=fa[a],b=fa[b];
		}
}
void work(){
	rep(i,2,n) c[i]=1;c[1]=0;
	dfs(1,0,1);
	int mx=mxlen;
	if (k==1){printf("%d\n",(n-1+k)*2-(mx+k));exit(0);}
	sol();
	mxlen=-INF;
	dfs(1,0,1);
	if (mxlen<0) printf("%d\n",(n-1+k)*2-(mx+k));
	else printf("%d\n",(n-1+k)*2-(mxlen+mx+k));
}
int main(){
	freopen("patrol.in","r",stdin);
	freopen("patrol.out","w",stdout);
	n=getx(),k=getx();
	rep(i,1,n-1){
		int x=getx(),y=getx();
		tjb(x,y);tjb(y,x);
		}
	work();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值