坐标离散化,imos

只是为了收藏一个很好的思路,算法。。。。

#include<alogrithm>
#include<vector>
#include<queue>
#include<iostream>
#include<cstring>
#define MAX_N 1000+16
using namespace std;
int N,H,W;
int x1[MAX_N],x2[MAX_N];,y1[MAX_N],y2[MAX_N];
int fid[2*MAX_N][2*MAX_N];
int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1};
int compress(int *x1,int *x2,int w)
{
    vector<int>xs;
	for(i=0;i<N;i++)
	{
		int tx1=x1[i],tx2=x2[i];
		if(1<=tx1&&tx1<W)xs.push_back(tx1);
		if(1<=tx2&&tx2<W)xs.push_back(tx2);
	}
	xs.push_back(0);
	xs.push_back(W);
	sort(xs.begin(),xs.end());
	xs.erase(unique(xs.begin(),xs.end()),xs.end());
	for(i=0;i<N;i++)
	{
		x1[i]=find(xs.begin(),xs.end(),x1[i])-xs.begin();
		x2[i]=find(xs.begin(),xs.end(),x2[i])=xs.begin();
	}
	return xs.size()-1;
}
int bfs()
{
	int ans=0;
	for(int i=0;i<H;i++)
	{
        for(int j=0;j<W;j++)
		{
			if(fid[i][j])
				continue;
			ans++;
			queue<pair<int,int>>que;
			que.push(make_pair(j,i));
			while(!que.empty())
			{
				int nx=que.front().first,ny=que.front().second;
				que,pop();
				for(int i=0;i<4;i++)
				{
					int tx=nx+d[i],ty=ny+dy[i];
					if(tx<0||W<tx||ty<0||H<ty||fid[ty][tx]>0)
						continue;
					que.push(make_pair(tx,ty));
					fid[tx][ty]=1;
				}
			}
		}
	}
	return ans;
}
int main()
{
	while(scanf("%d%d",&W,&H)&&W&&H)
	{
         scanf("%d",&N);
		 for(i=0;i<N;i++)
		 {
			scanf("%d%d%d%d",&x1[i],x2[i],y1[i],y2[i]);
		 }
		 memset(fid,0,sizeof(fid));
		 W=compress(x1,x2,W);
		 H=compress(y1,y2,H);
		 for(i=0;i<N;i++)
		 {
			 fid[y1[i]][x1[i]]++;
			 fid[y1[i]][x2[i]]--;
			 fid[y2[i]][x1[i]]++;
			 fid[y2[i]][x2[i]]--;
		 }
		 for(int i=0;i<H;i++)
			 for(int j=1;j<W;j++)
				 fid[i][j]+=fid[i][j-1];
			for(int i=1;i<H;i++)
				for(j=0;j<W;j++)
					fid[i][j]+=fid[i-1][j];
			printf("%d\n",bfs());

	}
}


以下是别人的帖子

図 1: 1 次元上へ 0 次関数を加算するイメージ

for (int i = 0; i < T; i++) table[i] = 0;


  
  
  1. for (int i = 0; i < C; i++) {
  2. // 時刻 S[i] から E[i] - 1 までのそれぞれについてカウントを 1 増やす
  3. for (int j = S[i]; j < E[i]; j++) {
  4. table[j]++;
  5. }
  6. }
  7. // 最大値を調べる
  8. M = 0;
  9. for (int i = 0; i < T; i++) {
  10. if (M < table[i]) M = table[i];
  11. }
いもす法による解法
いもす法では,出入り口でのカウントのみをするという発想で入店時に +1 を,出店時に -1 を記録しておき,答えを求める直前に全体をシミュレートします.記録には O(C) が,シミュレートには O(T) がかかるので,全体としての計算量は O(C+T) となります.

  
  
  1. for (int i = 0; i < T; i++) table[i] = 0;
  2. for (int i = 0; i < C; i++) {
  3. table[S[i]]++; // 入店処理: カウントを 1 増やす
  4. table[E[i]]--; // 出店処理: カウントを 1 減らす
  5. }
  6. // シミュレート
  7. for (int i = 0; i < T; i++) {
  8. if (0 < i) table[i] += table[i - 1];
  9. }
  10. // 最大値を調べる
  11. M = 0;
  12. for (int i = 0; i < T; i++) {
  13. if (M < table[i]) M = table[i];
  14. }

