题目大意:
给出一个棵有N个节点的树,对其进行Q次询问,每次询问给出两个点的标号X和Y,要求在X为根的情况下输出Y的儿子中标号最小的儿子的标号和Y的子孙中标号最小的。
输入时第一行输入测试组数case
然后每一组输入N和Q
然后输入N-1条边,
最后输入Q组查询数对X和Y
Sample input:
1
7 3
1 2
1 5
2 3
2 4
5 6
5 7
1 2
5 3
3 2
Sample output:
3 3
No answer!
1 1
题目分析:
这道题很明显是求最优解问题,而动态规划就是解决此类问题的很好的方法,首先从简单的方向入手,要求出给定节点的儿子中最小的,由于是树形结构,而且根不固定,与该节点相连的点都有可能是该节点的父亲,所以我们在进行动态规划时可以设置一个数组f[vertrix][2],数组下标第一个表示当前节点的标号,第二个下标用0表示记录的是最小相连节点的标号,1表示记录的是次小相连节点的标号,通过一次遍历比较,可以计算出最小相连节点标号和次小相连节点标号,每次只需判断最小相连节点是否是给定节点的父亲即可,如果不是,那么最小相邻节点就是最小子节点,如果是,那么次小相邻节点就是所求。
那么关键问题是,如何做我们才能判断在当前根为X的情况下,最小相连节点是否是给定节点的父亲呢?
我们可以借鉴tarjan算法的深度优先搜索中的标记时间戳的预处理方法高效地解决这一问题,具体操作如下:
我们设最小相连节点标号为Z
最开始默认1为根节点,然后按照深度优先搜索的顺序标记时间戳dfn[vertrix],并且记录子树中的子孙节点的最大时间戳back[vertrix],那么时间戳一定满足一个性质,就是父节点的时间戳dfn一定小于子孙节点dfn且子孙节点的时间戳一定不大于父节点back,那么我们可以根据这一性质进行判断,如果X节点的时间戳在当前搜索树的根为根的情况在Z的子树中,那么当X节点变为根时,Z是Y节点的父亲,否则不是,而X节点在子树中的判断只需要判断它的时间戳是否在dfn[Z] 和back[Z]之间即可。
相对难一点的:
求取在给定根X的前提下求取给定点Y最小子孙标号
其实可以用相似地思路进行求解,设置数组dp[vertrix][2],用第二个小标为0和为1的数组存储最小子孙和次小子孙。同样利用之前的时间戳预处理
转移方程如下:
V是当前根下u的子节点,对于每一个子节点v:
temp = min ( v , dp[v][0] );
if ( dp[u][0] > temp )
{
dp[u][1] = dp[u][0];
dp[u][0] = temp;
loc[u] = v;
}
else if ( dp[u][1] > temp )
dp[u][1] = temp;
那么剩下的就是针对每个查询进行回答了
可以分为以下几种情况:
一、在当前根为1的情况下,X在Y的子树中且Y为1时,那么判断X是否是在最小子孙所在的子树中,用loc[vertrix]记录最小子孙所在的子树的根( Y的儿子之一)的标号,如果X在这棵子树中,那么Y的最小子孙只可能在X为根的情况下与Y不在一棵子树或者是Y的祖先,那么Y的最小子孙就是次小子孙,否则是最小子孙。
二、如果当前根为1的情况下,X在Y的子树中且Y不为1时,那么X作为根时,1 一定是Y的子孙,所以Y的最小子孙一定是1
三、如果当前根为1的情况下,X不在Y的子树中,那么X变为根后并不会改变Y的子树结构,所以Y的最小子孙不变
下面是ac代码:
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #define MAX 100077 #define INF 0xfffffff using namespace std; int t , n , q; int maxn; struct { int v,next; }e[MAX<<1]; int head[MAX]; int cc = 0; void add ( int u , int v ) { e[cc].v = v ; e[cc].next = head[u]; head[u] = cc++; } int dfn[MAX] , step = 0; int back[MAX]; int dp[MAX][2]; int f[MAX][2]; int loc[MAX]; int father[MAX]; //bool used[MAX]; void dfs ( int u , int fa ) { dfn[u] = ++step; back[u] = dfn[u]; father[u] = fa; dp[u][0] = dp[u][1] = f[u][0] = f[u][1] = INF; for ( int i = head[u] ; i != -1 ; i = e[i].next ) { int v = e[i].v; if ( v != fa ) { if ( f[u][0] > v ) { f[u][1] = f[u][0]; f[u][0] = v; } else if ( f[u][1] > v ) { f[u][1] = v; } dfs ( v , u ); int temp; temp = min ( v , dp[v][0] ); if ( dp[u][0] > temp ) { dp[u][1] = dp[u][0]; dp[u][0] = temp; loc[u] = v; } else if ( dp[u][1] > temp ) dp[u][1] = temp; back[u] = max ( back[v], back[u] ); } } } int main ( ) { scanf ( "%d" , &t ); int x , y; while ( t-- ) { memset ( head , -1 , sizeof( head )); cc = 0; step = 0; scanf ( "%d%d" , &n , &q ); for ( int i = 1; i <= n-1 ; i++ ) { scanf ( "%d%d" , &x , &y ); add ( x , y ); add ( y , x ); } // memset ( used , 0 , sizeof ( used ) ); dfs ( 1 , -1 ); //cout << "sadasdadas"<< endl; for ( int i = 1 ; i <= q ; i++ ) { scanf ( "%d%d" , &x , &y ); int ans1 , ans2; if ( dfn[x] > dfn[y] && dfn[x] <= back[y] ) { if ( y == 1 ) { if ( dfn[x] >= dfn[f[y][0]] && dfn[x] <= back[f[y][0]]) ans1 = f[y][1]; else ans1 = f[y][0]; if ( dfn[x] >= dfn[loc[y]] && dfn[x] <= back[loc[y]] ) ans2 = dp[y][1]; else ans2 = dp[y][0]; } else { if ( dfn[x] >= dfn[f[y][0]] && dfn[x] <= back[f[y][0]] ) ans1 = min ( father[y] ,f[y][1] ); else ans1 = min ( f[y][0] , father[y] ); ans2 = 1; } } else { ans1 = f[y][0]; ans2 = dp[y][0]; } if ( ans1 == INF || ans2 == INF ) printf ( "no answers!\n"); else printf ( "%d %d\n" , ans1 , ans2 ); } puts (""); } }