SRM548 Div1 Medium(450) KingdomAndDice

KingdomAndDice

1番目のダイスと2番目のダイスのラベルのN*N通りの組合わせのうち、1番目ダイスのほうが大きいもの個数をp個とすると、勝率はp/(N*N)。pをN*N/2に近づければ良い。

ラベルの数字に意味は無くて、2番目のダイスのラベルのうち何個に勝てるかが重要。また、同じ勝ち数のラベルが0の個数以上あっても意味が無い。結局、例えば、最初の例{{0, 2, 7, 0}, {6, 3, 8, 10}, 12}は、勝ち数がそれぞれ0, 0, 1, 1, 3, 4, 4のラベルから、2枚を選んで、剥がれていないラベルの勝ち数の総和2と合わせて、N*N/2=8に近づけよ、という問題に帰着できる。

あとは動的計画法。O(N5)になりそうだけど、x番目までのラベルを使って、勝ち数の総和をiにするために必要な最小のラベル数を覚えておくことで、O(N4)で計算できる。たぶん、このために0のラベルは複数使える問題設定なのだと思う。

#include <vector>
#include <algorithm>
using namespace std;

class KingdomAndDice{public:
double newFairness( vector <int> firstDie, vector <int> secondDie, int X )
{
    int N = (int)firstDie.size();

    //  0の個数
    int zn = (int)count(firstDie.begin(),firstDie.end(),0);

    //  使えるラベル
    //  secondDieに何勝できるかを入れる
    //  同じ勝ち数でzn以上は意味が無いので、最大zn個
    vector<int> L;
    sort(secondDie.begin(),secondDie.end());
    for ( int i=1; i<=N; i++ )
    {
        int c = 0;
        for ( int j=secondDie[i-1]+1; j<=(i<N?secondDie[i]-1:X); j++ )
        {
            if ( count(firstDie.begin(),firstDie.end(),j)==0 )
            {
                L.push_back(i);
                if ( ++c >= zn )
                    break;
            }
        }
    }

    //  firstDieのsecondDieに対する勝ち数の総和
    int w = 0;
    for ( int i=0; i<N; i++ )
        for ( int j=0; j<N; j++ )
            if ( firstDie[i]>secondDie[j] )
                w++;

    //  secondDieに対する勝ち数の和をiにするのに必要な最小のラベル数
    vector<int> T(N*N+1,9999);
    T[w] = 0;
    for ( int i=0; i<(int)L.size(); i++ )
    {
        vector<int> P = T;
        for ( int j=L[i]; j<=N*N; j++ )
            T[j] = min(T[j],P[j-L[i]]+1);
    }

    //  N*N/2に最も近い勝ち数
    int ans = -N*N;
    for ( int i=0; i<=N*N; i++ )
    if ( T[i]<=zn )
        if ( abs(i*2-N*N) < abs(ans*2-N*N) )
            ans = i;

    return (double)ans/(N*N);
}};