次元の拡張

いもす法の 1 番の特徴はナイーブな方法と比較して,次元の上昇に強いという点です.次元の呪いからは逃れられないので高次元に適用することは難しいですが,問題設定として 2 次元や 3 次元が用いられることは非常に多く,それらの状況においていもす法は有用です.

問題例

あなたは様々な種類のモンスターを捕まえるゲームをしています.今あなたがいるのは W×H のタイルからなる草むらです.この草むらでは N 種類のモンスターが現れます.モンスター iAix<BiCiy<Di の範囲にしか現れません.このゲームを効率的に進めるため,1 つのタイル上で現れるモンスターの種類の最大値が知りたいです.(ただし, W×H は計算可能な程度の大きさとし, N は大きくなりうるものとします.)
図 2: モンスターが現れる矩形(赤色)と,
各タイルで現れるモンスターの種類の数
ナイーブな解法
ナイーブな解法としては,それぞれのモンスターについて現れる矩形の範囲に含まれるすべてのタイルについて 1 を足す方法が挙げられます.しかし,これの計算量は O(NWH) です.

  
  
  1. for (int y = 0; y < H; y++) {
  2. for (int x = 0; x < W; x++) {
  3. tiles[y][x] = 0;
  4. }
  5. }
  6. for (int i = 0; i < N; i++) {
  7. // モンスター i が現れる [(A[i],C[i]), (B[i],D[i])) の範囲に 1 を足す
  8. for (int y = C[i]; y < D[i]; y++) {
  9. for (int x = A[i]; x < B[i]; x++) {
  10. tiles[y][x]++;
  11. }
  12. }
  13. }
  14. // 最大値を調べる
  15. int tile_max = 0;
  16. for (int y = 0; y < H; y++) {
  17. for (int x = 0; x < W; x++) {
  18. if (tile_max < tiles[y][x]) {
  19. tile_max = tiles[y][x];
  20. }
  21. }
  22. }
いもす法による解法
いもす法では,矩形の左上 (A[i],C[i])+1 を,右上 (A[i],D[i])−1 を,左下 (B[i],C[i])−1 を,右下 (B[i],D[i])+1 を加算し,答えを求める直前に累積和を求めます.記録には O(N) が,累積和の計算には O(WH) がかかるので,全体としての計算量は O(N+WH) となります.

  
  
  1. for (int y = 0; y < H; y++) {
  2. for (int x = 0; x < W; x++) {
  3. tiles[y][x] = 0;
  4. }
  5. }
  6. // 重みの加算 (図 3)
  7. for (int i = 0; i < N; i++) {
  8. tiles[C[i]][A[i]]++;
  9. tiles[C[i]][B[i]]--;
  10. tiles[D[i]][A[i]]--;
  11. tiles[D[i]][B[i]]++;
  12. }
  13. // 横方向への累積和 (図 4, 5)
  14. for (int y = 0; y < H; y++) {
  15. for (int x = 1; x < W; x++) {
  16. tiles[y][x] += tiles[y][x - 1];
  17. }
  18. }
  19. // 縦方向の累積和 (図 6, 7)
  20. for (int y = 1; y < H; y++) {
  21. for (int x = 0; x < W; x++) {
  22. tiles[y][x] += tiles[y - 1][x];
  23. }
  24. }
  25. // 最大値を調べる
  26. int tile_max = 0;
  27. for (int y = 0; y < H; y++) {
  28. for (int x = 0; x < W; x++) {
  29. if (tile_max < tiles[y][x]) {
  30. tile_max = tiles[y][x];
  31. }
  32. }
  33. }
図 3: 重みの加算の結果
図 4: 横方向への累積
図 5: 横方向への累積の結果
図 6: 縦方向への累積
図 7: 縦方向への累積の結果

