兔子跳跃之谜下
题目描述
小生和小森在玩兔子之谜游戏。有三只兔子排成一排。知道每只兔子的初始位置,以及三个兔窝的位置。
游戏的规则是,重复以下步骤k次:选择两个不同的兔子A和B,分别位于a和b。A可以跳过B到达2*b-a的点:
跳跃是不允许其他小兔子已经在点2*b-a的位置上:
跳跃也不允许一次跳过一个以上的兔子:
现在小生和小森想要知道,k次操作之后,能否让所有兔子都分别跳到一个兔窝里面。注意,第i个兔子并不一定要在第i个巢。并且输出跳法的种数,数值可能很大,要对结果取模1000000007。只要有一个跳跃是不同的,两种方式被认为是不同的。
输入
有多组测试数据:
第一行,包含一个整数Num,表示测试数据的个数。(1<=Num<=10)
每组测试数据,
第一行三个整数,第二行三个整数,分别表示兔子的初始位置和兔窝的位置。两组数值都是严格递增给出。范围均为[-10^18,10^18]。
最后一个整数k。[1,100]。
输出
共Num行,
跳跃的种数。
样例输入
8
0 5 8
0 8 11
1
0 5 8
0 8 11
3
0 5 8
0 8 11
2
5 8 58
13 22 64
58
0 1 2
1 2 3
100
5 8 58
20 26 61
58
67 281 2348
235 1394 3293
83
-1000000000000000000 999999999999999998 999999999999999999
-1000000000000000000 999999999999999999 1000000000000000000
5
样例输出
1
5
0
0
0
537851168
167142023
29
题解:
首先考虑一下题目的特性
显然,中间的兔子可以往两边跳跃,而外面的兔子只有一只能往里面跳跃,甚至不能跳跃(当三只兔子等距时),这种性质让我们想到了什么?没错,就是树,二叉树。
一棵二叉树上的任意节点都有两个儿子结点和一个父亲结点(除了根和叶),而我们可以把三只兔子的坐标想象成树上的一个节点,每一次跳跃都是树上的一次相邻节点的移动,兔子们有无限的状态,所以这棵树也是无限的,甚至这是一片森林,因为有不同的等距状态。
有了这个思想的转化之后,我们就可以着手解决这个问题了。
求方案数,暴搜显然不可以,可以考虑树上DP,首先我们得判断一下这两只兔子在k步以内能否相遇。难道要把每只兔子k步能跳跃到的节点都暴搜出来在两两比较吗?显然不用。我们可以把每只兔子向上走k步以内的父亲结点求出来,再一一比较是否有相同的就行了(想一想为什么),这就是最暴力的做法。
我们用dp[i][j][k]表示初始节点A里LCA i步,结束点B里LCA j步,还剩下k步时的方案总数。为了方便处理,我们假设B节点不动(这很重要,也很巧妙)。
Case 1:when i>0 ,此时我们可以让A向上或者向下走
Dp[i][j][k]=dp[i-1][j][k-1]+dp[i+1][j][k-1]*2
Case 2:when i=0,j>0,此时我们也可以让A向上或者向下走,但是要注意A向上走时两者的LCA是A,所以也要跟着动,要注意照状态写方程
Dp[0][j][k]=dp[0][j+1][k-1]+dp[0][j-1][k-1]+dp[1][j][k-1]
Case 3:when i=0,j=0,种这情况类似于case2,同样要注意A向上走时两者的LCA是A,所以也要跟着动,要注意照状态写方程
Dp[0][0][k]=dp[0][1][k-1]+dp[1][0][k-1]*2
到了这里,问题已经解决了大半,但还有一点需要非常重视!那就是根的情况。
在case2和case3中存在着向上走的情况,这是要注意A是否走到了根节点,因为到了根节点后会无法向上走。
来自lzw大神的想法,我们有了一种非常重要又简单的判断方法,注意到我们一开始做的时候假定了B节点不懂,而我们一开始又能够处理出来B离根节点的距离,而到根节点的情况只会在i=0时A向上走,然后j+1是产生,所以我们断定j>dis时dp为0,dis是B到根节点的距离
另外一种解释是把状态解释为B向上j步为LCA,A向上i步为LCA,这样接可以轻易的看出j>dis时dp为0
具体可以看代码。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
#define MOD 1000000007
const int maxk=110;
struct node
{
ll x,y,z;
}fa[2][maxk];
int f[maxk][maxk][maxk];
int num,k,t1,t2,deep_a,deep_b;
node a,b;
bool flag;
inline bool pd(node a,node b)
{
return (a.x==b.x)&&(a.y==b.y)&&(a.z==b.z);
}
bool check(node a)
{
if (a.y-a.x==a.z-a.y) return false;
else return true;
}
void get_fa(node a,node &b)
{
ll x=a.x,y=a.y,z=a.z;
if (y-x<z-y)
{
b.x=y; b.y=y+y-x; b.z=z;
}
else if (y-x>z-y)
{
b.x=x; b.y=y+y-z; b.z=y;
}
}
int dp(int i,int j,int k)
{
if (k==0)
if (i==0&&j==0) return 1;
else return 0;
if (i+j>k||j>deep_b) return 0;
if (f[i][j][k]!=-1) return f[i][j][k];
//printf("%d %d %d %d %d\n",i,j,k,deep_a,deep_b);
if (i>0 && j>0)
{
f[i][j][k]=(dp(i+1,j,k-1)*2LL+dp(i-1,j,k-1))%MOD;
}
else if (i==0&&j>0)
{
f[i][j][k]=((ll)dp(1,j,k-1)+dp(0,j-1,k-1)+dp(0,j+1,k-1))%MOD;
}
else if (i>0 && j==0)
{
f[i][j][k]=(dp(i+1,0,k-1)*2LL+dp(i-1,0,k-1))%MOD;
}
else if (i==0&&j==0)
{
f[i][j][k]=(dp(1,0,k-1)*2LL+dp(0,1,k-1))%MOD;
}
return f[i][j][k];
}
int main()
{
scanf("%d",&num);
while (num--)
{
scanf("%lld%lld%lld",&a.x,&a.y,&a.z);
scanf("%lld%lld%lld",&b.x,&b.y,&b.z);
scanf("%d",&k);
fa[0][0]=a; fa[1][0]=b;
deep_a=0; deep_b=0;
for (int i=1;i<=k&&check(fa[0][i-1]);i++)
{
get_fa(fa[0][i-1],fa[0][i]);
deep_a=i;
}
for (int i=1;i<=k&&check(fa[1][i-1]);i++)
{
get_fa(fa[1][i-1],fa[1][i]);
deep_b=i;
}
flag=true; t1=-1; t2=-1;
for (int i=0;i<=deep_a&&flag;i++)
for (int j=0;j<=deep_b&&flag;j++)
if (pd(fa[0][i],fa[1][j])) { t1=i; t2=j; flag=false; }
if (t1==-1) { printf("0\n"); continue; }
f[0][0][0]=1;
memset(f,-1,sizeof(f));
printf("%d\n",dp(t1,t2,k));
}
return 0;
}