P7630 题解
这一题模拟赛做到的,也作为自己状压 dp 和根号分治的学习笔记,所以比较详细。
题目大意
- 有一个
n\times n 的棋盘,每个点(i,j) (第i 行,第j 列)上有一个数a_{i,j} 。 - 小马第
0 秒在起点(1,1) 上,然后他会以中国象棋里面跳马的方式在棋盘里跳跃,每次跳跃会耗费1 的时间。 - 对于
(i,j) 这个点,若当前时间t 满足a_{i,j}\big|t ,那么它可以经过。否则无法经过,相当于一个障碍。 - 询问经过
t 秒以后,小马可能在哪些位置,并输出它们。
解题思路
考虑设计 dp 状态
首先考虑暴力 dp。设
那么我们发现,这个
具体地,我们定义
我们考虑弱化一下这个题目,设现在没有
if(j-2>=1) s|= (f[i-1][j-2]<<1) | (f[i-1][j-2]>>1);
if(j-1>=1) s|= (f[i-1][j-1]<<2) | (f[i-1][j-1]>>2);
if(j+1<=n) s|= (f[i-1][j+1]<<2) | (f[i-1][j+1]>>2);
if(j+2<=n) s|= (f[i-1][j+2]<<1) | (f[i-1][j+2]>>1);
就是上一个时刻的
但是我们现在有了这个
同理,我们可以状态压缩成二维,变成
有了
现在的问题就转化成处理
考虑处理 can[i][j]
你会发现直接处理它,比较困难,还是会回到最初
我们设一个阈值
当
当
在 dp 的时候,对于当前时间
考虑下这样的复杂度是什么。
-
对于第一种情况,复杂度显然为
O(n^2\times \dfrac{t}{d}) ,瓶颈在预处理。 -
对于第二种情况,复杂度瓶颈不在预处理,而在 dp 的时候。我们设
div(i,j) 表示数i 在[1,j] 内的因数个数。复杂度为O(t\times d+n\times \sum_{i=1}^t div(i,d) )
我们发现这两个复杂度,一个
但是由于还有一个
那么我们有:
由于我们放大了一些下式,那么其实最优的
由于这里的时限 1.5s 非常充裕,所以随便了。这下我们就得到了正解代码?……真的是这样吗?这里是提交记录,只 AC 了 4 个点,其他都 MLE 了。
为什么呢?因为我们的空间只有 64MB,你的
考虑优化空间
首先,
那么
所以我们不能直接把这个贡献或到 vector
就像这样:
for(int k=1;k<=t/a[i][j];k++)
v[ k*a[i][j] ].push_back(make_pair(i,j));
在 dp 时就:
for(auto x:v[i]){
fi=x.first;
se=x.second;
can[fi]|=1<<(n-se);
}
这样的话,你的空间瓶颈就是 vector 的空间了。vector 里面的 pair,即 char 类型就可以。
空间复杂度为 char。注意到 vector 初始也有空间,其他变量也有空间等等,我们得把
这样我们这题就解完了。
参考代码
#include<bits/stdc++.h>
#define maxn 1000010
using namespace std;
typedef int ll;
typedef pair<char,char> pll;
struct node{
ll x,y;
}ans[1010];
ll n,t,x,y,a[40][40],f[2][40],d,s,now,las,can[40];
ll cnt[1010][40],sum,fi,se;
vector<pll>v[maxn];
int main(){
cin>>n>>t>>x>>y;
d=200;
int i,j,k;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++){
cin>>a[i][j];
/*
根号分治算法:设立一个阈值 D,对于 x<=D 的,我们用一种解法
对于 x>D 的,我们使用另一种解法
case 1: 如果 a[i][j]>D 说明,它比较大,是 a[i][j] 倍数的 t 不是很多
那么我们可以直接 for 过去加贡献 can [ k*a[i][j] ][i] |= ( 1<< n-j )
-------------------------------------------------------------
case 2: 如果 a[i][j]<=D 我们考虑首先记录下,对于值为 u 的元素,在第 v 行的状态 f[u][v]
那么,就是 cnt[ a[i][j] ] [i] |= ( 1<< n-j )
然后统计答案的时候该怎么办呢?见下半部分的注释。
*/
if(a[i][j]<=d)cnt[a[i][j]][i]|=(1<<(n-j));
else
for(k=1;k<=t/a[i][j];k++)
v[ k*a[i][j] ].push_back(make_pair(i,j));
}
f[0][x]=(1<<(n-y));
for(i=1;i<=t;i++){
/*我们要把 case 2 的贡献也加到 can 里面去。
这里的 a[i][j] 都很小 (<=D),因此我们可以直接 枚举 1 ~ D
看看里面是他的因数的有几个,然后处理这个贡献。
*/
now=i%2;las=1-now;
for(j=1;j<=n;j++)can[j]=0;
for(auto x:v[i]){
fi=x.first;se=x.second;
can[fi]|=1<<(n-se);
}
for(j=1;j<=d;j++)
if(i%j==0)
for(int k=1;k<=n;k++)
can[k]|=cnt[j][k];
for(j=1;j<=n;j++){
s=0;
if(j-2>=1) s|= (f[las][j-2]<<1) | (f[las][j-2]>>1);
if(j-1>=1) s|= (f[las][j-1]<<2) | (f[las][j-1]>>2);
if(j+1<=n) s|= (f[las][j+1]<<2) | (f[las][j+1]>>2);
if(j+2<=n) s|= (f[las][j+2]<<1) | (f[las][j+2]>>1);
f[now][j]=s&can[j];
}
}
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(f[now][i]&(1<<(n-j)))
ans[++sum]=node{i,j};
cout<<sum<<endl;
for(i=1;i<=sum;i++)
cout<<ans[i].x<<" "<<ans[i].y<<endl;
/*
时间复杂度分析:
a[i][j] > D 瓶颈在预处理,O(n^2 t/D)
a[i][j] < D 瓶颈在 dp 转移时处理贡献, O( t( D+ n* ∑f(t,D) ) )
其中 f(i,j) 表示,值为 i 的元素在 [1,j] 中的因数有几个。
这个东西啊,不好分析,,直接尝试微调阈值当然是一种方法。
考虑放大一点,直接变成 n^2 t/D + ntD >= nt\sqrt(n)
这样的话,你取 D = n/sqrt(n)
由于你放大了 case 2, case 2 是 ×D 的,所以 D 应该更大一点。
大多少我就不知道了,反正 nt\sqrt(n) 在本题应该也足以通过了。
*/
return 0;
}