TopCoder Jewelry


学习学习。。。

给定一个数组,要把这个数组中的一部分数给A,另一部分给B,使A和B的和相同但是A中最小的数不小于B中最大的数。例如:

values = {1,2,5,3,4,5}

一共有9中分配方法:

 Bob       Frank
  1,2         3
  1,3         4
  1,4         5  (first 5)
  1,4         5  (second 5)
  2,3         5  (first 5)
  2,3         5  (second 5)
   5  (first 5) 5  (second 5)
   5  (second 5) 5  (first 5)
1,2,3,4       5,5

需要注意的是不同位置的相等数字需要单独计算。数组大小最大30,每个数最大1000。

就算知道是动态规划也没做出来。。


先上大神的解法:

#include <algorithm>
#include <functional>
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <utility>
using namespace std;

#define REP(i,n) for(int i=0;i<(n);++i)
#define FOR(i,a,b) for(int i=(a);i<=(b);++i)
#define FORD(i,a,b) for(int i=(a);i>=(b);--i)
#define FOREACH(i,c) for(__typeof((c).begin()) i=(c).begin();i!=(c).end();++i)
typedef long long LL;
const int INF = 1000000000;
typedef vector<int> VI;
template<class T> inline int size(const T&c) { return c.size(); }

char buf1[1000];

string i2s(int x) {
  sprintf(buf1, "%d", x);
  return buf1;
}

const int MAXN = 30;
const int MAX = 30000;

int n;
VI v;

LL B[MAXN + 1][MAX + 1]; // [n pocz][sum]
LL F[MAXN + 1][MAX + 1];

LL nk[MAXN + 1][MAXN + 1];

void cnk() {
  nk[0][0] = 1;
  FOR(k, 1, MAXN) nk[0][k] = 0;
  FOR(n, 1, MAXN) {
    nk[n][0] = 1;
    FOR(k, 1, MAXN) nk[n][k] = nk[n - 1][k - 1] + nk[n - 1][k];
  }
}

void calc(LL T[MAXN + 1][MAX + 1]) {
  T[0][0] = 1;
  FOR(x, 1, MAX) T[0][x] = 0;
  FOR(ile, 1, n) {
    int a = v[ile - 1];
    FOR(x, 0, MAX) {
      T[ile][x] = T[ile - 1][x];
      if (x >= a) T[ile][x] += T[ile - 1][x - a];
    }
  }
}

// MAIN
long long howMany(vector <int> vv) {
  v = vv;
  n = size(v);
  cnk();
  sort(v.begin(), v.end(), greater<int>());
  calc(F);
  sort(v.begin(), v.end());
  calc(B);
  LL res = 0;
  int done = 0;
  while (done < n) {
    int p = done;
    while (p < n && v[p] == v[done]) ++p;
    int c = p - done;
    FOR(u, 1, c) {
      int uu = u * v[done];
      FOR(x, uu, MAX)
        res += B[done][x - uu] * F[n - done - u][x] * nk[c][u];
    }
    done = p;
  }
  return res;
}


按照大神的思路,仿写了一遍:

const int SIZEOFLIST = 30;
const int MAXSUM = 30000;
long long YANGHUI[SIZEOFLIST + 1][SIZEOFLIST + 1];//计算相同数字的取代因子
long long FORWARD[SIZEOFLIST + 1][MAXSUM + 1];//从小到大排列,到第i个数为止,部分和为j的子数组个数
long long BACKWARD[SIZEOFLIST + 1][MAXSUM + 1];//从大到小排列,到第i个数为止,部分和为j的子数组个数

vector<int> vals;
void CalYanghui() {//计算C(i,j),从i个相同的球中取j个的取法
  memset(YANGHUI, 0, sizeof(long long) * (SIZEOFLIST + 1) * (SIZEOFLIST + 1));
  YANGHUI[0][0] = 1;
  for (int i = 1; i <= SIZEOFLIST; i++) {
    YANGHUI[i][0] = 1;
    for (int j = 1; j <= i; j++) {
      YANGHUI[i][j] = YANGHUI[i - 1][j - 1] + YANGHUI[i - 1][j];
    }
  }
}
void CalSumCount(long long dep[SIZEOFLIST + 1][MAXSUM + 1]){//根据vals值,计算到第i个数为止,部分和为j的子数组个数
  memset(dep, 0, sizeof(long long) * (SIZEOFLIST + 1) * (MAXSUM + 1));
  dep[0][0] = 1;
  int n = vals.size();
  for (int i = 1; i <= n; i++) {
    int tmpval = vals[i - 1];
    for (int j = 0; j <= MAXSUM; j++) {
      dep[i][j] = dep[i - 1][j];//至少有[i-1][j]个
      if (j >= tmpval) dep[i][j] += dep[i - 1][j - tmpval];//如果把第i个数算入和中,则加上[i-1][j-vals[i]]个
    }
  }
}


long long howMany1(vector<int> values) {
  long long res = 0;
  int n = values.size();
  vals = values;
  CalYanghui();
  sort(vals.begin(), vals.end(), greater<int>());
  CalSumCount(BACKWARD);
  sort(vals.begin(), vals.end());
  CalSumCount(FORWARD);

  for (int i = 0; i < n;) {
    int p = i;
    while (p < n && vals[p] == vals[i]) p++;
    int c = p - i;//当前数的个数
    for (int j = 1; j <= c; j++) {//依次计算有一个、两个..当前数在和中的情况
      int tmpsum = j * vals[i];
      //当前和到最大和之间
      for (int x = tmpsum; x <= MAXSUM; x++) {
        //计算从i+j到n的和为x的子数组个数,乘以从0到i+j的和为x的子数组个数,再乘以c个相同数任意挑j个的因子
        res += BACKWARD[n - i - j][x] * FORWARD[i][x - tmpsum] * YANGHUI[c][j];
      }
    }
    i = p;
  }
  return res;
}


继续学习。。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值