您的位置:首页 > 其它

HDU 4582 DFS spanning tree 解题报告(贪心 & 树形DP)

2013-08-15 17:41 811 查看
    此题贪心可解。从下往上,到了必须选边的时候才选边即可。296MS,代码如下:

#include <bitset>
#include <iostream>
using namespace std;

const int maxn=2013;
int first[maxn],pre[maxn],vv[maxn*20],nxt[maxn*20];
bitset<2001> to[2001];
bool vis[maxn];
int ans;

void dfs(int u,int p)
{
vis[u]=true;
to[u].reset();

for(int e=pre[u];e;e=nxt[e]) if(vis[vv[e]])
to[u].set(vv[e]);
for(int e=first[u];e;e=nxt[e]) if(vv[e]!=p)
dfs(vv[e],u);
if(p==-1)
return;
if(to[u].test(p))
ans++;
else
to[p]|=to[u];
}

int main()
{
int n,m;
while(~scanf("%d%d",&n,&m) && n|m)
{
memset(vis,0,sizeof(vis));
memset(first,0,sizeof(first));
memset(pre,0,sizeof(pre));

int e=2;
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
nxt[e]=first[u],vv[e]=v,first[u]=e++;
nxt[e]=first[v],vv[e]=u,first[v]=e++;
}
for(int i=n;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
nxt[e]=pre[u],vv[e]=v,pre[u]=e++;
nxt[e]=pre[v],vv[e]=u,pre[v]=e++;
}

ans=0;
dfs(1,-1);
printf("%d\n",ans);
}
}

    当然,之前写的树形DP也是可以过的。

    首先声明,参考了frog1902的解题报告。http://blog.csdn.net/frog1902/article/details/9921845

    这里是另外一份解题报告的地址:http://www.cnblogs.com/wangfang20/archive/2013/08/13/3254920.html

    其实我都看了,仍然不是太懂。在画了很多图,debug许久后,终于有点理解了。希望和我一样不懂的弱菜能看懂= =。

    

    首先是题意:给定n个点,m条边的图。前n-1条边是DFS树。所说的T环是只有一条边不属于DFS树的环。问最小选取多少条边,可以使所有的T环都能被覆盖到。

    


    图中 1,2,3,1 是一个T环,1,2,4,1是一个T环。只要选择(1,2)这条边,那么两个T换都被覆盖到了。

    frog1902的解题报告中所说的横叉边与返祖边,简单引用并解释下:

    =====================================================

    “需注意的是,题目给出的是一个无向图的DFS树,

    所以不在这棵树上的边一定不可能是"横叉边",而一定是" 后向边(返祖边)"。

    也就是说,不在DFS树上的边,一定是由某点指向它的某个祖先的。

    很容易想象,如果不是这样,那么DFS树的形态一定会发生改变。”

    =====================================================

    横叉边是某一节点x到DFS树中另一子树上节点y的边,返祖边是某一节点x到其DFS树中祖先y的边。如下图:



    算法导论中DFS那里有说明,不懂大家可以去看一下。

    题中所说的a Depth-First-Search(DFS) spanning tree T of a undirected connected graph G。图是无向图,所以不会有类似于7->5这样的横叉边,以为无向,所以如果5,7间有边,遍历到5时,7就是5的子节点了。对于无向边(u,v),假设DFS到u点。如果u点通过其他路径访问了v点,DFS v点时发现u被标记过,自然不会走(u,v),(u,v)边自然就是不属于DFS树,相当于反向边。否则u一定会通过(u,v)边访问v,自然不存在横叉边。

    理解完这些后,就可以思考下怎么做了。



    上图中存在(4,1),(4,0)构成的两个T环。显然当小环中选边时大环自然也选边了。我们可以用lim[u]记录与u节点的最小环中选边的最小深度。我们用dep[u]记录u点的深度,一遍DFS既可以求出所有点的深度了。图中1,3,4,1构成的T环中,我们可以选择(1,3)边或者(3,4)边。则lim[u]的值为节点3的深度,2。也就是说这里定义一条边的深度以为深度较大节点的深度,选择3节点即为选择3节点和3节点的父节点构成的边。

    那么对于u节点来说,选边时应该选择深度为[ lim[u],dep[u] ] 的边。

    接下来是dp[u][k]的定义。定义dp[u][k]为对于节点u,在选择了深度为k的边后,以u点为根节点的树中还需要选择多少条边。

    首先看dp[0][0]。dp[0][0]就是对于节点0,选择了深度为0的边后,以0为根节点的树中还需要多少边。我们假象0节点上有一个-1节点,这样比较好理解。而dp[0][0]看起来就是我们最终要求的值。如下图:



    同样,lim[u]=0就代表u节点可以选择(-1,0)这条边,当然这条边不在计数内。

    如何求dp[u][k]的值呢?对于任一子节点v来说,dp[v][j]的合理取值是lim[v]<=j<=dep[v]。对于dp[u][k]来说,合理取值是lim[u]<=k<=dep[u]。我们希望dp[u][k]尽量小,那么选择了深度为k的边后,dp[v][j]如果存在j,满足lim[v]<=j<=k,那么在这一区间内的最小的dp[v][j]是满足条件的。因为选择了k边,j满足lim[v]<=j<=k,说明v点形成的T环里有了符合条件的深度为k的边被选择了。当然,对于区间lim[u]<=k<=dep[u]内所有的边,我们可以选择k边,再选择(v,u)这条边。我们在所有的可能中选择最小值。所以我们将(v,u)这条边计算在dp[u][dep[u]]中。

    举个例子:当u为节点3时。节点3中,dp[3][k],深度k可以是0,1,2。也就是(-1,0),(0,1),(1,3)这3条边。当我们选(-1,0)时,显然节点4的lim[4]深度大了点,我们需要同时选择(3,4)这条边。当我们选择(1,3)这条边时,节点4的需求也满足了,也就不要选择(3,4)这条边了。对于所有的dp[u][k],我们都让它们取最小值,这样最终的dp[0][0]是最小的。

    在下表达能力欠佳,如果不懂,可以直接回复= =。

    代码中相当于选择了(-1,0)这条边,所以最后的答案减1了。杭电上时984MS,代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxV=2013;
