P3713 [BJOI2017] 机动训练

P3713 [BJOI2017] 机动训练

题目背景

AM 4:45

又是晴朗的好天气。

AM 5:00

不要嘛,再睡一会

AM 5:05

呜……欺负人

题目描述

睡眼朦胧的菜酱 (?) 已经被二爷拉起来晨跑了。而这个时间你在做什么呢?

咳咳,言归正传,最近菜酱的训练遇到了点小麻烦。

凌晨绕格瑞赛亚岛跑一圈是基本,在那之后,菜酱还要接受严格的机动训练。所谓机动训练,就是在紧急情况 (?) 下,高速且隐蔽地从位置 s 移动到位置 t 的训练。一般来说,st 是根据二爷的心情随机决定的,但是由于菜酱已经熟悉了地形,每次总是能找到不太费劲的路径,二爷决定增大难度。所谓增大难度,其实就是指定整条路径,这样菜酱就没办法抄近道了。当然,由于是为了实战而进行的训练,二爷也不会随便乱指定路径,至少不会故意绕路。然后发生的问题就是,如何才能「随机」一整条路径出来?二爷统计了全岛所有的合法路径,打算每次在这个表格里随机抽一条出来。但是很快二爷发现,许多路径所经过的地形是完全相同的,这类路径的训练会更加有用。于是二爷修改了随机策略,地形较为常见的路径权重会变得更大。

一次偶然的机会,菜酱看到了二爷的随机策略,并发动技能「过目不忘 (?)」记了下来。现在你要帮菜酱分析数据。

为什么是你?当然是因为否则就会被菜酱爆头 (并不)。

整个岛可以看作一片 m×n 的区域,每个格子有自己的地形。

一条路径由一系列八连通的格子组成,两个格子八连通当且仅当这两个格子拥有公共的顶点。

定义一条“机动路径”如下:

  1. 它是一条不自交的路径,即路径上任意两个格子都不是同一个。
  2. 它的起点和终点处于不同位置,换言之这条路径至少包含 2 个格子。
  3. 从起点开始,任何一步只能向不远离终点的方向移动,这里不远离指的是 xy 两个方向都不远离。

举例说明:

.....t ...... .---.
-++... ---... .-s-.
-s+... -s+..t .-+-.
---... ---... ..t..

图中加号和减号标明了与 s 八连通的所有格子,其中加号是“不远离 t”的方向。

因此可以看出,如下路径是机动路径:

++++++t ......+t .......t
+...... .....++. ......+.
+...... ..++++.. ...+++..
s...... s++..... s+++....

而如下路径不是机动路径:

\../---t .......t .s.
|--..... ....../. /..
|....... s..../.. \..
s....... .\--/... .t.

需要注意的是,某些不合法的路径甚至比机动路径还要短,这是因为机动路径不是按照长度来定义的。

接下来定义一条机动路径的地形,岛上的地形由一个矩阵给出,如:

.**.
*..*
*..*
.**.

那么,一条机动路径的地形序列就是它所经过的地形排成一列,如:

s-\.
...\
...|
...t

地形序列就是 .****.

每条机动路径的权重就是与之拥有相同地形序列的机动路径数量之和,例如与这条路径拥有相同地形序列的路径有

./-t t... ...s s-\. ./-s s... ...t t-\.
/... |... ...| ...\ /... |... ...| ...\
|... \... .../ ...| |... \... .../ ...|
s... .\-s t-/. ...t t... .\-t s-/. ...s

8 条,注意回文时正反算两条,以及自己也算一条。

所以这条机动路径的权重是 8,同时所有这 8 条机动路径的权重都是 8

现在你需要统计所有的机动路径权重之和。

如果对这种统计方式没有直观的感受,可以查看样例说明。

输入格式

第一行两个整数 m,n,表示岛的大小。

接下来 m 行,每行 n 个字符,表示岛的地形。

输出格式

仅一个数,表示所有机动路径的权重之和。

由于这个数可能很大,你只需要输出它对 109+9 取模的结果。

样例 #1

样例输入 #1

2 2
.*
*.

样例输出 #1

72

样例 #2

样例输入 #2

2 3
.*.
*.*

样例输出 #2

418

提示

样例解释 1

用中括号括起来的一些数对表示一条机动路径,坐标先行后列:

  • 地形序列 .*[(1,1),(1,2)], [(1,1),(2,1)], [(2,2),(2,1)], [(2,2),(1,2)],共 4 条,每条权重为 4,计 16
  • 地形序列 *.[(1,2),(1,1)], [(2,1),(1,1)], [(2,1),(2,2)], [(1,2),(2,2)],共 4 条,每条权重为 4,计 16
  • 地形序列 ..[(1,1),(2,2)], [(2,2),(1,1)],共 2 条,每条权重为 2,计 4
  • 地形序列 **[(1,2),(2,1)], [(2,1),(1,2)],共 2 条,每条权重为 2,计 4
  • 地形序列 .*.[(1,1),(1,2),(2,2)], [(1,1),(2,1),(2,2)], [(2,2),(2,1),(1,1)], [(2,2),(1,2),(1,1)],共 4 条,每条权重为 4,计 16
  • 地形序列 *.*[(1,2),(1,1),(2,1)], [(2,1),(1,1),(1,2)], [(1,2),(2,2),(2,1)], [(2,1),(2,2),(1,2)],共 4 条,每条权重为 4,计 16

