COCI2011/2012 #1 skakac 状态压缩加速DP
COCI2011/2012 #1 skakac 状态压缩加速DP
题目链接:在里面找一下
写在前面:原题的空间为 64MB。但本题在校内 OJ 上空间开了 128MB。而本题解介绍的写法在 64MB 的限制下无法通过。若要在 64MB 的空间内实现,需要根号分治。
这题有一个比较显然的暴力,开一个三维数组 \(dp_{k,i,j}\) 表示第 \(k\) 个时刻,\((i,j)\) 这个位置是否能够到达。但是这个暴力时空的复杂度都为 \(O(T\times n^2)\)。我们接受不了。显然,第一维可以滚动掉,这样我们的空间就够了,但是时间还不够。注意到,\(dp\) 数组记录的是可行性,而 \(n\) 的大小很小,只有 \(30\)。我们可以考虑压位,即用一个二进制数来表示一行的可行性状态(bitset 貌似是这么实现的)。此时如果没有限制的话,我们可以很轻松的写出复杂度为 \(O(T\times n)\) 的转移。
代码如下:
for(R int k=1;k<=T;++k)
{
g^=1;e=g^1;
memset(dp[g],0,sizeof dp[g]);
for(R int i=1;i<=n;++i)
{
if(i>1)
{
dp[g][i]|=dp[e][i-1]<<2;
dp[g][i]|=dp[e][i-1]>>2;
}
if(i>2)
{
dp[g][i]|=dp[e][i-2]<<1;
dp[g][i]|=dp[e][i-2]>>1;
}
if(i<n)
{
dp[g][i]|=dp[e][i+1]<<2;
dp[g][i]|=dp[e][i+1]>>2;
}
if(i<n-1)
{
dp[g][i]|=dp[e][i+2]<<1;
dp[g][i]|=dp[e][i+2]>>1;
}
}
}
但是,题目是有限制的,有些格子只有在特定时间的时候才能跳到。所以我们可以再开一个数组 \(can_{i,j}\) 表示第 \(i\) 个时刻第 \(j\) 行格子可行位置的状态。那么 \(dp_{i,j} \& can_{i,j}\) 得到的就是 \(i\) 这个时刻第 \(j\) 行能够到达的状态
\(can\) 数组可以通过预处理得到。代码如下:
for(R int i=1;i<=n;++i)
{
for(R int j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
for(R int k=a[i][j];k<=T;k+=a[i][j]) can[k][i]|=1<<(j-1);
}
}
但是这样预处理的时间复杂度上界依然是 \(O(T\times n^2)\)。考虑到时间主要是因为 \(a_{i,j}\) 过小而浪费的,我们可以定义一个参数 \(P\),如果 \(a_{i,j}\le P\) ,我们不预处理它们,而是直接记录,然后在转移的时候,枚举 \(1\) 到 \(P\) 计算出格子中的值为在 \([1,P]\) 范围内的能否到达的情况。那么时间复杂度就为 \(O(\dfrac{T}{P+1}+T\times P+T\times n)\)。对于参数 \(P\) 自己算一下,微调一下就好了。
那么预处理和转移的代码就变成如下:
for(R int i=1;i<=n;++i)
{
for(R int j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
if(a[i][j]<=P) d[a[i][j]][i]|=1<<(j-1);
else for(R int k=a[i][j];k<=T;k+=a[i][j]) can[k][i]|=1<<(j-1);
}
}
int g=0,e=0;
for(R int k=1;k<=T;++k)
{
g^=1;e=g^1;
int tmp[31]={};
memset(dp[g],0,sizeof dp[g]);
for(R int j=1;j<=P;++j) if(k%j==0) for(R int i=1;i<=n;++i) tmp[i]|=d[j][i];
for(R int i=1;i<=n;++i)
{
if(i>1)
{
dp[g][i]|=dp[e][i-1]<<2;
dp[g][i]|=dp[e][i-1]>>2;
}
if(i>2)
{
dp[g][i]|=dp[e][i-2]<<1;
dp[g][i]|=dp[e][i-2]>>1;
}
if(i<n)
{
dp[g][i]|=dp[e][i+1]<<2;
dp[g][i]|=dp[e][i+1]>>2;
}
if(i<n-1)
{
dp[g][i]|=dp[e][i+2]<<1;
dp[g][i]|=dp[e][i+2]>>1;
}
dp[g][i]&=(tmp[i]|can[k][i]);
}
}
整个代码的空间瓶颈是 \(can\) 数组。所以本文的代码无法在 64MB 空间限制的情况下通过。
全部代码如下:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define R register
using namespace std;
const int MAXN = 1e6+5;
const int P = 7;
int n,T,x,y;
int dp[2][31],a[35][35],can[MAXN][31],d[10][31];
int main()
{
scanf("%d %d",&n,&T);
scanf("%d %d",&x,&y);
dp[0][x]|=1<<(y-1);
for(R int i=1;i<=n;++i)
{
for(R int j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
if(a[i][j]<=P) d[a[i][j]][i]|=1<<(j-1);
else for(R int k=a[i][j];k<=T;k+=a[i][j]) can[k][i]|=1<<(j-1);
}
}
int g=0,e=0;
for(R int k=1;k<=T;++k)
{
g^=1;e=g^1;
int tmp[31]={};
memset(dp[g],0,sizeof dp[g]);
for(R int j=1;j<=P;++j) if(k%j==0) for(R int i=1;i<=n;++i) tmp[i]|=d[j][i];
for(R int i=1;i<=n;++i)
{
if(i>1)
{
dp[g][i]|=dp[e][i-1]<<2;
dp[g][i]|=dp[e][i-1]>>2;
}
if(i>2)
{
dp[g][i]|=dp[e][i-2]<<1;
dp[g][i]|=dp[e][i-2]>>1;
}
if(i<n)
{
dp[g][i]|=dp[e][i+1]<<2;
dp[g][i]|=dp[e][i+1]>>2;
}
if(i<n-1)
{
dp[g][i]|=dp[e][i+2]<<1;
dp[g][i]|=dp[e][i+2]>>1;
}
dp[g][i]&=(tmp[i]|can[k][i]);
}
}
int ans=0;
for(R int i=1;i<=n;++i)
for(R int j=1;j<=n;++j) if(dp[T&1][i]&(1<<(j-1))) ++ans;
printf("%d\n",ans);
for(R int i=1;i<=n;++i)
for(R int j=1;j<=n;++j) if(dp[T&1][i]&(1<<(j-1))) printf("%d %d\n",i,j);
return 0;
}
关于 64MB 限制的做法,我粗略的看了下官方题解。大致是对于大于 \(1000\) 的 \(a\) 值,我们直接枚举它的倍数,对于小于 \(1000\) 的 \(a\) 值,我们对其进行质因数分解,然后以更小的空间处理出类似本文 \(can\) 数组的数组。
因为懒,就贴份官方代码好了。。。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int dx[] = { -2, -2, -1, -1, 1, 1, 2, 2 };
int dy[] = { -1, 1, -2, 2, -2, 2, -1, 1 };
const int MAXN = 31;
const int MAXT = 1000001;
const int MAXP = 170; //prosti brojevi manji od 1000
const int MAXPOT = 20; //najveca potencija nekog prostog broja u rastavu broja t
int pr[MAXT], f[MAXT], ff[MAXT], fp[MAXT];
int k[MAXN][MAXN], b[MAXN], bb[MAXN], m[MAXN];
int n, T;
int ind[1000], prosti[MAXP], cnt;
int faktor[MAXP][MAXPOT][MAXN]; // matrica F(p na q)
int fnula[MAXP][MAXP][MAXN]; // matrica G(x,y)
vector< int > v[MAXT];
inline void postavi( int &x, int y, int v ) { // postavlja y-ti bit u x na vrijednost v
if( v == 1 ) x |= (1<<y); else
x &= ~(1<<y);
}
inline int daj( int x, int y ) { return (x>>y)&1; } // vraca vrijednost y-tog bita u x
void sito( int MAX ) {
// izracunava sve proste brojeve manje od 1000
for( int i = 2; i*i < MAXT; ++i )
if( !pr[i] ) {
for( int j = i+i; j < MAXT; j += i )
pr[j] = 1, f[j] = i;
ind[i] = cnt;
prosti[cnt++] = i;
}
// izracunava po jedan prosti faktor i njegov eksponent za svaki broj manji od MAXT kako bi mogli brojeve rastavljati na proste faktore
for( int i = 2; i < MAXT; ++i ) {
if( f[i] == 0 ) f[i] = i;
int x = i;
ff[i] = 1, fp[i] = 0;
while( x%f[i] == 0 ) x /= f[i], ff[i] *= f[i], fp[i]++;
}
}
void solveG() { // izracunava funkciju G(i,j)
for( int i = 0; i < cnt; ++i ) {
for( int j = 0; j < n; ++j )
fnula[i][i][j] = faktor[i][0][j];
for( int j = i+1; j < cnt; ++j )
for( int k = 0; k < n; ++k )
fnula[i][j][k] = (fnula[i][j-1][k] & faktor[j][0][k] );
}
}
int main( void ) {
scanf( "%d %d", &n, &T );
int px, py;
scanf( "%d %d", &px, &py ); --px, --py;
sito( MAXT );
for( int i = 0; i < n; ++i )
for( int j = 0; j < n; ++j ) {
scanf( "%d", k[i]+j );
if( k[i][j] >= 1000 ) { // manje od 1000 slobodnih vremena, sve ih stavi rucno
for( int l = k[i][j]; l <= T; l += k[i][j] )
v[l].push_back( i*32 + j );
} else { // period manji do 1000
int x = k[i][j];
for( int p = 0; p < cnt; ++p ) {
int c = 0;
while( x%prosti[p] == 0 ) x /= prosti[p], c++;
while( c < MAXPOT ) postavi( faktor[p][c][i], j, 1 ), c++;
}
}
}
solveG();
postavi( b[px], py, 1 );
int ms1 = (1<<(n-1))-1, ms2 = (1<<(n-2))-1;
for( int t = 0; t < T; ++t ) {
// b - trenutna matrica
// bb - sljedeca matrica
// m - matrica blokiranosti sekunde t+1
for( int i = 0; i < n; ++i ) {
if( i-1 >= 0 ) bb[i-1] |= (b[i]>>2)|((b[i]&ms2)<<2);
if( i-2 >= 0 ) bb[i-2] |= (b[i]>>1)|((b[i]&ms1)<<1);
if( i+1 < n ) bb[i+1] |= (b[i]>>2)|((b[i]&ms2)<<2);
if( i+2 < n ) bb[i+2] |= (b[i]>>1)|((b[i]&ms1)<<1);
}
for( int i = 0; i < n; ++i ) {
b[i] = bb[i], bb[i] = 0;
m[i] = (1<<n)-1;
}
int x = t+1, last = cnt-1;
while( x > 1 && f[x] < 1000 ) {
int now = ind[ f[x] ];
if( last >= now+1 ) {
for( int i = 0; i < n; ++i )
m[i] &= fnula[now+1][last][i];
}
for( int i = 0; i < n; ++i )
m[i] &= faktor[now][ fp[x] ][i];
x /= ff[x], last = now-1;
}
if( last >= 0 )
for( int i = 0; i < n; ++i )
m[i] &= fnula[0][last][i];
for( int i = 0; i < v[t+1].size(); ++i )
postavi( m[ v[t+1][i]/32 ], v[t+1][i]%32, 1 );
for( int i = 0; i < n; ++i )
b[i] &= m[i];
}
int ans = 0;
for( int i = 0; i < n; ++i )
for( int j = 0; j < n; ++j )
if( daj( b[i], j ) ) ans++;
printf( "%d\n", ans );
for( int i = 0; i < n; ++i )
for( int j = 0; j < n; ++j )
if( daj( b[i], j ) ) printf( "%d %d\n", i+1, j+1 );
return 0;
}