IOI 94 The Clock 详细的总结,从指数级复杂度到常数时间的优化

原题

如果用bfs的话,也即,把操作顺序的先后考虑上的话,那么要注意用hash表来判重,用空间换时间。这个低效的方法也是能过的……

//hash表判重,dfs,从短到长按字典序扩展操作序列的长度 
/*
{
ID: lzwjava1
PROG: clocks
LANG: C++
}
*/
#include<cstdio>
#include<cstring>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
const int MaxState=500000;
int move[9][5]={{0,1,3,4},{0,1,2},{1,2,4,5},{0,3,6},
{1,3,4,5,7},{2,5,8},{3,4,6,7},{6,7,8},{4,5,7,8}};
int Nmove[9]={4,3,4,3,5,3,4,3,4};

typedef char state[9];
state st[MaxState];
char oper[MaxState];

bool isGoal(state a)
{
	for(int i=0;i<9;i++)
	  if(a[i]) return false;
  return true;
}

int fa[MaxState];

void doOper(state &a,int op)
{
	for(int i=0;i<Nmove[op];i++)
	  ++a[move[op][i]]%=4;
}

const int HashSize=10007;
int head[HashSize];
int next[MaxState];
int value[10];

int hash(int u)
{
	int t=0;
	for(int i=9;i>=0;i--)//mistook:i++,it should be"i--"
	  t+=value[9-i]*st[u][i];
  return t%HashSize;
}

bool tryToInsert(int u)
{
	int h=hash(u);
	int p=head[h];
	while(p!=-1)
	{
		if(memcmp(st[p],st[u],sizeof(st[p]))==0) return false;
		p=next[p];
	}
	next[u]=head[h];
	head[h]=u;
	return true;
}

void init_hash()
{
	memset(head,-1,sizeof(head));
	value[0]=1;
	for(int i=1;i<10;i++)
	  value[i]=value[i-1]*4;
}

int bfs()
{
	int front,rear;
	front=0;rear=1;
	fa[front]=-1;
	init_hash();
	while(front<rear)
	{
		state &u=st[front];
		if(isGoal(u)) return front;
		for(int i=0;i<9;i++)
		{
			state &v=st[rear];
			memcpy(v,u,sizeof(v));
			doOper(v,i);
			if(tryToInsert(rear))
			{
				fa[rear]=front;
				oper[rear++]=i;
			}
		}
		front++;
	}
}

void print_ans(int u)
{
	if(fa[u]==-1) return;
	print_ans(fa[u]);
	if(fa[u]!=0)  printf(" ");
  printf("%d",oper[u]+1);
}

int main()
{
  freopen("clocks.in","r",stdin);
  freopen("clocks.out","w",stdout);
  int i,j;
  for(i=0;i<9;i++)
  {
  	int t;
  	scanf("%d",&t);
  	st[0][i]=t/3%4;
  	//printf("%d\n",st[0][i]);
  }
  int ans=bfs();
  //printf("%d\n",ans);
  print_ans(ans);printf("\n");
  return 0;
}

/*
*/

事实上,操作的先后并不重要。用dfs降低空间复杂度,但最坏的时间复杂度是一样的。4^9。bfs找到解即返回。

//dfs,无所谓操作的先后,而只记次数。遍历每一种可能的情况。 
/*
{
ID: lzwjava1
PROG: clocks
LANG: C++
}
*/
#include<cstdio>
#include<cstring>
#include<stdlib.h>
using namespace std;
const int MaxMove=27;
char move[9][9]={"ABDE","ABC","BCEF",//直接复制粘贴题目,在程序中来得到操作矩阵 
"ADG","BDEFH","CFI","DEGH","GHI","EFHI"};
int A[9];
int best[9];//最优答案 
int nbest;//最优答案的序列长度。先要最短,然后字典序最小 
int Clock[9][9];//Clock[i][j]为操作i一次j需要转过的刻钟的数目,3表示90°,0表示0°。只有这两种情况 
FILE *in,*out;

void cal_Clock()
{
	memset(Clock,0,sizeof(Clock));
	char *p;
	for(int i=0;i<9;i++)
		for(p=move[i];*p;p++)
		  Clock[i][*p-'A']=3;
}

