kmjp's blog

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

AtCoder AGC #019 : C - Fountain Walk

なんか部分点とってたら割といい結果になった。
http://agc019.contest.atcoder.jp/tasks/agc019_c

問題


1区画100mの格子状の道がある。
一部の交差点では半径10mの噴水があり、その部分のみ噴水を取り囲む円状の道路がある。
2つの格子点が指定されたとき、両頂点の最短経路を求めよ。

解法

噴水で90度曲がると通常の格子点で曲がるより若干距離が短く、逆に噴水を通過しようとすると普通の格子点より距離が長くなる。
ただ、かといって始点終点を囲む矩形の外側を通るほど距離が節約できるわけではない。

よって、始点終点を囲む矩形内で、できるだけ多くの噴水で曲がるようにすればよい。
これには矩形内の噴水の位置に対しLISを求めれば、通れる噴水の数が求まる。

ただし、噴水の数が(幅+1)または(高さ+1)の場合、1回は噴水を突っ切らないといけないので注意。

double d1,d2;
int X1,Y1,X2,Y2;
int N;
int XX[202020],YY[202020];

vector<int> LIS(vector<int>& v) {
	int i,N=v.size();
	vector<int> dp(N,1<<30),id(N);
	FOR(i,v.size()) {
		id[i] = lower_bound(dp.begin(),dp.end(),v[i]) - dp.begin();
		dp[id[i]] = v[i];
	}
	int nl = *max_element(id.begin(),id.end());
	vector<int> ret(nl+1);
	FOR(i,N) if(id[N-1-i] == nl) ret[nl--] = v[N-1-i];
	return ret;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	double PI=atan(1)*4;
	d1=20-20*PI/4;
	d2=20*PI/2-20;
	
	cin>>X1>>Y1>>X2>>Y2;
	cin>>N;
	FOR(i,N) {
		cin>>XX[i]>>YY[i];
		if(XX[i]<min(X1,X2) || XX[i]>max(X1,X2) || YY[i]<min(Y1,Y2) || YY[i]>max(Y1,Y2)) i--,N--;
	}
	
	X2-=X1;
	Y2-=Y1;
	vector<pair<int,int>> V;
	FOR(i,N) {
		XX[i]-=X1;
		if(X2<0) XX[i]=-XX[i];
		YY[i]-=Y1;
		if(Y2<0) YY[i]=-YY[i];
		V.push_back({XX[i],YY[i]});
	}
	if(X2<0) X2=-X2;
	if(Y2<0) Y2=-Y2;
	
	double ret=100.0*(X2+Y2);
	if(N) {
		vector<int> V2;
		
		sort(ALL(V));
		FORR(v,V) V2.push_back(v.second);
		auto V3=LIS(V2);
		
		if(V3.size()==min(X2,Y2)+1) {
			ret+=d2-d1*(V3.size()-1);
		}
		else {
			ret-=d1*V3.size();
		}
	}
	_P("%.12lf\n",ret);
	
	
}

まとめ

まんまとコーナーケースにかかってしまった。