题面解释
给定大小的花园,有一些不能种花的土坑,现给定所有格的状态,求在花园里种出"C"形和"F"形的方案数。
"C"形:
"F"形:
解法
要保证不重不漏,我们枚举每个点为左上角,让该点所在行为"C"形上方的横,它的长度有若干种情况,对于这个点,我们设,表示这个点到其右方最近土坑的距离,则该行的方案数为。递推式:
若为土坑,;
若可种花,。
接着我们考虑"C"形下面一行,方案数为,枚举从行开始每一行,累加方案数,与第行方案数相乘得答案,设:
无法向下延伸时break;
匚,受物之器。 同队大佬亅匚丫
观察到“F”形一竖长度也有若干种情况,考虑“C”形左上角,只需乘以“C”形下方横行方案数按照求的方法累加相乘即可。
设表示这个点到其下方最近土坑的距离,则以作为“C”形左上角向下延伸为“F”形的方案数为。递推式:
若为土坑,;
若可种花,。
设:
所有点的方案数相加即答案。复杂度,考场上能过!
显然的优化:求答案时加的一维枚举可以省去。
观察求和的式子,它们都包含了从当前行的下方第二行到末尾行的方案数。考虑从下方行的答案递推上方行的答案。注意当前行的状态并不一定来自其下方第一行:可能有土坑,也可能它右边第一格为土坑导致无法延伸,该点答案为。
因此维护一个表示能转移到的行数,为表示无法转移,此时用上述方法暴力求算。
从行到行枚举行数:初始,成功转移后表示它可用于转移上方状态。上方行被枚举到时如果为土坑将重置为0,否则。
改写一下前面的递推式:
注意除改为乘逆元(打表打个10KB)
但这个转移式是错误的。当从下方第二行及更远的地方转移时,这行的方案数对于第行是合法的,因此应加入。
在求时我们忽略这一行,但这一行对于上面所有行都是合法的。在累加进答案后,我们将这一行加进来才可以正确转移。
正确的递推式:
最后,
复杂度。贴上优雅的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| #include <bits/stdc++.h> using namespace std; namespace Kochi { template<typename Kopi> inline void read(Kopi& ret) { ret=0; bool f=0; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=1; ch=getchar(); } while(ch>='0'&&ch<='9') { ret=(ret<<3)+(ret<<1)+(ch^48); ch=getchar(); } if(f) ret=-ret; return; } const int maxn=1003,mod=998244353; int rt[maxn][maxn]; int dw[maxn][maxn]; int nxt[maxn][maxn]; long long inv[maxn]; char gd[maxn][maxn]; long long ansc[maxn][maxn]; long long ansf[maxn][maxn]; long long ascc,asff; int T,id; int n,m,c,f; int Ayatsuki() { read(T),read(id); inv[1]=1; for(int i=2;i<=1000;i++) inv[i]=(mod-inv[mod%i]*(mod/i)%mod); while(T--) { read(n),read(m),read(c),read(f); if(c==0&&f==0) { printf("0 0\n"); continue; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { gd[i][j]=getchar(); while(gd[i][j]==' '||gd[i][j]=='\n') gd[i][j]=getchar(); } for(int i=n;i>=1;i--) for(int j=m;j>=1;j--) { if(i==n) dw[i][j]=(gd[i][j]=='0')?1:0; else dw[i][j]=(gd[i][j]=='1')?0:(dw[i+1][j]+1); if(j==m) rt[i][j]=(gd[i][j]=='0')?1:0; else rt[i][j]=(gd[i][j]=='1')?0:(rt[i][j+1]+1); } ascc=asff=0; for(int i=n-2;i>=1;i--) for(int j=1;j<=m-1;j++) { ansc[i][j]=ansf[i][j]=0; if(i==n-2) nxt[i][j]=0; else nxt[i][j]=nxt[i+1][j]; if(rt[i][j]<2||dw[i][j]<3) { if(dw[i][j]==0) nxt[i][j]=0; continue; } if(!nxt[i][j]) { for(int k=i+2;k<=n;k++) { if(gd[k][j]=='1') break; ansc[i][j]=(ansc[i][j]+(rt[i][j]-1)*(rt[k][j]-1)%mod)%mod; ansf[i][j]=(ansf[i][j]+(rt[i][j]-1)*(rt[k][j]-1)%mod*(dw[k][j]-1)%mod)%mod; } nxt[i][j]=i; } else { int k=nxt[i][j]; ansc[i][j]=(ansc[k][j]*inv[rt[k][j]-1]%mod+(k-i>1)*(rt[k][j]-1)%mod)*(rt[i][j]-1)%mod; ansf[i][j]=(ansf[k][j]*inv[rt[k][j]-1]%mod+(k-i>1)*(rt[k][j]-1)*(dw[k][j]-1)%mod)*(rt[i][j]-1)%mod; nxt[i][j]=i; } ascc=(ascc+ansc[i][j]%mod)%mod; asff=(asff+ansf[i][j]%mod)%mod; ansc[i][j]=(ansc[i][j]+(rt[i][j]-1)*(rt[i+1][j]-1)%mod)%mod; ansf[i][j]=(ansf[i][j]+(rt[i][j]-1)*(rt[i+1][j]-1)%mod*(dw[i+1][j]-1)%mod)%mod; } printf("%lld %lld\n",c*ascc,f*asff); } return 0; } } int main() { return Kochi::Ayatsuki(); }
|
如果要更简洁的解法,可以试试枚举左下角,复杂度相同但可能更快。这个交给你们自己实现。
Stickman的小窝 省一爷爷coding_hong