int first[maxV],vv[maxV<<1],nxt[maxV<<1];
int dp[maxV][maxV];
int lim[maxV],dep[maxV];

void DFS(int u,int p)
{
for(int e=first[u];e;e=nxt[e]) if(vv[e]!=p)
{
dep[vv[e]]=dep[u]+1;
DFS(vv[e],u);
}
}

void DP(int u,int p)
{
for(int i=lim[u];i<=dep[u];i++)
dp[u][i]=0;

for(int e=first[u];e;e=nxt[e]) if(vv[e]!=p)
{
int v=vv[e];
DP(v,u);
int temp=dp[v][dep[v]];
for(int i=lim[v];i<lim[u];i++)
temp=min(temp,dp[v][i]);
for(int i=lim[u];i<=dep[u];i++)
{
temp=min(temp,dp[v][i]);
dp[u][i]+=temp;
}
}
dp[u][dep[u]]++;
}

int main()
{
int n,m;
while(~scanf("%d%d",&n,&m) && n|m)
{
memset(lim,0,sizeof(lim));
memset(dp,0x7f,sizeof(dp));
memset(first,0,sizeof(first));

int e=2;
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
u--,v--;
nxt[e]=first[u],vv[e]=v,first[u]=e++;
nxt[e]=first[v],vv[e]=u,first[v]=e++;
}

dep[0]=0;
DFS(0,-1);

for(int i=n;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
u--,v--;
if(dep[u]<dep[v])
swap(u,v);
lim[u]=max(lim[u],dep[v]+1);
}
DP(0,-1);
printf("%d\n",dp[0][0]-1);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  图论 DP