您的位置:首页 > 其它

第六届蓝桥杯校园选拔赛试题---派遣敢死队 解题报告

2015-04-18 15:49 288 查看
原题 :
G将军有一支训练有素的军队,这个军队除开G将军外,每名士兵都有一个直接上级(可能是其他士兵,也可能是G将军)。现在G将军将接受一个特别的任务,需要派遣一部分士兵(至少一个)组成一个敢死队,为了增加敢死队队员的独立性,要求如果一名士兵在敢死队中,他的直接上级不能在敢死队中。

请问,G将军有多少种派出敢死队的方法。注意,G将军也可以作为一个士兵进入敢死队。

输入格式

输入的第一行包含一个整数n,表示包括G将军在内的军队的人数。军队的士兵从1至n编号,G将军编号为1。

接下来n-1个数,分别表示编号为2, 3, ..., n的士兵的直接上级编号,编号i的士兵的直接上级的编号小于i。

输出格式

输出一个整数,表示派出敢死队的方案数。由于数目可能很大,你只需要输出这个数除10007的余数即可。

样例输入1

3

1 1

样例输出1

4

样例说明

这四种方式分别是:

1. 选1;

2. 选2;

3. 选3;

4. 选2, 3。

样例输入2

7

1 1 2 2 3 3

样例输出2

40

数据规模与约定

对于20%的数据,n ≤ 20;

对于40%的数据,n ≤ 100;

对于100%的数据,1 ≤ n ≤ 100000。

资源约定:

峰值内存消耗 < 256M

CPU消耗 < 2000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0

注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。

注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型

解题思路 :
熟悉树形动态规划的都能看出这是个极度类似于'没有上司的宴会'的题目, 但是这题的规划方向还融合了组合数学的内容, 建议对这题完全没有思路的同学去看一下树形DP和没有上司的宴会, 在这里, 仅详述从子树到原树的状态转移关系.
1. 所有的结点都包含两个成员, beChoosedCount 和 beAbandonCount, 分别代表该结点被选择时带来的方案数 和 被抛弃时带来的方案数, 并且这两个值的初始化都是1, 因为无论如何, 被选是1种情况, 不被选也是1种情况. 比如说, 一个物品放在桌面上, 我们能对它有多少中操作方式呢 ? 大家都知道是2种 : 拿或不拿. 所以, 操作总数 = beChoosedCount
+ beAbandonCount.
2. 当已知下属子树的操作方案信息, 如何求原树的操作方案信息 ? 按着题意, 原树的根节点代表子树根节点的上司, 而且上司与下属只能选其一, 所以, 我们能有如下的关系式:
上司的beChoosedCount = 累乘( 下属的beAbandonCount );
上司的beAbandonCount = 累乘( 下属的总操作数 ) = 累乘( 下属的beChoosedCount + 下属的beAbandonCount );

为什么是累乘 ? 这属于一个选排列的数学问题, 这里不作详述.
有了上述关系式, 我们就可以写代码了, 存储结构依旧采用有向图的邻接表实现, 而不是一般树的左孩子右兄弟表示法.

#include <iostream>
using namespace std;
#define MOD 10007
class Vertex;
class Edge{				// ...边
public:
Edge *m_next;	// ...出度顶点的下一条边
Vertex *m_to;	// ...本边指向
Edge():m_next(NULL),m_to(NULL){}
};
class Vertex{			// ...顶点
public:
Edge *m_edge;	// ...第一条出度边
int beChooseCount,beAbandonCount;	// ...自身被选时的总方案数, 自身不被选时的总方案数
Vertex():m_edge(NULL){beChooseCount = beAbandonCount = 1;}
void Add( Vertex* to, Edge *E){		// ...添加一条指向顶点to的边
E->m_next = m_edge;
E->m_to = to;
m_edge = E;
}
};
class Graph{			// ...图
public:
Vertex *VSet;	// ...顶点集
Edge   *ESet;	// ...边集
int	ECount;	// ...边的数量
Graph( int N ) {
VSet = new Vertex[N+1];
ESet = new Edge
;
ECount = 0;
}
inline void Add( int v1, int v2 ){	// ...添加一条从v1到v2的边
VSet[v1].Add( &VSet[v2] , ESet + ECount++ );
}
~Graph(){ delete VSet; delete ESet; }
int GetCount(){						// ...获取总方案数
DFS( &VSet[1] );
return (VSet[1].beAbandonCount + VSet[1].beChooseCount - 1)%MOD; // ...减去一种全空的可能
}
static void DFS( Vertex *V){		// ...搜索顶点V的方案信息
for( Edge *e = V->m_edge; e!=NULL; e=e->m_next ){
DFS( e->m_to );	// ...先求子树信息
// ...若原树结点能被选, 则累乘所有子树结点不能被选的方案数
V->beChooseCount = V->beChooseCount * e->m_to->beAbandonCount %MOD;
// ...若原树结点不能被选, 则累乘所有子树结点的总方案数
V->beAbandonCount = V->beAbandonCount * (e->m_to->beChooseCount+e->m_to->beAbandonCount )%MOD;
}
}
};
int main(int argc, char** argv)
{
int N,superior;	// ...士兵数, 上司序号
cin >> N;
Graph G(N);
for( int i = 2; i <= N; G.Add( superior,i++) )
cin >> superior;
cout << G.GetCount();
return 0;
}


算法分析 : 因为每个顶点只遍历一次, 所以整个算法的算法的时间复杂度为O(n), 从存储而言, 只存储了n个顶点和n-1条边, 所以空间复杂度为O(2n).
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: