kmjp's blog

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

TopCoder SRM 740 Div1 Medium DieDesign

これ想定解2通りありそう。
http://community.topcoder.com/stat?c=problem_statement&pm=15141

問題

 
2人が、N個の面があるサイコロを持っている。
互いにサイコロを振ったとき、目の大きな面を出した側が出すゲームを考える。
 
片方のプレイヤーの各面の目の数が与えられる。
もう片方は、目の総和が等しい範囲で各面の目の数を任意に選べるとき、勝率を最大化できる目の例を示せ。

解法

できるだけ効率よく相手に勝てるようにする方がいいので、最後の1面を除けば、面に振る目の数は0か相手の(いずれかの面+1)の計N+1通りである。
そこで、以下の通りdpを行いそれを復元しよう。
片方のプレイヤーの各面の目の最大値をAとすると、O(A*N^3)となりちょっと危ないが何とか通る。
dp(i,s) := i番目までの面が決まっているとき、残りの面に計sの目を触れるときの勝率の最大値。

まだ、今回はAがさほど大きくないのでこれでよいが、以下のようにするとO(N^4)でAの値に依存しない計算量にできる。
dp(i,x) := i番目までの面が決まっているとき、勝率がx/(N^2)となるような目の総和の最小値

int win[30*5100+1];
int ma[31][30*5100];
int from[31][30*5100];


class DieDesign {
	public:
	vector <int> design(vector <int> P) {
		int i,j,k;
		int N=P.size();
		int S=0;
		FORR(p,P) S+=p;
		
		vector<int> C;
		C.push_back(0);
		FOR(i,30*5002) {
			win[i]=0;
			FORR(p,P) if(p<i) win[i]++;
			if(i && win[i]>win[i-1]) C.push_back(i);
		}
		
		MINUS(from);
		MINUS(ma);
		ma[0][S]=0;
		FOR(i,N-1) FOR(j,S+1) FORR(c,C) if(j>=c) {
			if(ma[i][j]+win[c]>ma[i+1][j-c]) {
				ma[i+1][j-c]=ma[i][j]+win[c];
				from[i+1][j-c]=c;
			}
		}
		
		int be=-1;
		int ca=-1;
		FOR(i,S+1) if(ma[N-1][i]+win[i]>be) be=ma[N-1][i]+win[i], ca=i;
		vector<int> R;
		R.push_back(ca);
		for(i=N-1;i>=1;i--) {
			R.push_back(from[i][ca]);
			ca+=from[i][ca];
		}
		cout<<be<<endl;
		return R;
	}
}

まとめ

本番(計算時間は確認したが)何も考えず前者を取ってしまった。