头脑风暴时间~搬积木问题

super_chris 2009-09-15 05:23:49
问题如下:
有K<30000个相同的立方体积木 编号从1-K
起初 每个立方体单独自成一堆
有俩人A,B
A给B下指令
B按顺序执行
指令数为N<100000
指令有两种:
1.MOVE X Y 把含有立方体X的堆摞在含有立方体Y的堆上
2.COUNT X 报告X下面压着多少个立方体
现在请你编程帮助B同学完成COUNT的任务

程序输入:
指令数N;
N行指令;

程序输出:
对于每个COUNT指令,打印一行来报告COUNT的结果

注意:输入中不包含立方体数K

BRAIN STORM~NOW!!!!!
...全文
327 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
fenix124 2009-09-17
  • 打赏
  • 举报
回复
描述有问题。不过可以用二叉树这样做的。
一个节点的总的偏移等于他在堆内偏移加上他所在堆在整堆中的偏移,就是递归向上相加。
右节点就等于父节点在整堆中的偏移,而左节点需要额外加上父节点右节点的个数。
super_chris 2009-09-17
  • 打赏
  • 举报
回复
[Quote=引用 22 楼 fenix124 的回复:]
可以用二叉树描述合并过程,左子上,右子下。叶子为每个积木,内点为合并成的堆。
二叉树的节点记录节点左右节点数,每个节点从下往上计算,如果当前节点是父节点的右子节点则结束,否则继续向上累加节点的右节点数。时间复杂度为log(n)
[/Quote]能说的详细些吗?谢谢啦~
super_chris 2009-09-17
  • 打赏
  • 举报
回复
[Quote=引用 25 楼 xiaoyu821120 的回复:]
引用 22 楼 fenix124 的回复:
可以用二叉树描述合并过程,左子上,右子下。叶子为每个积木,内点为合并成的堆。
二叉树的节点记录节点左右节点数,每个节点从下往上计算,如果当前节点是父节点的右子节点则结束,否则继续向上累加节点的右节点数。时间复杂度为log(n)

这个算法查询太慢了,肯定要超时的。根据这个数据量,这题的算法已经限的很死了,就是要你用并查集来解的。并查集查询效率,是小于log(n)的。
[/Quote]对 进行路径压缩
xiaoyu821120 2009-09-17
  • 打赏
  • 举报
回复
[Quote=引用 22 楼 fenix124 的回复:]
可以用二叉树描述合并过程,左子上,右子下。叶子为每个积木,内点为合并成的堆。
二叉树的节点记录节点左右节点数,每个节点从下往上计算,如果当前节点是父节点的右子节点则结束,否则继续向上累加节点的右节点数。时间复杂度为log(n)
[/Quote]
这个算法查询太慢了,肯定要超时的。根据这个数据量,这题的算法已经限的很死了,就是要你用并查集来解的。并查集查询效率,是小于log(n)的。
绿色夹克衫 2009-09-17
  • 打赏
  • 举报
回复
LS说的不错,用二叉树合并应该可以解,复杂度大概是n*log(n)。n应该不会超过30000,
这样的效率应该可以接受了!
linren 2009-09-17
  • 打赏
  • 举报
回复
【程序】
#include <stdio.h>

int a[30001];
int b[30001];
int c[30001];
int w[30001];
int n;

void move(int i,int j){
int x=i,y=j;
n=0;
while(x!=a[x]){
w[n]=x;
n++;
x=a[x];
}
while(y!=a[y]) y=a[y];
if(x==y) return;
a[x]=y;
b[x]=c[y];
c[y]+=c[x];
for(i=n-1;i>=0;i--){
b[w[i]]+=b[a[w[i]]];
a[w[i]]=y;
}
}

void count(int i){
int x=i,y=i;
n=0;
while(a[x]!=a[a[x]]){
w[n]=x;
n++;
x=a[x];
}
for(i=n-1;i>=0;i--){
b[w[i]]+=b[a[w[i]]];
a[w[i]]=a[x];
}
printf("%d\n",b[y]);
}

int main(){
int i,n;
char o;
int x,y;
for(i=1;i<=30000;i++){
a[i]=i;b[i]=0;c[i]=1;
}
scanf("%d",&n);
while(n--){
scanf("\n%c",&o);
if(o=='M'){
scanf("%d %d",&x,&y);
move(x,y);
}else if(o=='C'){
scanf("%d",&x);
count(x);
}
}
return 0;
}

