题目链接:
http://acm.sgu.ru/problem.php?contest=0&problem=449
题目意思:
这题目真难读懂。
有n个节点,m条水平线,告诉每条水平线下竖线的数量以及每个竖线下的节点值。问怎样组合该树,使得该树结构清晰(竖线和横线不交叉)且靠左边的节点尽可能小。
解题思路:
先按水平线的高度从大到小排序,高度大的在下面,也就是从下往上处理,对每条水平线,把他所有的竖线连成一个连通块,并新建一个共同的父亲节点(两个属性,一个id,一个孩子节点的最小值),最后把所有的树联合在一起,凑成一颗完整的树结构。也就是虚拟一条水平线,它包含所有的节点。建好图后,对每个节点对孩子节点按值从小到大排序,最后dfs把叶子输出来就行了。
代码:
//#include<CSpreadSheet.h>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<sstream>
#include<cstdlib>
#include<string>
#include<string.h>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#include<ctime>
#include<bitset>
#define eps 1e-6
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define ll __int64
#define LL long long
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
#define M 1000000007
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define Maxn 110000
struct Node
{
int id,va;
Node(int a=0,int b=0)
{
id=a;
va=b;
}
friend bool operator < (struct Node a,struct Node b) //按节点大小从小到大排序
{
return a.va<b.va;
}
}node[Maxn<<2]; //类似于线段树 合并点 并查集处理
vector<Node>myv[Maxn*4]; //存每棵树的子树节点情况
vector<int>pp[Maxn]; //子树包含关系
int n,m,q;
int ans[Maxn];
map<int,int>myp; //map中是按第一关键字从小到大排序的
int a[Maxn]; //按高度从大到小进行处理
int b[Maxn];//缓存当前子树节点的情况
int fa[Maxn<<2],cnt;
int find(int x)
{
if(fa[x]!=x)
fa[x]=find(fa[x]); //边查找变压缩
return fa[x];
}
void init()
{
myp.clear();
for(int i=1;i<=n;i++)
pp[i].clear();
for(int i=1;i<=4*n;i++) //初始化fa数组
{
fa[i]=i;
myv[i].clear();
}
int tt=0; //给给定的子树标号
for(int i=1;i<=m;i++)
{
++tt;
int cur,nn;
scanf("%d%d",&cur,&nn);
myp[cur]=tt; //记录标号
while(nn--)
{
int temp;
scanf("%d",&temp);
pp[tt].push_back(temp); //该标号下的节点
}
}
++tt; //为了最终凑成一颗树,增加一个虚拟的组合情况
myp[0]=tt;//放到最后 把所有的树整合在一起 凑成一棵树
for(int i=1;i<=n;i++)
pp[tt].push_back(i); //该虚拟树包含所有节点
m++;
for(map<int,int>::iterator it=myp.begin();it!=myp.end();it++)
a[tt--]=it->second; //相当于从大到小排序,记录标号
}
void dfs(int cur) //图建好了后 只用跑一边dfs就可以把叶子节点按从左到右的顺序找到就行了
{
if(!myv[cur].size()) //到达了叶子节点
{
ans[++cnt]=node[cur].va; //把该棵树的值记录
return ;
}
for(int i=0;i<myv[cur].size();i++) //依次往下扫
dfs(myv[cur][i].id);
}
void Cal()
{
for(int i=1;i<=n;i++)
{
node[i].id=i;
node[i].va=i; //最初只有n颗只含一个叶子节点的树
}
for(int i=1;i<=m;i++)
{
int cur=a[i]; //处理当前的
for(int j=0;j<pp[cur].size();j++)
b[j]=find(pp[cur][j]); //找到各子节点所在的树根
sort(b,b+pp[cur].size()); //从小到大排序
int kk=unique(b,b+pp[cur].size())-b; //去掉重复的子树
++n; //新建一个节点 归并所有的子树
node[n].id=n;
node[n].va=n;
for(int j=0;j<kk;j++) //建图
{
int temp=b[j];
fa[temp]=n; //所有的子树的父亲归并
node[n].va=min(node[n].va,node[temp].va); //用值最小的叶子节点代替
myv[n].push_back(Node(temp,node[temp].va)); //建树
}
}
for(int i=1;i<=n;i++) //对每颗子树 根据值排序
sort(myv[i].begin(),myv[i].end()); //子树的键值小的排在前面 然后一边dfs就行了
cnt=0;
dfs(n); //从最后一个总节点出发
for(int i=1;i<=q;i++)
{
int cur;
scanf("%d",&cur);
printf("%d\n",ans[cur]);
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
while(~scanf("%d%d%d",&n,&m,&q))
{
init();
Cal();
}
return 0;
}