【墨西哥区域赛】Carpet
原题:
题意:
给你一个树,有1e5个节点,让你把这个树放在一个长1e6宽20的网格图里,要求一个格子放一个节点,树边之间不能相交
这是一道构造题
因为树的形状可能性很多,很复杂,所以不能简单猜测,而必须要依据某种性质,来保证生成的解一定合法
先尝试小规模,或特殊的问题也是一个重要的思想方法
首先考虑一个节点A和它的所有儿子
不难发现,这时可以把这个节点放在某层,然后把它的儿子放在下一层的一个区间内
只要爸爸出现的顺序和儿子所在区间出现的顺序相同,就不会发生冲突
因为在两层之间连边,斜率只会无限趋于0,所以不会出现于(xA, yA+1)和(xA, yA-1)冲突的情况,只需保证这两层之间的边不相交
继而可以发现,只要满足这个条件,不管A和它的儿子们在什么位置,都不会和别人冲突
为了压缩空间,所有的儿子可以连续放在一起,即使和爸爸相距太远也没有问题
其次,题目数据中宽度为20
根据经验,与众不同的数字往往有重要含义
可以敏感地发现20刚好是log1e6
联系到树链剖分
因为重链剖分保证任意一个节点到根节点的路径最多只会经过logn条重链
所以可以让一条重链躺在一层,而轻儿子都放到下一层,根节点放左上角
这样对于任意一点,想要通过构造的网格图去往根节点,至多会往上爬logn层
所以深度不会超过限制,而宽度显然充足
所以问题就解决了
轻儿子放在连续的区间,区间出现的顺序和爸爸出现的顺序相同,这能保证不发生冲突
进行树链剖分,重链剖分的性质保证深度不会超过限制
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 using namespace std; 7 int rd(){int z=0; char ch=getchar(); 8 while(ch<'0'||ch>'9') ch=getchar(); 9 while(ch>='0'&&ch<='9'){z=(z<<3)+(z<<1)+ch-'0'; ch=getchar();} 10 return z; 11 } 12 struct edg{int y,nxt;}e[210000]; int lk[110000],ltp=0; 13 void ist(int x,int y){ 14 e[++ltp]=(edg){y,lk[x]}; lk[x]=ltp; 15 e[++ltp]=(edg){x,lk[y]}; lk[y]=ltp; 16 } 17 int n; 18 int fth[110000],hvc[110000],sz[110000]; 19 int ax[110000],ay[110000]; 20 int hd[30]; 21 void dfs1(int x,int y){ 22 fth[x]=y; sz[x]=1; 23 int mx=0,mxid=0; 24 for(int i=lk[x];i;i=e[i].nxt)if(e[i].y!=y){ 25 dfs1(e[i].y,x); 26 sz[x]+=sz[e[i].y]; 27 if(sz[e[i].y]>mx) mx=sz[e[i].y],mxid=e[i].y; 28 } 29 hvc[x]=mxid; 30 } 31 void dfs(int x,int y){ 32 ay[x]=y,ax[x]=++hd[y]; 33 for(int i=lk[x];i;i=e[i].nxt)if(e[i].y!=fth[x] && e[i].y!=hvc[x]) 34 dfs(e[i].y,y+1); 35 if(hvc[x]) dfs(hvc[x],y); 36 } 37 int main(){ 38 //freopen("ddd.in","r",stdin); 39 memset(lk,0,sizeof(lk)); 40 memset(hd,0,sizeof(hd)); 41 cin>>n; 42 int l,r; 43 for(int i=1;i<n;++i){ 44 l=rd(),r=rd(); 45 ist(l,r); 46 } 47 dfs1(1,0); 48 dfs(1,1); 49 for(int i=1;i<=n;++i) printf("%d %d\n",ax[i],ay[i]); 50 return 0; 51 }