二維莫隊解題報告


我寫的莫隊教程

其實這是一道bzoj上的題(bzoj2639,貌似是權限題,反正我看不了),在YALI做模擬賽的時候遇到了.

然后在網上查到了幾篇關於這道題的博客,都和我的做法略有不同...

題目大意

給你一個 \(r*c\) 的矩陣,每個點有一個顏色, \(m\) 個詢問,每次詢問一個子矩陣內,每種顏色出現次數的平方和。

\(r,c\le 200,m\le 100000\)

做法簡述

首先我們要明白,莫隊究竟在干什么。

莫隊其實就是幾個指針在那跳來跳去,每跳一步都需要一定的時間,通過對詢問排序使得指針跳的總次數盡量小。

所以,這題中詢問為 \((x_1,y_1,x_2,y_2)\) ,也就是四個指針在那跳,分別分塊再排序就可以了,即:

//為避免和cmath庫中的y0y1重名,下文中代碼內的x1,y1,x2,y2都用x,y,xx,yy代替
struct Query
{
	int x,y,xx,yy,id;
	bool operator<(Query& b)
	{
		return x/B==b.x/B?(y/B==b.y/B?(xx/B==b.xx/B?yy<b.yy:xx<b.xx):y<b.y):x<b.x; //B為分塊大小
	}
} q[M];

答案更新

一般的莫隊都是 \(O(1)\) 更新答案的,然而這題是 \(O(n)\) (用 \(n\) 代表 \(r,c\) ) 更新。

移動指針的時候,把一排一起修改。

需要注意的是,8個while的順序如果排列不當在某些情況下會導致答案出錯,所以最好是將所有add都放在del前面(實際上有多種排列順序都可以在不進行“反操作”的情況下保證答案正確,所有add放在del前面只是其中一種),或者是對“反區間”進行“反操作”。

所謂“反區間”,如:修改 \(x_1\) 指針時,本應進行add操作,而此時\(y_1>y_2+1\),那么就要將 \((y_2,y_1)\) 這個開區間內的所有點進行del。

while的排列順序得當可以使“反區間”不可能出現。

“反操作”參考代碼:

while (x<q[i].x)
{
	for (j=y;j<=yy;++j)
	{
		del(a[x][j]);
	}
	for (j=yy+1;j<y;++j)
	{
		add(a[x][j]);
	}
	++x;
}
while (y<q[i].y)
{
	for (j=x;j<=xx;++j)
	{
		del(a[j][y]);
	}
	for (j=xx+1;j<x;++j)
	{
		add(a[j][y]);
	}
	++y;
}
while (xx>q[i].xx)
{
	for (j=y;j<=yy;++j)
	{
		del(a[xx][j]);
	}
	for (j=yy+1;j<y;++j)
	{
		add(a[xx][j]);
	}
	--xx;
}
while (yy>q[i].yy)
{
	for (j=x;j<=xx;++j)
	{
		del(a[j][yy]);
	}
	for (j=xx+1;j<x;++j)
	{
		add(a[j][yy]);
	}
	--yy;
}
while (x>q[i].x)
{
	--x;
	for (j=y;j<=yy;++j)
	{
		add(a[x][j]);
	}
	for (j=yy+1;j<y;++j)
	{
		del(a[x][j]);
	}
}
while (y>q[i].y)
{
	--y;
	for (j=x;j<=xx;++j)
	{
		add(a[j][y]);
	}
	for (j=xx+1;j<x;++j)
	{
		del(a[j][y]);
	}
}
while (xx<q[i].xx)
{
	++xx;
	for (j=y;j<=yy;++j)
	{
		add(a[xx][j]);
	}
	for (j=yy+1;j<y;++j)
	{
		del(a[xx][j]);
	}
}
while (yy<q[i].yy)
{
	++yy;
	for (j=x;j<=xx;++j)
	{
		add(a[j][yy]);
	}
	for (j=xx+1;j<x;++j)
	{
		del(a[j][yy]);
	}
}
out[q[i].id]=ans;

