ABC420F 题解

· · 题解

好像就我用了这个奇怪的 O(nm\alpha(n) + m \log m) 做法,场上把两处 n,m 写反没调出来。

前置知识

并查集,树状数组,单调队列。

Solution

考虑枚举矩形的最左侧的那一列,统计对应的合法矩形个数,最后相加即可。

l_{i,j} 表示从格子 (i,j) 开始,向右延申最多多少格子,不会碰到障碍,格子 (i,j) 也计入统计。

那么,假设枚举到的是 (x,c),(x+1,c),\cdots,(y,c),那么,假如不考虑矩形面积小于等于 k 的限制,那么对应的合法矩形个数将是 \min\limits_{i=x}^y l_{i,c}

也就是说,在不考虑面积限制的情况下,一种枚举方案对应的矩形合法方案数仅取决于其 l 值最小的格子

那么,考虑枚举这个 l 值最小的格子,计算出有多少种合法的枚举左侧第一列的方案,使得这个格子是 l 值最小的,那么假设不考虑面积限制,就可以直接得出答案了。

此处实现有一些简单的技巧:若同一列多个格子的 l 值一致,那么行数更小的格子视作 l 值更小;可以使用单调队列均摊 O(1) 求出每个格子的同一列最近的 l 值更小的格子。

现在考虑面积小于等于 k 的限制。

假设枚举了 (x,y) 作为 l 值最小的格子,令其 l 值为 p,且 (l-1,y),(r+1,y) 分别是 y 列中,与 (x,y) 最近的行数更小且 l 值更小的格子,与 (x,y) 最近的行数更大且 l 值更小的格子。

那么,原本没有面积限制的情况下,任意选择 l \le a \le b \le r,选中 y 列的 ab 行的格子都是一种合理的枚举方案。

但是若 \lfloor \frac{b-a+1}{k}\rfloor \lt p,那么实际上枚举 (a,y),(a+1,y),\cdots,(b,y) 对应的合法矩形方案数仅会受到 b-a+1 的值的约束,而不是当前 (x,y)lp 的约束。

考虑先只统计 \lfloor \frac{b-a+1}{k}\rfloor \ge p,也就是 b-a+1 \le \lfloor\frac{p}{k}\rfloor 的情况。

::::info[现在需要求解一个这样的问题] 有 a+b-1 个从左到右排列的格子,一个非负整数 lim

称从左边开始第 a 个格子为中心格子。

那么现在需要选择不超过 lim 个连续格子使得包含中心格子,问有多少种方案。
::::
:::::info[解决方案] 令 x 表示从中心格子开始向左边数,一共有多少个格子被选中了。。

现在,需要对于 \forall x \in [1,a],计算出对应的合法的右端点的个数。

可能存在一部分 x,其右端点的可选集合必然是 [a,a+b-1],要满足这个,则 x+b-1 \le lim,即 x \le lim-b+1

那么这一部分 x 的总贡献就是 \max(0,lim-b+1)\cdot b

对于 x \gt lim-b+1,其贡献将会是 \max(0,lim-x+1)

这一部分的最小 x 将会是 \max(0,lim-b+1)+1,最大的 x 将会是 \min(lim,a),分别求出贡献值,然后这一部分 x 的贡献的累加形式是一个等差数列,刚才求解了最大值最小值,并且公差为 1,直接算总和就行了。

时间复杂度 O(1)
:::::

最后,需要统计有多少种选择方案 (a,y),(a+1,y),\cdots,(b,y) 满足 \lfloor\frac{k}{b-a+1}\rfloor \lt \min\limits_{i=a}^b l_{i,y},每种此类方案都会再贡献 \lfloor\frac{k}{b-a+1}\rfloor 的答案。

考虑从小到大枚举 b-a+1 的值,计算有多少选择同一列连续 b-a+1 行的方案,使得不覆盖任意一个 l 值小于等于 b-a+1 的点。

随着 b-a+1 的增大,原来能覆盖的点不会变得不能覆盖,所以可以每次暴力更新能覆盖的点。

然后,对于一列中长度为 len 的极大的连续可覆盖格子,其贡献的选择方案数是 len-\lfloor\frac{k}{b-a+1}\rfloor+1

