这道题的意思大致就是给你一个数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则加边,不然不管,就将图构建出来了。
只能膜拜了。