[噼昂!]fake(Pending)
壹、关于题目 ¶
懒得。
贰、关于题解 ¶
这种奇奇怪怪的凸性是真的想不到。
我们不妨先将下标从 \(0\) 开始编,于是每件物品的重量都在 \([0,4]\) 之间。然后开始朴素 \(\rm DP\):定义 \(f(i,j)\) 表示前 \(i\) 组物品,共选择重量为 \(j\) 时的最大价值,转移就不用说了。
显然朴素的转移会让复杂度达到 \(\mathcal O(n^2)\),这是不可接受的。不过这个 \(\rm DP\) 有奇奇怪怪的凸性:\(f(i, j + 24) - f(i, j + 12) \ge f(i, j + 12) - f(i, j)\),即第二维按照 \(\bmod 12\) 分组,每组都是凸的。
至于证明,先有引理:
对于任一整数可重集 \(S=\{s_i\}\),若 \(\sum s_i=24\) 且 \(\forall i,s_i\in [0,4]\),则一定存在一种划分,使得划分之后的两个集合 \(S_1,S_2\) 满足:
- \(S_1\cup S_2=S\land S_1\cap S_2=\emptyset\);
- \(\sum_{x\in S_1}x=12,\sum_{y\in S_2}y=12\);
那么,对于任意由 \(f(i,j)\) 到 \(f(i, j + 24)\) 的情形,我们一定可以拼出一种 \(f(i, j + 12)\),并且一定可以构造出一种划分使得 \(f(i, j + 12) \ge \frac{f(i, j) + f(i, j + 24)}{2}\),至于构造可以自己想一想,这里不再赘述。于是我们证明了它的凸性。
然后就很好做了,我们对于第一维分治,将两个区间的凸包集合合并,由于合并的都是凸包,于是可以使用闵科夫斯基和的方法。
总复杂度 \(\mathcal O(k^2\times \frac{n}{k}\log n)=\mathcal O(kn\log n)\).
叁、参考代码 ¶
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
// # define USING_STDIN
// # define NDEBUG
// # define NCHECK
#include <cassert>
namespace Elaina {
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
template<class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }
#ifndef USING_STDIN
inline char freaGET() {
# define BUFFERSIZE 1 << 18
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARGET freaGET()
#else
# define CHARGET getchar()
#endif
template<class T> inline T readret(T x) {
x=0; int f = 0; char c;
while((c = CHARGET) < '0' || '9' < c) if(c == '-') f = 1;
for(x = (c^48); '0' <= (c = CHARGET) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) { x = readret(T(1)); }
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writc(T x, char s = '\n') {
static int fwri_sta[55], fwri_ed = 0;
if(x < 0) putchar('-'), x = -x;
do fwri_sta[++fwri_ed] = x % 10, x /= 10; while(x);
while(putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
putchar(s);
}
} using namespace Elaina;
const int maxn = 1e5;
vector<ll> a[maxn + 5];
int pre[maxn + 5], n;
inline void input() {
readin(n); int k;
rep(i, 1, n) {
readin(k), pre[i] = pre[i - 1] + k - 1;
a[i].resize(k);
rep(j, 0, k - 1) readin(a[i][j]);
}
}
struct Hull_Set {
vector<ll> v[12];
inline vector<ll>& operator [](const int id) {
return v[id];
}
};
// Minkowski
inline void Merge(Hull_Set a, Hull_Set b, Hull_Set& res) {
for(int i = 0; i < 12; ++i) if(!a[i].empty())
for(int j = 0; j < 12; ++j) if(!b[j].empty()) {
int delta = (i + j >= 12), nxt = (i + j) % 12;
int x = 0, y = 0;
while(1) {
assert(x + y + delta < res[nxt].size());
res[nxt][x + y + delta] = max(res[nxt][x + y + delta], a[i][x] + b[j][y]);
if(x + 1 == a[i].size() && y + 1 == b[j].size()) break;
if(x + 1 == a[i].size()) ++y;
else if(y + 1 == b[j].size()) ++x;
else {
if(a[i][x + 1] - a[i][x] > b[j][y + 1] - b[j][y]) ++x;
else ++y;
}
}
}
}
Hull_Set solve(int l, int r) {
if(l == r) {
Hull_Set ret;
for(int i = 0; i < (int)a[l].size(); ++i)
ret[i].push_back(a[l][i]);
return ret;
}
int mid = l + r >> 1;
auto _lhs = solve(l, mid), _rhs = solve(mid + 1, r);
Hull_Set ret;
for(int i = 0; i < 12; ++i) {
for(int j = i; j <= pre[r] - pre[l - 1]; j += 12)
ret[i].push_back(-1);
}
Merge(_lhs, _rhs, ret);
return ret;
}
signed main() {
// freopen("fake.in", "r", stdin);
// freopen("fake.out", "w", stdout);
input();
auto res = solve(1, n);
rep(i, 0, pre[n]) writc(res.v[i % 12][i / 12], ' ');
return 0;
}
肆、关键 の 地方 ¶
证明凸性的另一种方法 —— 中间的点比左右俩点的中值都 大/小,则一定是有凸性的。