分塊大小

具體計算清楚非常復雜,這里只是估算一下.

\(x_1\) 指針的移動次數為 \(O(mB)\)\(y_2\) 指針的移動次數漸進復雜度中含有 \(O\left(\frac{n^4}{B^3}\right)\),取 \(mB=\frac{n^4}{B^3}\),得到 \(B=nm^{-\frac{1}{4}}\)

總時間復雜度為 \(O(mlogm+n^2m^{\frac{3}{4}})\)

反正這樣的分塊大小實測比 \(\sqrt{n}\) 優秀...有興趣的話可以嚴謹地算一算(如果發現我這個估算有問題可以直接在這篇博客下評論)

初始子矩陣

任意一個空矩陣就可以了,如 \(x_1=y_1=1,x_2=y_2=0\)

參考代碼

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N=210;
const int M=100010;

void add(int x);
void del(int x);

int r,c,m,B,a[N][N],lsh[N*N],tot,cnt[N*N],ans,out[M];

struct Query
{
	int x,y,xx,yy,id;
	bool operator<(Query& b)
	{
		return x/B==b.x/B?(y/B==b.y/B?(xx/B==b.xx/B?yy<b.yy:xx<b.xx):y<b.y):x<b.x;
	}
} q[M];

int main()
{
	int i,j,x=1,y=1,xx=0,yy=0;
	
	cin>>r>>c>>m;
	
	B=pow(r*c,0.5)/pow(m,0.25)+1.0;
	
	for (i=1;i<=r;++i)
	{
		for (j=1;j<=c;++j)
		{
			cin>>a[i][j];
			lsh[tot++]=a[i][j]; //這題要離散化
		}
	}
	
	sort(lsh,lsh+tot);
	tot=unique(lsh,lsh+tot)-lsh;
	
	for (i=1;i<=r;++i)
	{
		for (j=1;j<=c;++j)
		{
			a[i][j]=lower_bound(lsh,lsh+tot,a[i][j])-lsh;
		}
	}
	
	for (i=0;i<m;++i)
	{
		cin>>q[i].x>>q[i].y>>q[i].xx>>q[i].yy;
		q[i].id=i;
	}
	
	sort(q,q+m);
	
	for (i=0;i<m;++i)
	{
		while (x>q[i].x)
		{
			--x;
			for (j=y;j<=yy;++j)
			{
				add(a[x][j]);
			}
		}
		while (xx<q[i].xx)
		{
			++xx;
			for (j=y;j<=yy;++j)
			{
				add(a[xx][j]);
			}
		}
		while (y>q[i].y)
		{
			--y;
			for (j=x;j<=xx;++j)
			{
				add(a[j][y]);
			}
		}
		while (yy<q[i].yy)
		{
			++yy;
			for (j=x;j<=xx;++j)
			{
				add(a[j][yy]);
			}
		}
		while (x<q[i].x)
		{
			for (j=y;j<=yy;++j)
			{
				del(a[x][j]);
			}
			++x;
		}
		while (xx>q[i].xx)
		{
			for (j=y;j<=yy;++j)
			{
				del(a[xx][j]);
			}
			--xx;
		}
		while (y<q[i].y)
		{
			for (j=x;j<=xx;++j)
			{
				del(a[j][y]);
			}
			++y;
		}
		while (yy>q[i].yy)
		{
			for (j=x;j<=xx;++j)
			{
				del(a[j][yy]);
			}
			--yy;
		}
		out[q[i].id]=ans;
	}
	
	for (i=0;i<m;++i)
	{
		cout<<out[i]<<endl;
	}
	
	return 0;
}

void add(int x)
{
	ans=ans+2*cnt[x]+1;
	++cnt[x];
}

void del(int x)
{
	ans=ans-2*cnt[x]+1;
	--cnt[x];
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM