现在的位置: 首页 > 综合 > 正文

Codeforces Round #228 Div1 B Fox and Minimal path

2017年04月27日 ⁄ 综合 ⁄ 共 2345字 ⁄ 字号 评论关闭

    这道题的意思大致就是给你一个数k(k<=10^9),要你用不超过1000个点构建一张图,使得这张图中从1号点到n号点刚好有k条最短路。

    想法很简单,乘法原理加加法原理就可以解决了。


    我的想法就是下面这张图:

    比如,上面这张图就是k=31=11111的图,我们把k化为2进制后,从左到右,先加

1—>3这条边,然后加3—>4,3—>5,4—>5,5—>6这四条边,就将总的方案数乘以了2,得到了2=10的图,再加上1—>6的这一条边,方案数+1,变为了3=11的方案数,继续将11左移,即加上6—>7,6—>8,7—>9,8—>9这四条边,方案数就变为了6=110,再+1得到7=111的图,以此类推。

   所以,对于一个数k,我们从二进制的左边第一位开始向右找,每移动一位就加三个点,使方案数*2,若当前位上为1,则在下方加一条边从1连到当前的点,将方案数+1,否则不加,这样就构造出了整个图。

    因为要保证最短路,所以下方的路径要与上方对应,如图所示的加一条边的方法是非常节约点的,但本人的代码因为是在考场上写的,因此多加了很多无用的点,最极端的数据会用到899个点,但如果像上图一样加边的话,最多60*5个点就够了,代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;

/*
  101-->1011(加一条边)-->10110(*2) 
*/

int cnt,ll,last,k;//last表示加倍时应连接的最后一个点,cnt为点的总数

int f[1000+10][1000+10];

void expand()//方案数加倍
{
	f[last][cnt+1]=f[last][cnt+2]=1;
	f[cnt+1][last]=f[cnt+2][last]=1;
	cnt+=3;
	f[cnt][cnt-1]=f[cnt][cnt-2]=1;
	f[cnt-1][cnt]=f[cnt-2][cnt]=1;
	ll+=2;
	last=cnt;
}

void add()//新加一条边
{
	int ret=cnt;
	cnt+=ll;
	cnt--;
	int la=1;
	for(int j=ret+1;j<=cnt;j++)
	{
		f[la][j]=f[j][la]=1;
		la=j;
	}
	f[la][last]=f[last][la]=1;
}

int lowbit(int x)
{
  return x&(-x);
}

void work()
{	
   int sum,tt=0;
   int ret=k;
   while(ret)
   {
   	sum=lowbit(ret);
    ret-=lowbit(ret);
   }
   //求第一个1
   while(sum)
   {
   	sum=(sum>>1);
   	tt++;
   }
   //初始化,有一条边,最后一个点是3
   f[1][3]=1;
   f[3][1]=1;
   ll=1;
   cnt=3;
   last=3;
   
   for(int i=tt-2;i>=0;i--)
   {
   	 expand();//左移一位
   	 if((1<<i)&k)
   	 {
   	 	add();//为1,加1条边
   	 }
   }
   //最后一个点连上2
   f[last][2]=1;
   f[2][last]=1;
   //输出
   printf("%d\n",cnt);
   
   for(int i=1;i<=cnt;i++)
   {
     for(int j=1;j<=cnt;j++)
     {
   	   if(f[i][j])
   	   {
   	      printf("Y");
   	   } 
   	   else printf("N");
     }
     printf("\n");
   }
   
}

int main()
{
  scanf("%d",&k);
  
  work();
  
  return 0;
}

   当然,Gemini同学用十进制也做出来了,还抢了一血,对其机智表示膜拜。


   下来后还看到了一份神代码,只用了100个点,还是为了凑整,简直膜拜,代码如下(注释是本人加的):

#include <stdio.h>
#include <string.h>

const int N = 105;

int g[N][N];

inline void set (int x, int y) 
{
	g[x][y] = g[y][x] = 1;
}

void init () //预处理出初始图 
{
	memset(g, 0, sizeof(g));

	set(1, 3); set(1, 4); set(2, 100);
	for (int i = 3; i < 60; i += 2) //交叉加边 
	{
		set(i, i+2); set(i, i+3);
		set(i+1, i+2); set(i+1, i+3);
	}

	for (int i = 63; i < 100; i++) set(i, i+1);//直线连边 
}

void solve () 
{
	int k;
	scanf("%d", &k);

	for (int i = 30; i; i--) if (k >= (1<<i)) //二进制加边 
	{
		k -= (1<<i);
		set(2*i+1, 63+i); set(2*i+2, 63+i);
	}
	if (k) set(1, 63);
}

void put () //输出地图 
{
	printf("100\n");
	for (int i = 1; i <= 100; i++) 
	{
		for (int j = 1; j <= 100; j++)
			printf("%c", g[i][j] ? 'Y' : 'N');
		printf("\n");
	}
}

int main () 
{
	init();
	solve ();
	put();
	return 0;
}

    代码很短,只有49行,运行速度超快。


    什么意思呢,大概如下图:

    他是先构建了一个这样的图,其中到3,4号点的路有1条,5,6有两条,61,62有2^29条路,这样的话,如果当前要加1的方案数,只需将1号点连到63号点,若要加2,则将3,4,一起连到64号点,以此类推。可以证明这些路的长度相等。因此,我们将k化为二进制,然后扫一遍,有1则加边,不然不管,就将图构建出来了。


    只能膜拜了。

      

抱歉!评论已关闭.