题目链接:
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; }