bool is_ok(int *A)
{
	for(int i=0;i<9;i++)
	  if(A[i]!=12) return false;
  return true;
}

void solve(int *m,int cur)
{
	int i,j;
	if(cur==9)
	{
		if(is_ok(A))
		{
			int t=0;
			for(i=0;i<cur;i++) t+=m[i];
			if(!nbest || t<nbest)
			{
				memcpy(best,m,sizeof(best));
				nbest=t;
			}
		}
		return;//mistook:没加 
	}
	for(i=3;i>=0;i--)//先3个小序号的操作,这样可以得到最小字典序的方案 
	{
		for(j=0;j<i;j++)
			for(int k=0;k<9;k++)
			{
				A[k]+=Clock[cur][k];
				if(A[k]==15) A[k]=3;
				//fprintf(out,"A[k]=%d\n",A[k]);
			}
		m[cur]=i;
		solve(m,cur+1);
		for(j=0;j<i;j++)
			for(int k=0;k<9;k++)
			{
				A[k]-=Clock[cur][k];
				if(A[k]==0) A[k]=12;
			}
	}
}

void testClock()
{
	int i,j;
	for(i=0;i<9;i++,printf("\n"))
	  for(j=0;j<9;j++)
	  {
  		printf(" %d",Clock[i][j]);
  	}
}

int main()
{
	in=fopen("clocks.in","r");
	out=fopen("clocks.out","w");
	int i,j;
	for(i=0;i<9;i++) fscanf(in,"%d",&A[i]);
	//for(i=0;i<9;i++) printf("%d ",A[i]);
	int m[9];//m为次数数组,m[i]为执行操作i的次数 
	cal_Clock();
	//testClock();
	nbest=0;
	solve(m,0);
	bool first=true;
	for(i=0;i<9;i++)
	  if(best[i])
	  {
  		for(j=0;j<best[i];j++)
  		{
		  	if(!first) fprintf(out," ");
		  	else first=false;
		  	fprintf(out,"%d",i+1);
		  }
  	}
	fprintf(out,"\n");
	exit(0);
}

还可以通过打表得到最优解。这下可以优化到常数时间。

简便起见,把3,6,9,10表示成0,1,2,3。现在得到一个数组A,有9个操作,记第i个操作为O[i],O[i]让数组A中的某些数向前增加1,超过3则变为0。求一个最短的操作序列,让数组A的9个元素都变为0。

记V[j]为在最终的操作序列中执行O[j]的次数。

a[i][j],表示仅让A[i]向前增1,而其它数都不变,所要执行O[j]的次数。得到矩阵a。矩阵a可以通过dfs的方法得到。

先求得a数组。改造一下上面的程序即可。

//dfs,巧妙得到a数组 
#include<cstdio>
#include<cstring>
#include<stdlib.h>
using namespace std;
const int MaxMove=27;
char move[9][9]={"ABDE","ABC","BCEF",
"ADG","BDEFH","CFI","DEGH","GHI","EFHI"};
int A[9];
int best[9];
int nbest;
int Clock[9][9];
FILE *in,*out;

void cal_Clock()
{
	memset(Clock,0,sizeof(Clock));
	char *p;
	for(int i=0;i<9;i++)
		for(p=move[i];*p;p++)
		  Clock[i][*p-'A']=3;
}

bool is_ok(int *A)
{
	for(int i=0;i<9;i++)
	  if(A[i]!=12) return false;
  return true;
}

void solve(int *m,int cur)
{
	int i,j;
	if(cur==9)
	{
		if(is_ok(A))
		{
			int t=0;
			for(i=0;i<cur;i++) t+=m[i];
			if(!nbest || t<nbest)
			{
				memcpy(best,m,sizeof(best));
				nbest=t;
			}
		}
		return;//mistook:没加 
	}
	for(i=3;i>=0;i--)
	{
		for(j=0;j<i;j++)
			for(int k=0;k<9;k++)
			{
				A[k]+=Clock[cur][k];
				if(A[k]==15) A[k]=3;
				//fprintf(out,"A[k]=%d\n",A[k]);
			}
		m[cur]=i;
		solve(m,cur+1);
		for(j=0;j<i;j++)
			for(int k=0;k<9;k++)
			{
				A[k]-=Clock[cur][k];
				if(A[k]==0) A[k]=12;
			}
	}
}

