kmjp's blog

競技プログラミング参加記です

yukicoder : No.1874 Minimum of Sum of Rectangles

やりたいことがわかっても、若干実装が面倒な問題。
https://yukicoder.me/problems/no/1874

問題

2次元座標中にN個の点P[i]が与えられる。
f(u,v)は、2次元座標上で2点u,vを角に持ち、軸に平行な矩形の面積とする。
任意の座標Qを選択できるとき、sum(f(P[i],Q))の最小値を求めよ。

解法

QのX座標・Y座標は、P[i]のいずれかと一致する箇所だけ考えればよい。
X座標を平面走査しながら、その各X座標において最適なY座標を考える。

今X座標をaとすると、求める値はsum(|Px[i]-a|*|Py[i]-b|)が最小となるbである。
これはPy[i]がb以下であるiにおける|Px[i]-a|の総和と、b以上である|Px[i]-a|の総和が近ければ近いほど良い。
(よくある1次元座標における中央値を取る問題に、重み|Px[i]-a|がついたものと考えるとよい)

これには、あるY座標の区間において、そこに含まれる|Px[i]-a|の総和を高速に計算できるとよい。
そこで、点更新で区間和を取れるSegTreeであって、Y座標をキーとして以下の8つの値を持つものを考えるとよい。

  • X座標がa未満の点の個数
  • X座標がa未満の点のX座標の総和
  • X座標がa未満の点のY座標の総和
  • X座標がa未満の点のX座標*Y座標の総和
  • 上記4つで「X座標がa未満の点」を「X座標がa以上の点」に置き換えたもの

これらは平面走査してN個の点を横断するたびに対応するキーを更新すればよい。

上記区間和が取れると、点の個数とX座標の総和を用いることで|Px[i]-a|の総和が取れるので、Y座標を二分探索すれば、bを特定できる。
a,bが決まれば、sum(f(P[i],Q))は上記8つの値から計算できるので、X座標を走査しながら最小値を取ろう。

template<class V,int NV> class SegTree_1 {
public:
	vector<V> val;
	V comp(V l,V r){
		int i;
		FOR(i,8) l[i]+=r[i];
		return l;
	}
	
	SegTree_1(){
		val.resize(NV*2);
	};
	V getval(int x,int y,int l=0,int r=NV,int k=1) { // x<=i<y
		if(r<=x || y<=l) return V();
		if(x<=l && r<=y) return val[k];
		return comp(getval(x,y,l,(l+r)/2,k*2),getval(x,y,(l+r)/2,r,k*2+1));
	}
	void update(int entry, V v) {
		entry += NV;
		val[entry]=comp(v,val[entry]); //上書きかチェック
		while(entry>1) entry>>=1, val[entry]=comp(val[entry*2],val[entry*2+1]);
	}
};

SegTree_1<array<ll,8>,1<<20> st;

int N;
ll X[101010],Y[101010];
vector<ll> ev[1010101];


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N) {
		cin>>X[i]>>Y[i];
		st.update(Y[i],{0LL,0LL,0LL,0LL,1LL,X[i],Y[i],Y[i]*X[i]});
		ev[X[i]].push_back(Y[i]);
	}
	ll mi=1LL<<60;
	FOR(i,1000005) if(ev[i].size()) {
		auto a=st.getval(0,(1<<20)-1);
		ll v=a[0]*i-a[1]+a[5]-a[4]*i;
		if(v==0) {
			mi=0;
			break;
		}
		y=(1<<20)-1;
		ll sv;
		for(j=19;j>=0;j--) {
			int t=y-(1<<j);
			auto a=st.getval(0,t+1);
			ll v2=a[0]*i-a[1]+a[5]-a[4]*i;
			if(v2>=v/2) y=t, sv=v2;
		}
		
		a=st.getval(0,y+1);
		ll S=y*a[5]-a[4]*i*y-a[7]+i*a[6];  //右下
		S+=a[0]*i*y-a[1]*y-a[2]*i+a[3];  // 左下
		a=st.getval(y,(1<<20)-1);
		S+=a[7]-i*a[6]-a[5]*y+a[4]*i*y;  // 右上
		S+=a[1]*y-a[3]-a[0]*i*y+i*a[2];  // 左上
		
		mi=min(mi,S);
		
		FORR(e,ev[i]) {
			st.update(e,{1LL,i,e,i*e,-1,-i,-e,-i*e});
		}
	}
	cout<<(ll)mi<<endl;
	
	
}

まとめ

SegTreeにvector載せると重いので、今回初めてarray使ってみた。悪くないね。