kmjp's blog

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

TopCoder SRM 754 Div1 Medium OrthogonalProjections

久々に重いMedium来たな。
https://community.topcoder.com/stat?c=problem_statement&pm=15376

問題

2次元座標上にいくつかの点がある。
ここで1つの直線を引くことを考える。

各点をこの直線上に射影し、その並びを考えよう。
並びが一致する一連の直線群は、同類である、と定義する。
また、並びがちょうど反転している場合も同類としよう。

正整数Nが与えられる。
直線群がちょうどN個に同類ごとに分類できるような点の集合を構成せよ。

解法

傾きが同じ直線は、その切片によらず同類になる。

さて、ここで2点の関係について考えてみる。
この2点の垂直二等分線に2点を射影すると両者は同じ位置に来る。
そこから傾きを少し変えると、それぞれの順番は左右どちらに傾けるかにより逆転する。

全頂点間における垂直二等分線を考える。
切片をずらした直線も同類になるので、例えば全部の線を原点を通るように並べ替えよう。
このとき直線の傾きがm個あるとする。
原点を中心とする単位円を、これらm個の直線で区切ることを考えよう。
直線との交点が2m個出来、直線間の円弧が2m個で計4m個の領域に分かれる。

射影後の並びが反転するものは同一視するという条件より、原点に対称な位置の領域を同一視すると2m個に分類できることになる。

ここまでわかると、いくつか点を配置し、2点を結んでできる直線の傾きがm通りであれば(垂直二等分線の傾きもm通りで)結果2m個の同類の分類できることがわかる。
なお、例外ケースは先に処理しておこう。

  • Nが奇数の場合は解が無いが、N=1の場合は例外的に1点だけおけばよい。
  • N=2の時は、適当に2点おけばよい。
  • N=4は解なし。
  • N=6はサンプルにある通り。

以後Nは8以上の整数とし、点を並べて傾きがN/2通りとなるようにしよう。
(0,y)と(1,z)にk個ずつ点を並べることにする。
(0,y)には(0,0)~(0,k-1)に等間隔に点を配置し、(1,z)には傾きを微調整しながら点を配置しよう。
そうすると2*k~(k^2+1)通りの傾きのいずれかを構築できる。

class OrthogonalProjections {
	public:
	vector <int> generate(int n) {
		
		if(n==1) return {0,0};
		if(n==2) return {0,0,0,1};
		if(n==6) return {0,0,0,1,1,2};
		if(n%2 || n==4) return {};
		int i,v=1;
		n=n/2-1;
		while(v*v<n) v++;
		vector<int> R;
		FOR(i,v) R.push_back(0),R.push_back(i);
		n-=2*v-1;
		R.push_back(1);
		R.push_back(0);
		int pre=0;
		FOR(i,v-1) {
			int x=min(n,v-1);
			pre+=x+1;
			n-=x;
			R.push_back(1);
			R.push_back(pre);
		}
		return R;
	}
	
}

まとめ

考察は重いけど実装は軽いんだよな。