只是为了收藏一个很好的思路,算法。。。。
#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;
- for (int i = 0; i < C; i++) {
- // 時刻 S[i] から E[i] - 1 までのそれぞれについてカウントを 1 増やす
- for (int j = S[i]; j < E[i]; j++) {
- table[j]++;
- }
- }
- // 最大値を調べる
- M = 0;
- for (int i = 0; i < T; i++) {
- if (M < table[i]) M = table[i];
- }
いもす法による解法
いもす法では,出入り口でのカウントのみをするという発想で入店時に +1 を,出店時に -1 を記録しておき,答えを求める直前に全体をシミュレートします.記録には O(C) が,シミュレートには O(T) がかかるので,全体としての計算量は O(C+T) となります.
- for (int i = 0; i < T; i++) table[i] = 0;
- for (int i = 0; i < C; i++) {
- table[S[i]]++; // 入店処理: カウントを 1 増やす
- table[E[i]]--; // 出店処理: カウントを 1 減らす
- }
- // シミュレート
- for (int i = 0; i < T; i++) {
- if (0 < i) table[i] += table[i - 1];
- }
- // 最大値を調べる
- M = 0;
- for (int i = 0; i < T; i++) {
- if (M < table[i]) M = table[i];
- }
次元の拡張
いもす法の 1 番の特徴はナイーブな方法と比較して,次元の上昇に強いという点です.次元の呪いからは逃れられないので高次元に適用することは難しいですが,問題設定として 2 次元や 3 次元が用いられることは非常に多く,それらの状況においていもす法は有用です.問題例
あなたは様々な種類のモンスターを捕まえるゲームをしています.今あなたがいるのは W×H のタイルからなる草むらです.この草むらでは N 種類のモンスターが現れます.モンスター i は Ai≦x<Bi, Ci≦y<Di の範囲にしか現れません.このゲームを効率的に進めるため,1 つのタイル上で現れるモンスターの種類の最大値が知りたいです.(ただし, W×H は計算可能な程度の大きさとし, N は大きくなりうるものとします.)
図 2: モンスターが現れる矩形(赤色)と,
各タイルで現れるモンスターの種類の数
各タイルで現れるモンスターの種類の数
ナイーブな解法
ナイーブな解法としては,それぞれのモンスターについて現れる矩形の範囲に含まれるすべてのタイルについて 1 を足す方法が挙げられます.しかし,これの計算量は O(NWH) です.
- for (int y = 0; y < H; y++) {
- for (int x = 0; x < W; x++) {
- tiles[y][x] = 0;
- }
- }
- for (int i = 0; i < N; i++) {
- // モンスター i が現れる [(A[i],C[i]), (B[i],D[i])) の範囲に 1 を足す
- for (int y = C[i]; y < D[i]; y++) {
- for (int x = A[i]; x < B[i]; x++) {
- tiles[y][x]++;
- }
- }
- }
- // 最大値を調べる
- int tile_max = 0;
- for (int y = 0; y < H; y++) {
- for (int x = 0; x < W; x++) {
- if (tile_max < tiles[y][x]) {
- tile_max = tiles[y][x];
- }
- }
- }
いもす法による解法
いもす法では,矩形の左上 (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) となります.
- for (int y = 0; y < H; y++) {
- for (int x = 0; x < W; x++) {
- tiles[y][x] = 0;
- }
- }
- // 重みの加算 (図 3)
- for (int i = 0; i < N; i++) {
- tiles[C[i]][A[i]]++;
- tiles[C[i]][B[i]]--;
- tiles[D[i]][A[i]]--;
- tiles[D[i]][B[i]]++;
- }
- // 横方向への累積和 (図 4, 5)
- for (int y = 0; y < H; y++) {
- for (int x = 1; x < W; x++) {
- tiles[y][x] += tiles[y][x - 1];
- }
- }
- // 縦方向の累積和 (図 6, 7)
- for (int y = 1; y < H; y++) {
- for (int x = 0; x < W; x++) {
- tiles[y][x] += tiles[y - 1][x];
- }
- }
- // 最大値を調べる
- int tile_max = 0;
- for (int y = 0; y < H; y++) {
- for (int x = 0; x < W; x++) {
- if (tile_max < tiles[y][x]) {
- tile_max = tiles[y][x];
- }
- }
- }
図 3: 重みの加算の結果
図 4: 横方向への累積
図 5: 横方向への累積の結果
図 6: 縦方向への累積
図 7: 縦方向への累積の結果
特殊な座標系への拡張
いもす法は三角形や六角形の座標系にも適用できます.どこに重みを加算して,どのような方向に累積するのかは,目標となる形に対してできる限り数が少なくなるような方向に様々な方向に差分を取って見つけます.
図 8: 三角形の座標系
差分を取る手法
図 9 は三角形の座標系において埋めるべき重みを表しています.緑の線は間が省略されていることを示します(一辺の長さが 4 である三角形の場合には線を無視してもこれらの図では同様の結果になります).いもす法でこのような重み付けを行うため,重みのあるセルの数が十分に少なくなるまで差分をとって,重みを付けるべきセルの配置と累積和をとる方向を知る必要があります. 図 9 〜 12 は差分をとる過程を表しています.これらの間に,「左上から右下への差分」「右上から左下への差分」「左から右への差分」をとったので,いもす法で三角形の累積和を求める場合は,図 12 のように数値を配置し逆の動作「左から右への累積和」「右上から左下への累積和」「左上から右下への累積和」を行います.
図 9: 埋めるべき重み
図 10: 図 9 の左上から右下への差分
図 11: 図 10 の右上から左下への差分
図 12: 図 11 の左から右への差分
次数の拡張
競技プログラミングで出題される問題は高くとも 1 次までだとは思いますが,いもす法は高次の区分多項式関数も適用することができます.本来多項式で良い近似が得られないと思われている関数が区分多項式で表せられることがあり,それを利用すると連続ウェーブレット変換の高速化が可能になります.2 次関数の差分
試しに 1 次元上で 2 次関数のいもす法を行なってみましょう.0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 0 | 0 | 1 | 4 | 9 | 16 | 25 | 36 | 49 |
↓差分 ↑累積和
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 0 | 0 | 1 | 3 | 5 | 7 | 9 | 11 | 13 |
↓差分 ↑累積和
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 0 | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 |
↓差分 ↑累積和
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
図 13: 2 次関数
(x−2)+2 の差分
ガウス関数といもす法
次数の拡張の例としてガウス関数を見てみましょう.いもす法を適用するため,式 1 の左辺に表されるようなガウス関数を,右辺のような区分多項式で近似します.この近似関数は図 14 に示されるように非常によく近似されています.
式 1: ガウス関数(左辺)とその近似(右辺)
図 14: ガウス関数(青)と多項式近似(赤)
-12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 |
0 | 0 | 3 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | -11 | -11 | 0 | 0 | 0 |
↓累積和 ↑差分
-12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 |
0 | 0 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | -5 | -16 | -16 | -16 | -16 |
↓累積和 ↑差分
-12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 |
0 | 0 | 3 | 9 | 15 | 21 | 27 | 33 | 39 | 45 | 40 | 24 | 8 | -8 | -24 |
↓累積和 ↑差分
-12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 |
0 | 0 | 3 | 12 | 27 | 48 | 75 | 108 | 147 | 192 | 232 | 256 | 264 | 256 | 232 |
図 15: いもす法によって近似されたガウス関数
-12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 |
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: 近似目標となるガウス関数