背景
(创作声明:包含虚构创作)
自从阴暗潮湿的沼泽地里莫名其妙生长出红树的那一刻起,有一种传说中的红皮肤怪物诞生了。
它们在沼泽莲叶上发出诱人的声响,它们身形小巧,穿梭于水草和藤蔓之间。
第一天便有经过的傻子村民被它生吞。村民们找遍了沼泽外围,连尸体都没看见。和他一齐失踪的还有常年在附近山地生活,偶尔下沼泽地喝水的一只山羊。
第二天,村民们隔着老远听到女巫的惨叫。当提着剑和牛奶的村民们赶到附近时,只见到红皮肤魔头远遁的身影。
村民们一向惧怕的女巫,也未能幸免。
沼泽附近的村庄笼罩在恐惧的阴霾中。
……
几个月后,一位身着蓝色铠甲的勇者来到村庄,他有很多东西,与村民们进行了友好的交易,借住在村民的小木屋里。不大的村庄里充满了快活的空气。后一天村民们发现,一直以来守护村庄的强大铁人不见了。
那一天,村民们回想起了被红皮肤魔头支配的恐惧。
勇者决定亲自去沼泽地探查,我和另外几个胆大的村民跟了上去。
结果却极其惨烈……上帝啊!我们为勇者默哀!
当我们到达沼泽地外围时,就迎面遇上了魔头!勇者毫无戒备地走了过去,说:“这不就是个……”
话音未落,他就被魔头一口吞掉了!
我们逃得很慌,根本来不及回头看,只听见魔头近似嬉笑的响声渐渐远去……
~
——沼泽村庄图书馆:《村民回忆录》
题面
当Mojump更新了1.19快照版本时,std通过摸鱼了解了所有更新的内容,包括沼泽红树林,红色的青蛙,泥土等等……
他当然也知道了青蛙刚出来时有个特性,那就是吞噬一切,包括玩家。所以他打算到沼泽地去探险。
他开启了一个新地图,并出生在了一块 n × m n\times m n×m 的沼泽地。该地有些位置的草地刚触及海平面,另一些位置则低了一格,被水填充。
std 现在有一个有意思的问题,由于种子特性,地图生成时该区域 n × m n\times m n×m 个位置各有一个独立的概率 a i , j a_{i,j} ai,j 会低一格,被水填充。他想知道最大的单个水池面积的期望大小。单个水池可以理解为一个被水填充的格子的极大四联通块。
a i , j a_{i,j} ai,j 和答案都对 998244353 998244353 998244353 取模。(并不是用多项式的暗示)
1 ≤ n , m ≤ 40 , n × m ≤ 40. 1\leq n,m\leq 40,n\times m\leq 40. 1≤n,m≤40,n×m≤40.
题解
(由于题解极其简略,我多花了点时间在题面背景上摸鱼)
由于 n × m n\times m n×m 不超过 40,所以 n , m n,m n,m 的最小值不超过 6,我们不妨设 n ≤ m n\leq m n≤m ,那么我们可以维护当前列的连通性、每个连通块的大小、扫过的连通块的最大大小——作为状态(!)设计DP。
当你用精心打磨出的大模拟爆搜对总合法状态数进行计算后,会发现它最大在 1.6 e 5 1.6e5 1.6e5 左右。
实现上,把连通性用最小表示法后,大可直接用 map
存 DP 状态(pair<int,string>
,最大大小和连通块大小都不超过 40,可以用 char
)然后带一个
log
\log
log 的全局复杂度加成进行计算。不然,可能有亿点麻烦。
最终用插头DP的方式转移,总时间复杂度 O ( 可 过 ) O(可过) O(可过) (两个 log \log log?)。
CODE
pair
的第一位存的是最小表示法,第二位的 string
第一个位置存最大连通块大小,后面的位置存当前列每个连通块的大小。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 500005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
static const int maxn = 1000000;
static char b[maxn];
static int pos = 0,len = 0;
if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
if(pos == len) return -1;
return b[pos ++];
}
//#define getchar() xchar()
LL read() {
LL f = 1,x = 0;int s = getchar();
while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
if(!x) {putchar('0');return ;}
if(x<0) putchar('-'),x = -x;
return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}
const int MOD = 998244353;
int n,m,s,o,k;
int a[45][45],pr[45][45];
int id[5000005],to[5000005],tp[5000005],cnt;
int findd(int S) {
int ty[10] = {},xx = S,a[10],cn = 0,c2 = 0,rs = 0;
while(xx) a[++ cn] = xx%10,xx /= 10;
for(int i = cn;i > 0;i --) {
if(a[i] && !ty[a[i]]) ty[a[i]] = ++ c2;
rs = rs*10+ty[a[i]];
}return id[rs];
}
string ep = "0";
pair<int,string> dw(int S,string b) {
int ty[10] = {},xx = S,a[10],cn = 0,c2 = 0,rs = 0,mx = b[0];
int bu[10] = {};string re = ep; re[0] = (char)mx;
for(int i = 1;i < (int)b.size();i ++) bu[i] = b[i];
while(xx) a[++ cn] = xx%10,xx /= 10;
for(int i = cn;i > 0;i --) {
if(a[i] && !ty[a[i]]) ty[a[i]] = ++ c2,re += (char)bu[a[i]];
rs = rs*10+ty[a[i]];
}return {rs,re};
}
void dfs(int x,int mx,int S) {
if(x > n) {
id[S] = ++ cnt;
to[cnt] = S; tp[cnt] = mx;
return ;
}
for(int i = 0;i <= mx+1;i ++) {
dfs(x+1,max(mx,i),S*10+i);
}return ;
}
map<pair<int,string>,int> dp,pd;
int pw[10];
int main() {
freopen("memory.in","r",stdin);
freopen("memory.out","w",stdout);
n = read(); m = read();
int mx = 0;
for(int i = 1;i <= n;i ++) {
for(int j = 1;j <= m;j ++) {
pr[i][j] = read();
mx = max(mx,pr[i][j]);
}
}
if(n > m) {
swap(n,m);
for(int i = 1;i <= n;i ++) {
for(int j = 1;j <= m;j ++) {
a[i][j] = pr[j][i];
}
}
}
else {
for(int i = 1;i <= n;i ++) {
for(int j = 1;j <= m;j ++) {
a[i][j] = pr[i][j];
}
}
}
dfs(0,0,0);
pw[0] = 1;
for(int i = 1;i <= n;i ++) pw[i] = pw[i-1]*10;
for(int i = 1;i < pw[n];i ++) {
id[i] = findd(i);
}
ep[0] = (char)0;
dp[{0,ep}] = 1;
for(int c = 1;c <= m;c ++) {
for(int r = 1;r <= n;r ++) {
pd.clear(); swap(pd,dp);
for(auto i = pd.begin();i != pd.end();i ++) {
int mx = i->FI.SE[0],s = i->FI.FI;
string bu = (i->FI).SE;
int l = (s/pw[r-1])%10,u = (r==1 ? 0:(s/pw[r-2])%10);
pair<int,string> nx = dw(s-l*pw[r-1],bu);
(dp[nx] += i->SE*1ll*(MOD+1-a[r][c])%MOD) %= MOD;
if(a[r][c]) {
if(!l && !u) {
int ma = tp[id[s]]; bu[0] = (char)max(mx,1);
bu += (char)1;
nx = dw(s+(ma+1)*pw[r-1],bu);
(dp[nx] += i->SE*1ll*a[r][c]%MOD) %= MOD;
}
else if(!l || !u || l == u) {
int ii = max(l,u);
bu[ii] = bu[ii]+1; bu[0] = (char)max(mx,(int)bu[ii]);
nx = dw(s-l*pw[r-1]+ii*pw[r-1],bu);
(dp[nx] += i->SE*1ll*a[r][c]%MOD) %= MOD;
}
else {
for(int j = 0;j < n;j ++) {
if((s/pw[j])%10 == u) {
s += (l-u)*pw[j];
}
}
bu[l] += bu[u]+1; bu[0] = (char)max(mx,(int)bu[l]);
nx = dw(s,bu);
(dp[nx] += i->SE*1ll*a[r][c]%MOD) %= MOD;
}
}
}
}
}
int ans = 0;
for(auto i = dp.begin();i != dp.end();i ++) {
(ans += ((int)i->FI.SE[0])*1ll*i->SE%MOD) %= MOD;
}
AIput(ans,'\n');
return 0;
}
后记
该特性的首次发现,便是一只青蛙吞了一只山羊。
“剑和牛奶”:众所周知,牛奶可以针对性地对女巫造成极大伤害(
勇者:“这不就是个癞蛤蟆?”
铁傀儡为什么消失了?这个不清楚,我只知道勇者的背包里多了四块铁锭。