特殊な座標系への拡張

いもす法は三角形や六角形の座標系にも適用できます.どこに重みを加算して,どのような方向に累積するのかは,目標となる形に対してできる限り数が少なくなるような方向に様々な方向に差分を取って見つけます.
図 8: 三角形の座標系

差分を取る手法

図 9 は三角形の座標系において埋めるべき重みを表しています.緑の線は間が省略されていることを示します(一辺の長さが 4 である三角形の場合には線を無視してもこれらの図では同様の結果になります).いもす法でこのような重み付けを行うため,重みのあるセルの数が十分に少なくなるまで差分をとって,重みを付けるべきセルの配置と累積和をとる方向を知る必要があります. 図 9 〜 12 は差分をとる過程を表しています.これらの間に,「左上から右下への差分」「右上から左下への差分」「左から右への差分」をとったので,いもす法で三角形の累積和を求める場合は,図 12 のように数値を配置し逆の動作「左から右への累積和」「右上から左下への累積和」「左上から右下への累積和」を行います.
図 9: 埋めるべき重み
図 10: 図 9 の左上から右下への差分
図 11: 図 10 の右上から左下への差分
図 12: 図 11 の左から右への差分
同様の方法は三角形だけではなく,六角形の場合や,つける重みが定数ではなく傾き(ただし,多項式に限る)を持っている場合にも適用できます.

次数の拡張

競技プログラミングで出題される問題は高くとも 1 次までだとは思いますが,いもす法は高次の区分多項式関数も適用することができます.本来多項式で良い近似が得られないと思われている関数が区分多項式で表せられることがあり,それを利用すると連続ウェーブレット変換の高速化が可能になります.

2 次関数の差分

試しに 1 次元上で 2 次関数のいもす法を行なってみましょう.
0123456789
00014916253649
↓差分   ↑累積和
0123456789
000135791113
↓差分   ↑累積和
0123456789
0001222222
↓差分   ↑累積和
0123456789
0001100000
図 13: 2 次関数 (x2)+2 の差分
図 13 より,2 次関数 (xa)+2 を加算するとき,テーブルの a+1 および a+2 に 1 を加算し,最後に 3 度累積和をとることにより 2 次関数を用いた,いもす法が適用できることがわかります(ただし, (・)+=max(0,・) とする).同様の方法で,より高次の関数であっても,いもす法が適用可能です.

ガウス関数といもす法

次数の拡張の例としてガウス関数を見てみましょう.いもす法を適用するため,式 1 の左辺に表されるようなガウス関数を,右辺のような区分多項式で近似します.この近似関数は図 14 に示されるように非常によく近似されています.
式 1: ガウス関数(左辺)とその近似(右辺)
図 14: ガウス関数(青)と多項式近似(赤)
-12-11-10-9-8-7-6-5-4-3-2-1012
0033000000-11-11000
↓累積和   ↑差分
-12-11-10-9-8-7-6-5-4-3-2-1012
0036666666-5-16-16-16-16
↓累積和   ↑差分
-12-11-10-9-8-7-6-5-4-3-2-1012
003915212733394540248-8-24
↓累積和   ↑差分
-12-11-10-9-8-7-6-5-4-3-2-1012
00312274875108147192232256264256232
図 15: いもす法によって近似されたガウス関数
-12-11-10-9-8-7-6-5-4-3-2-1012
1
.49
3
.42
7
.28
14
.41
26
.57
45
.58
72
.76
108
.08
149
.41
192
.20
230
.09
256
.31
265
.70
256
.31
230
.09
図 16: 近似目標となるガウス関数
さらに, d 次元のガウス関数は 1 次元のガウス関数の積で表すことができるので, d 次元のいもす法が適用できます.またガウス関数は,中心からのユークリッド距離で重みが決まる(2 次元上では円形,3 次元上では球形である)ため,回転不変な値をとりたい時などに非常に有用です. これについてより詳しく知りたい場合は, ICPR 2012 での発表資料をご参照ください.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值