[噼昂!]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\) 满足:

  1. \(S_1\cup S_2=S\land S_1\cap S_2=\emptyset\)
  2. \(\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;
}

肆、关键 の 地方 ¶

证明凸性的另一种方法 —— 中间的点比左右俩点的中值都 大/小,则一定是有凸性的。

posted @ 2021-10-08 15:56  Arextre  阅读(41)  评论(0编辑  收藏  举报