那么,考虑定义两个数组 sum,cnt,每个 len 都会导致 sum_{len} \leftarrow sum_{len}+len,cnt_{len}\leftarrow cnt_{len}+1

那么,当前 b-a+1 对应的选择方案数就是:

这个选择方案数乘上 $\lfloor\frac{k}{b-a+1}\rfloor$ 就是这个 $b-a+1$ 对应的答案总贡献了。 $sum,cnt$ 都可以用树状数组维护,同一列的极大连续可覆盖格子则可以用并查集维护。 ```cpp #include <bits/stdc++.h> using namespace std; #define FOR(i,a,b) for(auto i=(a);i<=(b);i++) #define REP(i,a,b) for(auto i=(a);i>=(b);i--) #define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k)) #define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k)) #define pb push_back #define mkpr make_pair typedef long long ll; typedef pair<int,int> pii; typedef pair<ll,ll> pll; typedef vector<int> vi; template<class T> void ckmx(T& a,T b){ a=max(a,b); } template<class T> void ckmn(T& a,T b){ a=min(a,b); } template<class T> T gcd(T a,T b){ return !b?a:gcd(b,a%b); } template<class T> T lcm(T a,T b){ return a/gcd(a,b)*b; } #define gc getchar() #define eb emplace_back #define pc putchar #define ep empty() #define fi first #define se second #define pln pc('\n'); #define islower(ch) (ch>='a'&&ch<='z') #define isupper(ch) (ch>='A'&&ch<='Z') #define isalpha(ch) (islower(ch)||isupper(ch)) template<class T> void wrint(T x){ if(x<0){ x=-x; pc('-'); } if(x>=10){ wrint(x/10); } pc(x%10^48); } template<class T> void wrintln(T x){ wrint(x); pln } template<class T> void read(T& x){ x=0; int f=1; char ch=gc; while(!isdigit(ch)){ if(ch=='-')f=-1; ch=gc; } while(isdigit(ch)){ x=(x<<1)+(x<<3)+(ch^48); ch=gc; } x*=f; } void ioopti(){ ios::sync_with_stdio(0); cin.tie(0); } const int maxn=5e6+5; struct UNIONFIND{ int fa[maxn],siz[maxn]; void init(){ FOR(i,1,maxn-1)fa[i]=i,siz[i]=0; } int get(int x){ return x==fa[x]?x:fa[x]=get(fa[x]); } void ms(int a,int b){ if(get(a)!=get(b)){ siz[get(b)]+=siz[get(a)]; fa[get(a)]=get(b); } } }uf; struct BIT{ ll sum[500005]; int cnt[500005]; int lowbit(int x){ return x&-x; } void add(int pos,int _v){ for(;pos<=500004;pos+=lowbit(pos)){ sum[pos]+=_v; cnt[pos]++; } } void del(int pos,int _v){ for(;pos<=500004;pos+=lowbit(pos)){ sum[pos]-=_v; cnt[pos]--; } } ll _qsum(int pos){ ll res=0; for(;pos;pos-=lowbit(pos)){ res+=sum[pos]; } return res; } ll qsum(int l,int r){ return _qsum(r)-_qsum(l-1); } int _qcnt(int pos){ int res=0; for(;pos;pos-=lowbit(pos)){ res+=cnt[pos]; } return res; } int qcnt(int l,int r){ return _qcnt(r)-_qcnt(l-1); } }tree; ll gas(ll s,ll t){ return (s+t)*(t-s+1)/2; } ll calc(ll lflen,ll rglen,ll lim){ // 包括中点的左半部分 =lflen,包括中点的右半部分=rglen // lim:长度上限,要求包括中点 // rglen+lfuse-1 <= lim // lfuse <= lim-rglen+1 ll lfuse=lim-rglen+1;// 左边使用了 lfuse 个或以下时,右边可以填满 ckmn(lfuse,lflen); ll ans=0; if(lfuse>0){ ans+=rglen*lfuse; } ckmx(lfuse,0ll); // 左边使用 lfuse 以上的时候,将会限制长度为 lim ll lfmx=min(lflen,lim);// 左边最多选择多长 // 左边选择 lfuse+1 的话,有 lim-(lfuse+1) if(lim-lfmx+1<=lim-(lfuse+1)+1){ ans+=gas(lim-lfmx+1,lim-(lfuse+1)+1); } return ans; } vector<pii> ids[500005]; void solve(int id_of_test){ int n,m,k; read(n); read(m); read(k); auto id=[&](int i,int j)->int{ return (i-1)*m+j; }; char s[n+5][m+5]; FOR(i,1,n){ scanf("%s",s[i]+1); } int rg[n+5][m+5]; FOR(i,1,n){ rg[i][m+1]=0; REP(j,m,1){ if(s[i][j]=='.'){ rg[i][j]=rg[i][j+1]+1; }else{ rg[i][j]=0; } } } ll ans=0; FOR(col,1,m){ int lef[n+5],rig[n+5]; stack<int> stk; FOR(row,1,n){ while(!stk.empty()&&rg[row][col]<rg[stk.top()][col]){ stk.pop(); } if(stk.empty())lef[row]=1; else lef[row]=stk.top()+1; stk.push(row); } while(!stk.empty())stk.pop(); REP(row,n,1){ while(!stk.empty()&&rg[row][col]<=rg[stk.top()][col]){ stk.pop(); } if(stk.empty())rig[row]=n; else rig[row]=stk.top()-1; stk.push(row); } FOR(row,1,n){ if(rg[row][col]==0)continue; int lenlim=k/rg[row][col];// k/lenlim = rg[row][col],将会是最高限制 ans+=calc(row-lef[row]+1,rig[row]-row+1,lenlim)*rg[row][col]; // printf("(%d,%d): [%d,%d] lenlim %d calc %d\n",row,col,lef[row],rig[row],k/rg[row][col],calc(row-lef[row]+1,rig[row]-row+1,lenlim)); } } FOR(i,1,n){ FOR(j,1,m){ if(rg[i][j])ids[rg[i][j]].eb(mkpr(i,j)); } } // printf("preans %lld\n",ans); FOR(len,1,n){ { if(k/len==0)break; int up,dw; if(len==1)up=m; else up=k/(len-1); dw=k/len+1; ckmn(up,m); // printf("len %d add [%d,%d]\n",len,dw,up); // 所有 rg 小于等于 k/len 的格子都不能放, // 所有大于当前 m/len 限制的,全部都可以放进去了 REP(i,up,dw){ for(auto [x,y]:ids[i]){ if(x>=2){ if(uf.siz[uf.get(id(x-1,y))]){ tree.del(uf.siz[uf.get(id(x-1,y))],uf.siz[uf.get(id(x-1,y))]); } } if(x<n){ if(uf.siz[uf.get(id(x+1,y))]){ tree.del(uf.siz[uf.get(id(x+1,y))],uf.siz[uf.get(id(x+1,y))]); } } if(x>=2&&uf.siz[uf.get(id(x-1,y))]){ if(uf.siz[uf.get(id(x-1,y))]){ uf.ms(uf.get(id(x,y)),uf.get(id(x-1,y))); } } if(x<n&&uf.siz[uf.get(id(x+1,y))]){ if(uf.siz[uf.get(id(x+1,y))]){ uf.ms(uf.get(id(x,y)),uf.get(id(x+1,y))); } } uf.siz[uf.get(id(x,y))]++; tree.add(uf.siz[uf.get(id(x,y))],uf.siz[uf.get(id(x,y))]); } } ans+=(tree.qsum(len,n)-1ll*tree.qcnt(len,n)*(len-1))*(k/len); } } printf("%lld\n",ans); } int main() { uf.init(); int T; T=1; FOR(_,1,T){ solve(_); } return 0; } /* 1. 对题意的理解能否和样例对的上? 2. 每一步操作,能否和自己的想法对应上? 3. 每一步操作的正确性是否有保证? 4. 是否考虑到了所有的 case?特别是极限数据。 5. 变量的数据类型是否与其值域匹配? 6. 时间复杂度有保证吗? 7. 空间多少 MB? */ ```