LG13647 [NOISG 2016] Fabric

这里有这道题的 \(O(n^{2.5})\)\(O(n^2)\) 解法,目前包揽 \(luogu\) 最优解和前三劣解。喜欢 \(ynoi\) 系列或者想给自己找点苦吃的人可以看第一个,喜欢数论或者想要轻松通过本题的可以看第二个。


我们先讲 \(O(n^{2.5})\)。实际上,这个做法在常数优秀、块长合理的情况下,可以稳稳通过。

背后的故事:机房某骗分战神用正难则反+珂朵莉树在比赛时 \(A\) 了这道题,但是无法通过 \(luogu\) 数据。

感觉 \(S\ge k\) 的矩形面积没有 \(S<k\) 的矩形面积好算,考虑正难则反,计算矩形数量后转化为后面的问题。

计算矩形数量可以用单调栈简单计算,这里重点讲 \(S<k\) 的矩形计数。

考虑枚举右下角 \((x,y)\),计算一个数组 \(a_i\),表示以当前点为右下角的宽度(行数)为 \(i\) 的矩形中,面积 \(<k\) 的有几个。显然设 \(b_i=\lfloor\dfrac{k-1}{i}\rfloor\),则 \(a_i\le b_i\)。同时,设同一列中 \(\le x\) 的横坐标最大的点的横坐标为 \(x'\),则 \(i\in[1,x-x']\)\(a_i\) 加一,\(i\in(x-x',n]\)\(a_i\) 归零。

所以,我们现在需要一种 \(ds\),可以实现前缀加一,后缀归零,以及在每一次操作后,数组整体对 \(b_i\)\(\min\)。查询求全局和。

发现这个东西是处理困难的,考虑大力分块。

由于枚举顺序是一行一行枚举的,所以 \(a_i\) 最大为 \(n\),因此空间上允许我们对于每个块内维护 \(c_{i,j}\),表示块 \(i\)\(b_k'\) 值为 \(j\) 的数量,初始 \(b_i'=b_i\)。这样我们就可以通过 \(c\) 计算出每次给 \(ans\) 减去的值的增量。

由于有覆盖操作,可以只保留整块 \(+1\),而不保留散块 \(+1\),因此对每个整块维护 \(tag_i\) 表示这个块 \(+1\) 的次数。

覆盖时分散块归零和整块归零。散块归零暴力重构整个 \(c,b'\) 即可,方法是设修改第 \(K\) 块,对于一个位置 \(i\),假如它没有被归零,则 \(b_i'\to b_i'-tag_K\);否则 \(b_i'\to b_i\)

整块归零时,考虑给每个块设 \(vs_i\) 表示有没有进行过散块归零操作。如果有,暴力重构;没有,则将 \(tag\) 归零即可。时间复杂度考虑均摊分析,共有 \(O(n)\) 次散块归零,因此整块归零时暴力重构次数也为 \(O(n)\)

设块长为 \(B\),则单次重构时间复杂度可以做到 \(O(B)\),因此时间复杂度为 \(O(n^2(\dfrac{n}{B}+B))\)。由于暴力重构常数较大,所以当 \(B=20\) 左右时,代码常数最小。

//傻白虎码风改良计划  Day 2
#include<bits/stdc++.h>
using namespace std;
char buf[1<<20],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<typename T>
inline void read(T &x){
    bool f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=(f?x:-x);
}
template<typename T>
inline void write(T x){
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=2005,bl=20,M=105;
int n,m,k,pr[N],nx[N],ls[N],st[N],tp;
int b[N],num[N],tag[M],sm[M],c[M][N],gx[M],vs[M],snm[M],mx,sum;
long long ans;
inline void ret(int x,int cc=0){
	if(!vs[x]&&!cc){
		sum-=gx[x];
		tag[x]=gx[x]=0;
		sm[x]=c[x][0];
		return;
	}
	int rs=x*bl,ls=rs-bl+1;
	rs=min(rs,mx),sum-=gx[x];
	gx[x]=tag[x]=vs[x]=0;
	while(ls<=rs){
		c[x][num[ls]]--;
		c[x][num[ls]=b[ls]]++,ls++;
	}
	if(cc&&rs==mx) c[x][0]++;
	sm[x]=c[x][0];
}
inline void chg(int x){
	for(int i=1;i*bl-bl<x;i++)
		sum+=snm[i]-sm[i],gx[i]+=snm[i]-sm[i],sm[i]+=c[i][++tag[i]];
	int bid=x/bl+1,rs=min(bid*bl,mx);
	sum-=gx[bid],gx[bid]=0;
	for(int i=bid*bl-bl+1;i<=x;i++){
		c[bid][num[i]]--,num[i]=max(num[i]-tag[bid],0);
		c[bid][num[i]]++,gx[bid]+=b[i]-num[i];
	}
	for(int i=rs;i>x;i--) c[bid][num[i]]--,c[bid][num[i]=b[i]]++;
	tag[bid]=0,sum+=gx[bid];
	sm[bid]=c[bid][0],vs[bid]=1;
	for(int i=bid+1;i*bl-bl<mx;i++) ret(i);
}
int main(){
	read(n),read(m),read(k);
	for(int i=1;i<=n;i++,tp=0){
		for(int j=1,cc;j<=m;j++){
			read(cc);
			(cc?pr[j]=0:pr[j]++);
			while(tp&&pr[st[tp]]>pr[j]) nx[st[tp--]]=j;
			ls[j]=st[tp],nx[j]=m+1,st[++tp]=j;
		}
		mx=i;
		for(int j=1;j<=m;j++) ans+=1ll*pr[j]*(j-ls[j])*(nx[j]-j);
		for(int j=1;j<=i;j++) b[i]=min((k-1)/j,2001); 
		for(int j=1;j*bl-bl<i;j++) ret(j,1),snm[j]=min(j*bl,mx)-j*bl+bl;
		for(int j=1;j<=m;j++) chg(pr[j]),ans-=sum;
	}
	write(ans);
	return 0;
}

——傻白虎傻白虎,第一种方法还是太类人了,有没有更人类一点的做法?

有的兄弟有的。考虑我们刚才是如何计算矩形数量的。容易发现,其实我们本质上是给每一行以 \(x-x'\)(定义见第一种解法“考虑枚举右下角”一段后半部分)为权值,建立了一颗小根笛卡尔树,然后矩形数量即为 \(\sum sz_i(x-x_i')\)

设一个数左边、右边第一个权值小于它的位置是 \(ls_i,nx_i\)\(g(n,m)\) 表示在一个高为 \(n\),宽为 \(m\) 的矩形中,有多少个以大矩形第一行为第一行的子矩形,满足面积 \(\ge k\),则有:

\[g(n,m)=\sum_{i=\lfloor\frac{k-1}n\rfloor}^m(m-i+1)(n-\lfloor\frac{k-1}{i}\rfloor) \]

\[=(m+1)\sum_{i=\lfloor\frac{k-1}n\rfloor}^m(n-\lfloor\frac{k-1}{i}\rfloor)-\sum_{i=\lfloor\frac{k-1}n\rfloor}^mi(n-\lfloor\frac{k-1}{i}\rfloor) \]

显然可以设 \(a(n,m)=\sum_{i=1}^mn-\lfloor\frac{k-1}i\rfloor,b(n,m)=\sum_{i=1}^mi(n-\lfloor\frac{k-1}i\rfloor)\),上式就可以写成 \(O(1)\) 的差分形式。容易发现 \(a(n,m-1)\to a(n,m),b(n,m-1)\to b(n,m)\) 的转移都是 \(O(1)\) 的,所以可以 \(O(n^2)\) 预处理,\(O(n^2)\) 计算。时间复杂度 \(O(n^2)\)

//傻白虎码风改良计划  Day 2
#include<bits/stdc++.h>
using namespace std;
char buf[1<<20],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
template<typename T>
inline void read(T &x){
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
template<typename T>
inline void write(T x){
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=2005;
int n,m,k,pr[N],nx[N],ls[N],st[N],tp,a[N][N],b[N][N];
long long ans;
inline int g(int n,int m){
	if(!n) return 0;
	int mn=(k-1)/n;
    if(mn>=m) return 0;
	return (m+1)*(a[n][m]-a[n][mn])-b[n][m]+b[n][mn];
}int main(){
	freopen("soul.in","r",stdin);
	freopen("soul.out","w",stdout);
	read(n),read(m),read(k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			a[i][j]=a[i][j-1]+i-(k-1)/j;
			b[i][j]=b[i][j-1]+j*(i-(k-1)/j);
		}
	for(int i=1;i<=n;i++,tp=0){
		for(int j=1,cc;j<=m;j++){
            read(cc);
			(cc?pr[j]=0:pr[j]++);
			while(tp&&pr[st[tp]]>pr[j]) nx[st[tp--]]=j-1;
			ls[j]=st[tp]+1,nx[j]=m,st[++tp]=j;
		}
		for(int j=1;j<=m;j++)
			ans+=g(pr[j],nx[j]-ls[j]+1)-g(pr[j],nx[j]-j)-g(pr[j],j-ls[j]);
	}
    write(ans);
	return 0;
}
posted @ 2025-11-26 19:49  white_tiger  阅读(22)  评论(0)    收藏  举报