有多少可能的记分卡与输入记分卡一致?

时间:2012-07-01 19:26:29

标签: algorithm dynamic-programming

我一直试图在面试街上解决以下问题。计分记分卡(30分)

在锦标赛中,N名选手只相互比赛一次。每场比赛都会导致任何一名球员获胜。没有关系。你已经给出了一张记分卡,其中包含了比赛结束时每位球员的得分。玩家的得分是玩家在锦标赛中赢得的总游戏数。但是,某些玩家的得分可能已从记分卡中删除。有多少可能的记分卡与输入记分卡一致?

输入: 第一行包含案例数T.T案例如下。每个案例包含第一行的数字N,后面是第二行的N个数字。第i个数字表示s_i,即第i个玩家的得分。如果第i个玩家的得分已被删除,则表示为-1。

输出: 输出T行,包含每种情况的答案。输出每个结果模数为1000000007。

Constraints:
1 <= T <= 20
1 <= N <= 40
-1 <= s_i < N

Sample Input:
5
3
-1 -1 2
3
-1 -1 -1
4
0 1 2 3
2 
1 1
4
-1 -1 -1 2

Sample Output:
2
7
1
0
12

说明: 对于第一种情况,可能有2个记分卡:0,1,2或1,0,2。 对于第二种情况,有效记分卡为1,1,1,0,1,2,0,2,1,0,0,2,1,2,0,2,0,1,2,1,0 。 对于第三种情况,唯一有效的记分卡是{0,1,2,3}。 对于第四种情况,没有有效的记分卡。两位球员都无法获得1分。

我试图提出通用函数方法,但我真的试图使用动态编程来解决这个问题。你怎么能想到这个问题的复发关系?。

5 个答案:

答案 0 :(得分:1)

以下是针对上述问题的DP解决方案

    public static int[][] table;  // stores the result of the overlapping sub problems
private static int N;

public static void main(String args[]) {
    Scanner scanner = new Scanner(System.in);
    int testCases = scanner.nextInt();
    for (int i = 0; i < testCases; i++) {
        N = scanner.nextInt();
        int[] scores = new int[N];
        for (int j = 0; j < N; j++) {
            scores[j] = scanner.nextInt();
        }
        long result = process(scores) % 1000000007L;
        System.out.println(result );

    }

}

private static long process(int[] scores) {
    int sum = 0; 
    int amongPlayers = 0; //count no of players whose score has been erased(-1)
    for (int i = 0; i < N; i++) {
        if (scores[i] != -1) {
            sum += scores[i];
        } else {
            amongPlayers++; 
        }
    }

    int noGames = (N * (N -1)) /2;  // total number of games



    if (sum < noGames) {
        int distribute = noGames - sum;  // score needed to be distributed;
        table = new int[distribute + 1 ][amongPlayers + 1];
        for (int m = 0; m <= distribute; m++) {
            for (int n = 0; n <= amongPlayers; n++) {
                table[m][n] = -1;
            }
        }
        return distribute(distribute, amongPlayers); // distrubute scores among players whose score is erased(-1)
    }
    else if(sum == noGames){
        return 1;
    }

    return 0;
}

/**
 * Dynamic programming recursive calls
 * @param distribute
 * @param amongPlayers
 * @return
 */
private static int distribute(int distribute, int amongPlayers) {
    if(distribute == 0 && amongPlayers == 0)
        return 1;

    if (amongPlayers <= 0)
        return 0;

    if(distribute == 0)
        return 1;

    int result = 0;
    if (table[distribute][amongPlayers - 1] == -1) {
        int zeroResult = distribute(distribute, amongPlayers - 1);
        table[distribute][amongPlayers - 1] = zeroResult;
    }
    result += table[distribute][amongPlayers - 1];

    for (int i = 1; i < N ; i++) { // A person could win maximum of N-1 games
        if (distribute - i >= 0) {
            if (table[distribute - i][amongPlayers - 1] == -1) {
                int localResult = distribute(distribute - i,
                        amongPlayers - 1);
                table[distribute - i][amongPlayers - 1] = localResult;
            }
            result += table[distribute - i][amongPlayers - 1];
        }
    }

    return result;
}

答案 1 :(得分:1)

观察:

序列s [1],s [2],...,s [n]是一致的记分卡,这些属性必须保持:

  1. s [i1] + s [i2] + .. + s [ik]&gt; = k *(k-1)/ 2,其中i1&lt; i2&lt; ..&lt; ik(即长度为k的每个子序列)
  2. s [1] + s [2] + .. + s [n] = n *(n - 1)/ 2
  3. 首先,我们需要检查未删除的分数,只需使用1个条件。然后使用动态编程来删除分数。

    让我们表示删除分数b [i],而不是删除分数a [i];

    sum {i = 1 .. l} a [i] + sum {i = 1 ... k} b [i]&gt; =(k + l)*(k + l - 1)/ 2

    sum {i = 1 .. l} a [i] + sum {i = 1 .. k} b [i]&gt; = 0 + 1 + .. +(k + l - 1)

    sum {i = 1 .. l}(a [i] - (k + i - 1))+ sum {i = 1 ... k} b [i]&gt; = 0 + 1 + .. + (k - 1)

    所以我们可以预先计算每k的最小值{i = 1 .. l}(a [i] - (k + i - 1))/

    动态编程:

    规定:

    dp [k] [得分] [总和]:我们知道第一个k最小擦除分数,它们的值不超过$ score $,sum是它们的总和。

    转换:

    1. Skip score,dp [k] [score] [sum] + = dp [k] [score + 1] [sum];

    2. 将$ i $得分值$ score $ dp [k] [得分] [和] + = C [m - k] [i] * dp [k + i] [得+ 1] [总和+ i *得分],其中m个被删除的分数,C [n] [k] =组合。

    3. <强> my code