共计 16+16+4+4+16+16=72

样例解释 2

  • 地形序列 .*7 条,每条权重为 7,计 49
  • 地形序列 *.7 条,每条权重为 7,计 49
  • 地形序列 ..4 条,每条权重为 4,计 16
  • 地形序列 **4 条,每条权重为 4,计 16
  • 地形序列 ..*2 条,每条权重为 2,计 4
  • 地形序列 .*.10 条,每条权重为 10,计 100
  • 地形序列 .**2 条,每条权重为 2,计 4
  • 地形序列 *..2 条,每条权重为 2,计 4
  • 地形序列 *.*10 条,每条权重为 10,计 100
  • 地形序列 **.2 条,每条权重为 2,计 4
  • 地形序列 .*.*6 条,每条权重为 6,计 36
  • 地形序列 *.*.6 条,每条权重为 6,计 36

共计 49+49+16+16+4+100+4+4+100+4+36+36=418

  • 对于 100% 的数据,1m,n30,字符集由大小写字母,数字和 . * 构成。

Sulution:

这应该是我为数不多没删样例及其解释的题目,因为真的很有用。

首先,题目要求的路径的起点和终点竟然是任意的,然后我们来看一下让我们求什么:对于个序列,假设它被“走出”过 a 次,那么他的贡献是 a2. 我们有点难想到将其转化:求从两个点 x,y 分别 出发,能走出该序列的二元组 (x,y) 的对数。

然后我们发现点的个数 n=900 并不大。所以我们考虑 n2 枚举点对。但还要加上方向这一限制:假设用 e,s,w,n 代表方向。我们不难发现,总方案数:

ans=fes+fenfe+fws+fwnfw

然后我们就可以钦定一个方向对(wayx,wayy),然后再钦定一个点对来进行记忆化搜索。至于复杂度嘛,虽然听起来挺爆炸的,理论貌似能跑到 O(8×n2×360) 但是由于能匹配上的点对并不多,并且我们使用了记忆化搜索,所以 360 这种骇人听闻的复杂度是根本跑不满的,我们两眼一闭就冲过去了。

Code:

#include<bits/stdc++.h>
#define int long long
const int N=35;
const int mod=1000000009;
using namespace std;
int n,m;
char Map[N][N];
int f[N][N][N][N];
int g[5][5][5][5];
void init(){for(int i=0;i<N;i++)for(int j=0;j<N;j++)for(int p=0;p<N;p++)for(int q=0;q<N;q++)f[i][j][p][q]=-1;}
struct Node{int dx,dy;};
void add(int &x,int y){x=(x+y)%mod;}
vector<Node> A,B;
void build_way(int a,int b,vector<Node> &V)
{
V.clear();
for(int i=-1;i<=1;i++)if(!i||i==a)for(int j=-1;j<=1;j++)if((i||j)&&(!j||j==b))V.push_back((Node){i,j});
}
int dp(int a,int b,int c,int d)
{
if(a<1||n<a||b<1||m<b)return 0;
if(c<1||n<c||d<1||m<d)return 0;
if(Map[a][b]!=Map[c][d]) return f[a][b][c][d]=0;
if(~f[a][b][c][d])return f[a][b][c][d];
int aa,bb,cc,dd;
int res=1;
for(Node d1 : A)for(Node d2 : B)
{
aa=a+d1.dx,bb=b+d1.dy,cc=c+d2.dx,dd=d+d2.dy;
add(res,dp(aa,bb,cc,dd));
}
return f[a][b][c][d]=res;
}
int dfs(int a,int b,int c,int d)
{
int aa=a+1,bb=b+1,cc=c+1,dd=d+1;
int _a=-a+1,_b=-b+1,_c=-c+1,_d=-d+1;
if(~g[aa][bb][cc][dd])return g[aa][bb][cc][dd];
build_way(a,b,A);build_way(c,d,B);
init();
int res=0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)for(int p=1;p<=n;p++)for(int q=1;q<=m;q++)
{
add(res,dp(i,j,p,q));
}
g[aa][bb][cc][dd]=g[cc][dd][aa][bb]=res;
g[_a][_b][_c][_d]=g[_c][_d][_a][_b]=res;
return res;
}
vector<Node> Way1,Way2;
void build()
{
for(int i=-1;i<=1;i++)for(int j=-1;j<=1;j++)
if(i&&j)Way2.push_back((Node){i,j});else if(i||j)Way1.push_back((Node){i,j});
}
int calc(int x,int y)
{
int res=0;
for(Node d : Way2)add(res,dfs(x,y,d.dx,d.dy));
for(Node d : Way1)add(res,-dfs(x,y,d.dx,d.dy)+mod);
return res;
}
void work()
{
cin>>n>>m;
memset(g,-1,sizeof(g));
build();
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>Map[i][j];
int ans=0;
for(Node d : Way2)add(ans,calc(d.dx,d.dy));
for(Node d : Way1)add(ans,-calc(d.dx,d.dy)+mod);
printf("%lld\n",ans);
}
#undef int
int main()
{
//freopen("makina.in","r",stdin);freopen("makina.out","w",stdout);
ios_base::sync_with_stdio(0);
cin.tie(0);
work();
return 0;
}
posted @   liuboom  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示