【说明】
OJ实在是……
太受挫折了……
5868361	linren	1988	Accepted	512K	250MS	C	823B	2009-09-17 13:02:30
5866649 linren 1988 Time Limit Exceeded C 591B 2009-09-16 23:03:12
5866644 linren 1988 Wrong Answer C 678B 2009-09-16 23:02:08
5866597 linren 1988 Wrong Answer C 627B 2009-09-16 22:52:44
5866394 linren 1988 Time Limit Exceeded C 572B 2009-09-16 22:16:21
5866369 linren 1988 Wrong Answer G++ 562B 2009-09-16 22:11:53
5866315 linren 1988 Wrong Answer GCC 567B 2009-09-16 22:03:05
5866284 linren 1988 Wrong Answer C 567B 2009-09-16 21:58:02
5864004 linren 1988 Time Limit Exceeded C++ 600B 2009-09-16 16:21:21
5863997 linren 1988 Time Limit Exceeded GCC 600B 2009-09-16 16:20:29
5863900 linren 1988 Time Limit Exceeded GCC 552B 2009-09-16 16:00:29
5863605 linren 1988 Time Limit Exceeded GCC 416B 2009-09-16 14:53:01
5863578 linren 1988 Time Limit Exceeded GCC 464B 2009-09-16 14:47:46
5863443 linren 1988 Time Limit Exceeded C 542B 2009-09-16 14:21:32
5863352 linren 1988 Time Limit Exceeded C++ 534B 2009-09-16 14:00:40
5863337 linren 1988 Time Limit Exceeded GCC 588B 2009-09-16 13:58:23
5863286 linren 1988 Time Limit Exceeded GCC 878B 2009-09-16 13:51:36
5863272 linren 1988 Runtime Error C 877B 2009-09-16 13:49:56
5863240 linren 1988 Runtime Error C 865B 2009-09-16 13:42:16
whg01 2009-09-16
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 super_chris 的回复:]
引用 8 楼 whg01 的回复:
给个算法,MOVE复杂度为O(N);COUNT复杂度为O(1)。
假设K=30000
typedef stCubeNode{
    stCubeNode *pNext;            //当前块下面的相邻方块。
    stCubeNode *pPre;            //当前块上面的相邻方块。
    stCubeNode *pTop;            //当前块所在堆,最上面的块。
    int        belowCubeCount;  //当前块下面方块的数目。
 
}CubeNode;
CubeNode *pAllNode = malloc(K*sizeof(CubeNode));
memset(pAllNode, 0, K*sizeof(CubeNode));
执行MOVE X Y指令时:
找到pAllNode[X]所在堆的末尾块pTailX,及pALLNode[Y]所在堆的pTopY
然后把pTailX和pTopY链接起来。
再从pTailX 逐级向上,更新每个块的belowCubeCount。
再从pTailY 逐级向下,更新每个块的pTop.
COUNT指令直接输出pAllNode[X].belowCubeCount。

积木数M 指令数N
复杂度O(MN)?这个题的M N都很大 可能不能承受
[/Quote]
执行MOVE指令复杂度不是O(MN),是O(K)。即方块的个数。具体速度取决于X所在的堆。
你的那个并查集和我说的方法大同小异。只不过在处理每个块下有多少块的方式上有差异。
super_chris 2009-09-16
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 linren 的回复:]
【程序】
C/C++ code#include<stdio.h>
#include<stdlib.h>void init(int**a,int**b,int n){int i;*a=(int*)malloc(sizeof(int)*n);*b=(int*)malloc(sizeof(int)*n);for(i=1;i<n;i++) (*a)[i]=-1;//记录集合每个立方体上面的立方体for(i=1;i<n;i++) (*b)[i]=-1;//记录集合每个立方体下面的立方体}void move(int*a,int*b,int x,int y){int px=x,py=y,pz=x;while(b[px]!=-1) px=b[px];//查找含有x的最下面的立方体=>pxwhile(a[py]!=-1) py=a[py];//查找含有y的最上面的立方体=>pywhile(a[pz]!=-1) pz=a[pz];//查找含有x的最上面的立方体=>pzif(py==pz)return;//如果在同一个集合不进行操作 b[px]=py;
a[py]=px;
}void count(int*b,int x){int px=x;int c=0;while(b[px]!=-1){
px=b[px];c++;
}
printf("%d cubes under x(%d)\n",c,x);
}void del(int**a,int**b){
free(*a);free(*b);
}int main(){int*a,*b;int n=30000;
init(&a,&b,n);/**执行指令******************/
move(a,b,1,2);
move(a,b,3,4);
move(a,b,3,2);

count(b,1);
count(b,2);
count(b,3);
count(b,4);/******************执行指令**/
del(&a,&b);return0;
}
【运行结果】
Assembly code1 cubes under x(1)0 cubes under x(2)3 cubes under x(3)2 cubes under x(4)
Press any key to continue
【说明】
move(a,b,1,2);//把含有1的堆放在含有2的堆的上面
1
2