答案 2 :(得分:0)

胜利的总和应为(N C 2)

减去输入中给出的已知值。设剩余的和(N C 2) - x称为S.输入中-1的数量为Q.

问题现在归结为找到 Q变量的整数解的数量,范围从0到N-1(可能的最大分数),其总和为S

设DP [q] [s]表示q和变量的积分解数,其和为s

然后我们有,

DP[q][s] = Sum (i=0 to N-1) DP[q-1][s-i]

DP [Q] [S]给出解决方案

编辑:
观察: 对于剩下的x人,总胜数应至少为x *(x-1)/ 2(当他们互相比赛时)。因此,在q人的任何时间,s都不能超过(N-q)(N-q-1)/ 2 = M

当s大于M时,DP [q] [s]应该再有一个约束

答案 3 :(得分:0)

我也在尝试解决这个问题,并认为它应该是这样的:

给出了玩家的数量(= N),未知卡的数量(计数“-1”)和已知卡的总和(计算除“-1”之外的所有卡)。可能的游戏总数应为1 + 2 + 3 + ... +(玩家-1):第一个玩家有(玩家-1)对手,第二个玩家(玩家-2)等。

现在您可以递归计算可能的记分卡总和:

使用(玩家,未知牌,已知牌总数)作为关键字以及可能得分卡的总和作为值初始化空的hashmap。

如果定义了所有牌,则答案为0(如果所有牌的总和等于可能的游戏总数)或1(如果所有牌的总和不等于可能的游戏总数)。

如果没有定义所有卡,则运行for循环并将一张未知卡设置为0,1,2 ......(player-1)并尝试从hashmap读取结果。如果它不在hashmap中,则调用方法本身并将结果保存在map中。

递归代码应该是这样的:

def recursion(players: Int, games: Int, unknownCards: Int, knownScore: Int): Int = {
  unknownCards match {
    case 0 if knownScore != games => 0
    case 0 if knownScore == games => 1
    case _ =>
      map.get(players, unknownCards, knownScore) getOrElse {
        var sum = 0
        for (i <- 0 until players) sum += main(players, games, unknownCards - 1, knownScore + i)
        sum %= 1000000007
        map.put((players, unknownCards, knownScore), sum)
        sum
      }
  }
}

答案 4 :(得分:0)

试试这个

import java.util.Scanner;

public class Solution {

final private static int size = 780;
private static long[][] possibleSplits = new long[size][size];

static {

    for(int i=0; i < size; ++i)
        possibleSplits[i][0] = 1;

    for(int j=0; j< size; ++j)
        possibleSplits[0][j] = j+1;

    for(int i=1; i< size; ++i)
        for(int j=1; j < size; ++j)
        {
            possibleSplits[i][j] = (possibleSplits[i-1][j] + possibleSplits[i][j-1]) % 1000000007;              
        }       
}

public long possibleWays = 0;

public Solution(int n, String scores)
{   
    long totalScores = 0;
    int numOfErasedScores = 0; 

    for(String str : scores.split(" "))
    {
        int s = Integer.parseInt(str);

        if (s < 0)
            ++numOfErasedScores;
        else        
            totalScores += s;
    }

    long totalErasedScores = ncr(n,2) - totalScores;

    if(totalErasedScores == 0)
        ++possibleWays;
    else if (totalErasedScores > 0)
        partition(n-1, totalErasedScores, numOfErasedScores);
}

private void partition(int possibleMax, long total, int split)
{       
    if (split == 0)
        return;

    possibleWays = possibleSplits[(int)total-1][split-1];       

    if (total > possibleMax)
        possibleWays -= split;      
}

public static void main(String[] args)
{
    Scanner in = new Scanner(System.in);

    int numberOfTestCases = Integer.parseInt(in.nextLine().trim());

    for(int i=0; i< numberOfTestCases; ++i)
    {
        String str = in.nextLine().trim();

        int    numberOfPlayers = Integer.parseInt(str);
        String playerScores    = in.nextLine().trim();

        long result = new Solution(numberOfPlayers, playerScores).possibleWays;

        System.out.println(result % 1000000007);
    }

    in.close();
}

public static long ncr(int n, int r)
{
    long result = 1;

    for(int i= Math.max(n-r, r)+1;i<=n;++i)
        result*= i;

    result/= fact(Math.min(n-r,r));

    return result;
}

public static long fact(int n)
{
    long result = 1;

    for(int i =2; i<= n; ++i)
        result *= i;



    return result;
    }
}