LOJ6039. 「雅礼集训 2017 Day5」珠宝【决策单调性优化DP】【分治】【思维好题】

LINK


懒得搬题面
简要题意:n个物品,每个物品有一个价格和一个吸引力,问你对于(i in [1,k]),花费i的价格能得到的最大吸引力

其中价格的范围很小,在([1,300])范围内

思路

首先想到一个dp
(dp_{i,j})表示用对于价格小于等于i的物品花费j的价格能得到的最大吸引力
然后对于相等的价格的物品,有一个贪心的的思想就是个数确定的时候选取最大吸引力的几个
这个可以前缀和预处理出来
然后考虑对于价格相等的时候
如果对于(dp_{w,p}的决策点i < j),存在(dp_{w-1,i} + sum[i->p] < dp_{w-1,j} + sum[j->p])
那么因为很显然sum这个函数是斜率单调递减的所以可以得到对于任何(dp_{w,-}),i一定不会比j更优
所以这样对于w相同的情况就可以处理了
然是题目中的w不完全相同怎么办?
那就强行把w变成相同的就可以了对于(dp_{w,-})我们只考虑不够w的补上多余的价格变成w就可以了
然是这样有可能算不完全,所以对于每一个小于c的数都需要当成决策点来计算一遍
然后一个决策点只会从和它对于c同余相等的决策点转移
这样一定是最优的
然后直接for计算所有可能的决策点
但是为什么需要分治算法呢?
因为虽然有决策点单调的性质,但是我们并不能快速计算出决策点
所以可以考虑分治,枚举中间的决策点然后递归成两个区间分别求解,这样的复杂度是有保障的


//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define for_up(a, b, c) for (int a = b; a <= c; ++a)
#define for_down(a, b, c) for (int a = b; a >= c; --a)
#define for_vector(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x){
  bool w = 1;x = 0;
  char c = getchar();
  while(!isdigit(c) && c != '-')c = getchar();
  if(c == '-')w = 0, c = getchar();
  while(isdigit(c)) {
    x = (x<<1) + (x<<3) + c -'0';
    c = getchar();
  }
  if(!w)x=-x;
}
template <typename T>
void Write(T x){
  if(x < 0) {
    putchar('-');
    x=-x; 
  }
  if(x > 9)Write(x / 10);
  putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 1e6 + 10;
const int M = 5e4 + 10;
const int C = 3e2 + 10; 
int n, k, maxc = 0;
int c[N], v[N];
int ind, table[N];
ll dp[C][M];
vector<ll> bag[C], buc[C];
bool cmp(int a, int b) { return a > b;}
ll calc(int id, int lastpos, int now) { // 计算一个决策点的贡献
  int num = (now - lastpos) / id;
  if (num > (signed)bag[id].size() - 1) return -1;
  return dp[id - 1][lastpos] + bag[id][num];
}
void solve(int id, int l, int r, int pl, int pr) {
  int mid = (l + r) >> 1;
  int pos = 0; ll maxv = 0;
  for_up(i, pl , min(mid, pr)) { // 当前决策点可能的区间
    ll val = calc(id, table[i], table[mid]); 
    if (val > maxv) maxv = val, pos = i; // 记录最大权值和决策点
  }
  if(l == r) { // 如果已经到达一个节点就更新答案
    dp[id][table[l]] = maxv;
    return;
  }
  // 因为决策单调 所以决策点可以进行划分求解
  // 总复杂度是有保证的
  solve(id, l, mid, pl, pos);
  solve(id, mid + 1, r, pos, pr);
}
int main() {
  memset(dp, 0, sizeof(dp));
  Read(n), Read(k);
  int maxc = 0;
  for_up(i, 1, n) {
    Read(c[i]), Read(v[i]);
    buc[c[i]].push_back(v[i]);
    maxc = max(maxc, c[i]); 
  }
  for_up(i, 1, maxc) {
    sort(buc[i].begin(), buc[i].end(), cmp);
    bag[i].push_back(0);
    ll now = 0;
    for_vector(j, buc[i]) {
      now += buc[i][j];
      bag[i].push_back(now);
    }
  }
  for_up(i, 1, maxc) {// 考虑单个售价是小于等于i的物品
    for_up(j, 0, i - 1) {// 考虑对mod i余j的所有转移
      table[ind = 1] = j;
      while (table[ind] + i <= k) {
        table[ind + 1] = table[ind] + i;
        ++ind;
      }
      solve(i, 1, ind, 1, ind);// 分治求解
    }
  }
  for_up(i, 1, k) {
    Write(dp[maxc][i]);
    putchar(' ');
  }
  return 0;
}
原文地址:https://www.cnblogs.com/dream-maker-yk/p/9733229.html