move(a,b,3,4);//把含有3的堆放在含有4的堆的上面
1  3
2  4

move(a,b,3,2);//把含有3的堆放在含有2的堆的上面
3
4
1
2
[/Quote]

写的很经典的模拟 不过不知道模拟在时间复杂度上能不能承受 我把你的代码改一下提交试试 :)
super_chris 2009-09-16
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 sbwwkmyd 的回复:]
看不明白:
1.只能实现且实现完成COUNT指令,那么COUNT中能使用哪些资源?
2.实现整个程序,使用COUNT指令的执行效率最优,这样比较简单,还是有什么限制?
[/Quote]你的问题我没看懂。。
showjim 2009-09-16
  • 打赏
  • 举报
回复
看不明白:
1.只能实现且实现完成COUNT指令,那么COUNT中能使用哪些资源?
2.实现整个程序,使用COUNT指令的执行效率最优,这样比较简单,还是有什么限制?
linren 2009-09-16
  • 打赏
  • 举报
回复
【程序】
#include <stdio.h>
#include <stdlib.h>

void init(int **a,int **b,int n){
int i;
*a=(int*)malloc(sizeof(int)*n);
*b=(int*)malloc(sizeof(int)*n);
for(i=1;i<n;i++) (*a)[i]=-1;//记录集合每个立方体上面的立方体
for(i=1;i<n;i++) (*b)[i]=-1;//记录集合每个立方体下面的立方体
}

void move(int *a,int *b,int x,int y){
int px=x,py=y,pz=x;
while(b[px]!=-1) px=b[px];//查找含有x的最下面的立方体=>px
while(a[py]!=-1) py=a[py];//查找含有y的最上面的立方体=>py
while(a[pz]!=-1) pz=a[pz];//查找含有x的最上面的立方体=>pz
if(py==pz) return;//如果在同一个集合不进行操作
b[px]=py;
a[py]=px;
}

void count(int *b,int x){
int px=x;
int c=0;
while(b[px]!=-1){
px=b[px];c++;
}
printf("%d cubes under x(%d)\n",c,x);
}

void del(int **a,int **b){
free(*a);free(*b);
}

int main(){
int *a,*b;
int n=30000;
init(&a,&b,n);

/**执行指令******************/
move(a,b,1,2);
move(a,b,3,4);
move(a,b,3,2);

count(b,1);
count(b,2);
count(b,3);
count(b,4);

/******************执行指令**/
del(&a,&b);
return 0;
}

【运行结果】
1 cubes under x(1)
0 cubes under x(2)
3 cubes under x(3)
2 cubes under x(4)
Press any key to continue

【说明】
move(a,b,1,2);//把含有1的堆放在含有2的堆的上面
1
2

move(a,b,3,4);//把含有3的堆放在含有4的堆的上面
1  3
2  4

move(a,b,3,2);//把含有3的堆放在含有2的堆的上面
3
4
1
2
LPR_Pro 2009-09-16
  • 打赏
  • 举报
回复
UP
super_chris 2009-09-16
  • 打赏
  • 举报
回复
我AC了,并查集。
看来大家对这个问题不感兴趣,呵呵,把代码发上来吧:
说几点,这个题用的是并查集,并和查这两个操作还各自分担了一部分的更新节点信息的任务。
并操作更新了和根结点直接相连的结点,即并之间的另一根结点。
查操作则利用自身的递归性质更新了其余结点的信息。
所以如果只并不查,信息是不正确的。每次并之后都要跟随一次查。
而一般的并查集问题,在解决问题时必须用到“查”操作,所以不存在信息不正确的问题。
而且并操作需要查操作为其做准备,也保证了并之前的节点信息正确性。
但是这道题里的COUNT指令,并不需要查操作,至少在我设计的这个并查集里是这样。
所以在响应COUNT指令时,我强制它查一次,这样就保证了结果的正确。
#include <iostream>

