kmjp's blog

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

天下一プログラマーコンテスト2012 決勝 : D さんかく

Dもなんとか解けた。
http://tenka1-2012-final.contest.atcoder.jp/tasks/tenka1_2012_final_d


3N個の格子点の座標が与えられるので、N個の三角形を作ったときの面積合計を十分大きくする。
最大値ではなくてよく、1000万回のランダム選択の結果に勝てばよい。

とはいえ、こちらは1000万回も繰り返す時間はないので、もう少し効率よく求めないといけない。
そこで、2つの三角形を選び、その6点の中で2つの合計が最大になるように6点の組み合わせを変える、ということを1.8秒(最大が2秒なので)繰り返したら、点が300個または3000個の時は意外とあっさりクリア。

一方、点が9個・18個と少ないときはちょくちょくWAが出た。
なのでこのときは総当たりで最大値を求めてうまく行った。

以前ARCの乱拓で、手元だとうまく行かず、AtCoderサーバでうまく行った例があったのでいまだに乱拓怖い。
CygwinとAtCoderで乱数の仕組みが違うんだろうか…。

#define rdtscll(val) do { \
     unsigned int __a,__d; \
     asm volatile("rdtsc" : "=a" (__a), "=d" (__d)); \
     (val) = ((u64)__a) | (((u64)__d)<<32); \
} while(0)

int N;
s64 X[3000],Y[3000];
s64 mina=0;
int F[1000][3];
s64 madp[1<<18];
int madpath[1<<18][18];

u64 mtime() {
	struct timeval mt;
	u64 t;
	
	gettimeofday(&mt,NULL);
	t = (mt.tv_sec * 1000) + mt.tv_usec/1000;
	return t;
	
}

s64 area(int x,int y,int z) {
	return abs((X[y]-X[x])*(Y[z]-Y[x])-(Y[y]-Y[x])*(X[z]-X[x]));
}

void swap(int f1,int f2) {
	s64 first,best,c;
	int pat=0,i1,i2;
	first = best = area(F[f1][0],F[f1][1],F[f1][2]) + area(F[f2][0],F[f2][1],F[f2][2]);
	//6通り試す
	c = area(F[f1][0],F[f1][1],F[f2][0]) + area(F[f1][2],F[f2][1],F[f2][2]);
	if(c>best){ best=c; pat=1; i1=F[f1][2]; i2=F[f2][0];}
	c = area(F[f1][0],F[f1][1],F[f2][1]) + area(F[f2][0],F[f1][2],F[f2][2]);
	if(c>best){ best=c; pat=2; i1=F[f1][2]; i2=F[f2][1];}
	c = area(F[f1][0],F[f1][1],F[f2][2]) + area(F[f2][0],F[f2][1],F[f1][2]);
	if(c>best){ best=c; pat=3; i1=F[f1][2]; i2=F[f2][2];}
	
	c = area(F[f1][0],F[f2][0],F[f1][2]) + area(F[f1][1],F[f2][1],F[f2][2]);
	if(c>best){ best=c; pat=4; i1=F[f1][1]; i2=F[f2][0];}
	c = area(F[f1][0],F[f2][1],F[f1][2]) + area(F[f2][0],F[f1][1],F[f2][2]);
	if(c>best){ best=c; pat=5; i1=F[f1][1]; i2=F[f2][1];}
	c = area(F[f1][0],F[f2][2],F[f1][2]) + area(F[f2][0],F[f2][1],F[f1][1]);
	if(c>best){ best=c; pat=6; i1=F[f1][1]; i2=F[f2][2];}
	
	if(pat>0) {
		mina += best-first;
		if(pat==1) { F[f2][0]=i1; F[f1][2]=i2;}
		if(pat==2) { F[f2][1]=i1; F[f1][2]=i2;}
		if(pat==3) { F[f2][2]=i1; F[f1][2]=i2;}
		if(pat==4) { F[f2][0]=i1; F[f1][1]=i2;}
		if(pat==5) { F[f2][1]=i1; F[f1][1]=i2;}
		if(pat==6) { F[f2][2]=i1; F[f1][1]=i2;}
	}
}

int bitcount(int n) {
	int i=0;
	while(n>0) {i += n%2; n>>=1;}
	return i;
}

s64 dfs(int mask) {
	int bc = bitcount(mask);
	int lbc= N - bc;
	int i;
	int v1,v2,v3,mask2,maxmask;
	s64 ma=0,na;
	
	if(lbc==0) return 0;
	
	if(madp[mask]==-1) {
		//3つ選択
		FOR(v1,N-2) {
			if(mask & (1<<v1)) continue;
			for(v2=v1+1;v2<N-1;v2++) {
				if(mask & (1<<v2)) continue;
				for(v3=v2+1;v3<N;v3++) {
					if(mask & (1<<v3)) continue;
					mask2 = (1<<v1) |(1<<v2)|(1<<v3);
					na = area(v1,v2,v3) + dfs(mask | mask2);
					if(na > ma){ ma=na; maxmask = mask2;}
				}
			}
		}
		
		madp[mask] = ma;
		FOR(i,lbc-3) madpath[mask][i]=madpath[mask|maxmask][i];
		FOR(v1,N) if(maxmask & (1<<v1)) madpath[mask][lbc++-3] = v1;
		
	}
	return madp[mask];
}


void solve() {
	u64 st,ct,tsc;
	s64 res;
	int f,r,i,x,y,n,m,v1,v2;
	int path[18];

	
	//combination
	N=GETi();
	FOR(i,N){
		X[i]=GETi();
		Y[i]=GETi();
	}
	
	st = mtime();
	
	if(N<=18) {
		//18以下なら決定的に行う
		MINUS(madp);
		ZERO(madpath);
		madp[(1<<N)-1]=0;
		dfs(0);
		FOR(i,N/3) _P("%d %d %d\n",madpath[0][i*3+0],madpath[0][i*3+1],madpath[0][i*3+2]);
	}
	else {
	
		//初期
		FOR(i,N) F[i/3][i%3]=i;
		FOR(i,N/3) mina += area(i*3,i*3+1,i*3+2);
		
		while(1) {
			ct = mtime();
			if(ct-st>=1800) break;
			rdtscll(tsc);
			srand(ct+tsc);
			
			FOR(n,100000) {
				int f1,f2;
				f1=rand()%(N/3);
				f2=rand()%(N/3);
				if(f1!=f2) swap(f1,f2);
			}
			
		}
		FOR(i,N/3) _P("%d %d %d\n",F[i][0],F[i][1],F[i][2]);
	}
}

まとめ

乱拓の行き当たりばったり感は置いといて、とりあえず解けて良かった。
18個以下は総当たり、というのも行き当たりばったりだけど無事解き切れてよかった。


ほかの人の回答も乱数で点を交換する方法をとってる人が多いな。
あと、18点以下と300点以上で方法切り替えてる人もいた。