void testClock()
{
	int i,j;
	for(i=0;i<9;i++,printf("\n"))
	  for(j=0;j<9;j++)
	  {
  		printf(" %d",Clock[i][j]);
  	}
}

void work()
{
	//in=fopen("clocks.in","r");
	int i,j;
	//for(i=0;i<9;i++) printf("%d ",A[i]);
	int m[9];
	//testClock();
	nbest=0;
	solve(m,0);
	bool first=true;
	fprintf(out,"{%d",best[0]);
	for(i=1;i<9;i++)
	  fprintf(out,",%d",best[i]);
	fprintf(out,"},\n");
}

int main()
{
	out=fopen("a.out","w");
	int i,j;
	for(i=0;i<9;i++) A[i]=12;
	cal_Clock();
	for(i=0;i<9;i++)
	{
		A[i]=9;
		work();//得到尽让钟i转过90度的最优序列
		A[i]=12;
	}
	exit(0);
}
接下来,神代码出现了:

//dfs下得到预处理数组。情况有限, 
/*
{
ID: lzwjava1
PROG: clocks
LANG: C++
}
*/
#include<cstdio>
#include<cstring>
int a[9][9]={
{3,3,3,3,3,2,3,2,0},
{2,3,2,3,2,3,1,0,1},
{3,3,3,2,3,3,0,2,3},
{2,3,1,3,2,0,2,3,1},
{2,3,2,3,1,3,2,3,2},
{1,3,2,0,2,3,1,3,2},
{3,2,0,3,3,2,3,3,3},
{1,0,1,3,2,3,2,3,2},
{0,2,3,2,3,3,3,3,3}
};

int main()
{
	freopen("clocks.in","r",stdin);
	freopen("clocks.out","w",stdout);
	int v[9];//v[i]表示操作i的执行次数 
	memset(v,0,sizeof(v));
	int i,j,k;
	for(i=0;i<9;i++)
	{
		scanf("%d",&k);
		for(j=0;j<9;j++)//计算把第i个钟的k变为12 v数组的变化。 
		  v[j]=(v[j]+a[i][j]*(12-k)/3)%4;//转一次,要执行j操作a[i][j]次 
	} 
	bool first=true;
	for(i=0;i<9;i++)
	  if(v[i])
	  {
  		for(j=0;j<v[i];j++)
  		{
		  	if(first) first=false;
  		  else printf(" ");
  		  printf("%d",i+1);
		  }	  
  	}
	printf("\n");
	return 0;
}

如果不是很理解,见:

Lucian Boca submitted a constant time solution

You can precalculate a matrix a as following:

a[i][0],a[i][1],....,a[i][8] is the number of moves '1','2','3',...'9' necessarly to move ONLY clock i (where 0 < i <= 8 - there are 9 clocks: 0=A, 1=B, ... 8=I) 90 degrees clockwise. So, you have the matrix:

int a[9][9]= { {3,3,3,3,3,2,3,2,0},
               {2,3,2,3,2,3,1,0,1},
               {3,3,3,2,3,3,0,2,3},
               {2,3,1,3,2,0,2,3,1},
               {2,3,2,3,1,3,2,3,2},
               {1,3,2,0,2,3,1,3,2},
               {3,2,0,3,3,2,3,3,3},
               {1,0,1,3,2,3,2,3,2},
               {0,2,3,2,3,3,3,3,3} };

That means: to move ONLY the clock 0 (clock A) 90 degrees clockwise you have to do {3,3,3,3,3,2,3,2,0}, 3 moves of type 1, three moves of type 2, ..., 2 moves of type 8, 0 moves of type 9, etc.

To move ONLY the clock 8 (clock I), you have to do the moves {0,2,3,2,3,3,3,3,3}: 0 moves of type 1, 2 moves of type 2... 3 moves of type 9....

That's it! You count in a vector v[9] how many moves of each type you have to do, and the results will be modulo 4 (%4 - 5 moves of any type have the same effect 1 move has). 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值