using namespace std;

struct {
int parent;
int CubeInStack;
int CubeBelow;
}cube[30010];

void MakeSet(int SizeOfSet)
{
for (int i = 1;i <= SizeOfSet;i++)
{
cube[i].parent = i;
cube[i].CubeInStack = 1;
cube[i].CubeBelow = 0;
}
}

void Union(int RootOfX,int RootOfY,int NodeX,int NodeY)
{
//将X所在集合依附于Y所在集合,更新Y集合根节点的CubeInStack,更新X集合根节点的CubeBelow
cube[RootOfX].parent = RootOfY;
cube[RootOfX].CubeBelow += cube[RootOfY].CubeInStack;
cube[RootOfY].CubeInStack += cube[RootOfX].CubeInStack;
}

int Find(int NodeToFind)
{
if(cube[NodeToFind].parent==NodeToFind)
return NodeToFind;
int temp = cube[NodeToFind].parent;
cube[NodeToFind].parent = Find(cube[NodeToFind].parent);
cube[NodeToFind].CubeBelow += cube[temp].CubeBelow;
return cube[NodeToFind].parent;
}

int main()
{
MakeSet(30005);
int NumOfOrders;
cin>>NumOfOrders;
char order;
int x,y;
while(NumOfOrders--)
{
//scanf("%c %d %d",&order,&x,&y);
scanf("\n%c",&order);
//cin>>order;

if (order=='M')
{
scanf(" %d %d",&x,&y);
//cin>>x>>y;
int rootx = Find(x);
int rooty = Find(y);
Union(rootx,rooty,x,y);
}
else
{
scanf(" %d",&x);
Find(x);
//cin>>x;
cout<<cube[x].CubeBelow<<endl;
}
//getchar();
}
}
fenix124 2009-09-16
  • 打赏
  • 举报
回复
合并和计算数目的复杂度均为log(n)
fenix124 2009-09-16
  • 打赏
  • 举报
回复
可以用二叉树描述合并过程,左子上,右子下。叶子为每个积木,内点为合并成的堆。
二叉树的节点记录节点左右节点数,每个节点从下往上计算,如果当前节点是父节点的右子节点则结束,否则继续向上累加节点的右节点数。时间复杂度为log(n)
tiancaocn 2009-09-16
  • 打赏
  • 举报
回复
K=30000
typedef stCubeNode{
stCubeNode *pNext; //当前块下面的相邻方块。
stCubeNode *pPre; //当前块上面的相邻方块。
stCubeNode *pTop; //当前块所在堆,最上面的块。
int belowCubeCount; //当前块下面方块的数目。

}CubeNode;
CubeNode *pAllNode = malloc(K*sizeof(CubeNode));
memset(pAllNode, 0, K*sizeof(CubeNode));
执行MOVE X Y指令时:
找到pAllNode[X]所在堆的末尾块pTailX,及pALLNode[Y]所在堆的pTopY
然后把pTailX和pTopY链接起来。
再从pTailX 逐级向上,更新每个块的belowCubeCount。
再从pTailY 逐级向下,更新每个块的pTop.
COUNT指令直接输出pAllNode[X].belowCubeCount。
super_chris 2009-09-16
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 whg01 的回复:]

执行MOVE指令复杂度不是O(MN),是O(K)。即方块的个数。具体速度取决于X所在的堆。
你的那个并查集和我说的方法大同小异。只不过在处理每个块下有多少块的方式上有差异。
[/Quote]

MOVE复杂度是O(N)指令数为M 总的复杂度就是O(MN)
并查集的话MOVE复杂度为O(logN)指令数为M,总的复杂度为O(MlogN)

MOVE的区别正好体现了并查集的优点
要说本质的话
感觉你的程序还是做了些多余的工作 每个木块都记录自己上面的下面的和下面总数

linren同学的程序和你的四项基本一致 只是他的COUNT也是O(N),不影响本质 刚才改了一下他的代码 提交了一下 超时了 不知道还能不能优化
fenix124 2009-09-16
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 fenix124 的回复:]
并查集,数据量很小
[/Quote]
不好意思,看错
fenix124 2009-09-16
  • 打赏
  • 举报
回复
并查集,数据量很小
super_chris 2009-09-15
  • 打赏
  • 举报
回复
并查集可达到 O(nlogn) 正在研究

加载更多回复(9)

33,008

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