[HDU6757]Hunting Monsters

\[\color{red}{\text{校长者,真神人也,左马桶,右永神,会执利笔破邪炁,何人当之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本当に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{对曰:“无人,狗欲当之,还请赐教!”}} \\ \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \]


壹、关于题目 ¶

一道神题,没什么可以阐述的,唯一需要注意的就是在 \(\rm HDU\) 上提交好像只能使用 scanf().

传送门 to HDU.

贰、关于题解 ¶

是为神题,则定有其过人之处。题之精妙者,诚尔;吾之愚笨者,深尔!天丧予!天丧予!

◆ 加工生产调度

一共有 \(n\) 个产品,某个产品 \(i\)\(A,B\) 两车间加工的时间分别为 \(a_i,b_i\). 怎样安排这 \(n\) 个产品的加工顺序,才能使总的加工时间最短?

一个很经典的贪心题,存在结论:

  • 按照 \(a_i\le b_i\),与 \(a_i>b_i\) 分成两组 \(T,S\). 对于 \(T\),按照 \(a_i<a_j\) 排序;对于 \(S\),按照 \(b_i>b_j\) 排序,再将 \(T,S\) 拼接起来,按顺序选择最优;

有感性证明:

不难发现,\(A\) 会一直加工直到全部的东西都加工完毕;那么,有两个特殊位置 —— 在最开始的时候,以及 \(A\) 机器加工完毕之后;

显然,在最开始的时候,\(A\) 是单独运作而 \(B\) 停机;当 \(A\) 加工完所有的东西之后,\(B\) 仍然在加工最后的一部分产品,只有当 \(B\) 完成加工,任务结束。

那么,贪心地,在最开始的时候选择 \(a_i\) 最小的,在最后的时候选择 \(b_i\) 最小的,即让 \(a_i\) 最小的最先选,\(b_i\) 最小的最后选择。

不过,我们可以尝试选择一些更理性的证明:

\(S=\set{J_1,J_2,\cdots,J_s}\) 为等待加工的集合,其中 \(J_i=(a_i,b_i)\),定义 \(T(S,t)\) 表示 \(B\) 机器要在 \(t\) 之后才可以开始加工,目前剩下的代加工集合为 \(S\) 时的最小加工时间,不难发现:

\[T(S,t)=\min_{i=1}^s a_i+T(S\setminus \set{J_i},b_i+\max\set{t-a_i,0}) \]

于是,我们分别假设先加工 \(i\) 比先加工 \(j\) 更优,算出 \(T_{ij}\)\(T_{ji}\) 进行比较(过程过于冗杂便不给出),最后我们可以得到,当先执行 \(i\) 比先执行 \(j\) 更优时,应当满足:

\[\min\set{a_i,b_j}<\min\set{a_j,b_i} \]

这是 \(\rm Johnson\) 法则的数学表达式。

不过,当我们实际操作的时候,发现这个表达式可能存在一些问题:它不满足不可比性传递性。

所以,我们才要对 \(\set{J_i}\) 进行分组,使得组内比较满足不可比性传递性。于是才有了上述分组。

◆ 问题划分

该题可类比上面的题,于是,我们可以先对所有 \((a_i,b_i)\) 分组:相同地,按照 \(a_i\le b_i\),与 \(a_i>b_i\) 分成两组 \(T,S\). 排序也是同样的道理。

到现在,问题就变成:

  • 对于任意 \(k\),在 \(S\) 中选择 \(k\) 个任务的最小初始能量;
  • 对于任意 \(k\),在 \(T\) 中选择 \(k\) 个任务的最小初始能量;
  • 将上面两种合并;

接下来考虑逐个击破;

◆ 我与 \(S\) 最后的战场,亦或是世界起始的圣战

先考虑简单 \(\rm DP\):定义 \(f(i,j)\) 表示在 \(S\) 最后 \(i\) 个数对中,选出 \(j\) 个的最小初始值,那么有转移:

\[f(i,j)=\min\set{a_i+\max\set{f(i-1,j-1)-b_i,0},f(i-1,j)} \]

另外,我们不难看出几个比较显然的不等关系:\(f(i,j-1)\le f(i,j)\le f(i-1,j)\),后者是因为遵循排序规则选择,从越前面的开始选择答案应该更优。

不过,我们似乎不能直接从这个转移得到什么东西,考虑挖掘这个转移的一些关系:

  • \(\min\) 的前一项中,最小值显然为 \(a_i\),所以当 \(f(i-1,j)\le a_i\) 时,后一项一定更优,我们可以直接从后一项转移;
    • 更进一步地,若 \(f(i-1,j)\le b_i\),又 \(b_i<a_i\),且 \(b_i\) 上升,所以对于后面任意时刻都满足前面的不等式,这个时候后面的每一项 \(f(k,j)(k\in [i,|S|])\) 值都为 \(f(i-1,j)\) 了;
  • \(f(i-1,j)>a_i\) 时,我们又应该考察另外的情况了:
    • \(f(i-1,j)\le f(i-1,j-1)-b_i+a_i\) 时,显然对 \(f(i-1,j-1)-b_i\) 再套上和 \(0\)\(\max\) 只会让值更大,此时 \(f(i,j)\) 也可以直接由 \(f(i-1,j)\) 转移了;
    • \(f(i-1,j)>f(i-1,j-1)-b_i+a_i\) 时,看似因为 “对 \(f(i-1,j-1)-b_i\) 再套上和 \(0\)\(\max\) 只会让值更大” 导致我们不能直接看出 \(f(i,j)\) 该由哪边转移,可是若 \(f(i-1,j-1)-b_i\) 真的都比 \(0\) 小了,那么转移的前一项就是 \(a_i\),根据前提 \(f(i-1,j)>a_i\),我们也可以直接由前一项转移了,即在这种情况下 \(f(i,j)=\max\set{f(i-1,j-1)-b_i,0}+a_i\)

于是,我们似乎可以这样实现转移:

维护一个数据结构,这个数据结构中保留着 \(f(i-1)\) 的所有信息,现在到了 \(i\),找到某些位置 \(j\) 使得 \(f(i-1,j)\le b_i\),这些位置直到最后都一定会是 \(f(i-1,j)\),所以将这些位置删去,同时保留 \(f(n,j)=f(i-1,j)\),对于那些 \(f(i-1,j)\le a_i\) 的位置,我们不需要管它,因为这些位置是 \(f(i,j)=f(i-1,j)\),即直接继承上一次的值,同样对于那些 \(f(i-1,j)\le f(i-1,j-1)-b_i+a_i\) 的位置,我们也不需要管这些 \(j\). 需要特殊处理的,只有 \(f(i-1,j)>f(i-1,j-1)-b_i+a_i\)\(j\),我们将这些 \(j\) 整体更新为 \(\max\set{f(i-1,j-1)-b_i,0}+a_i\).

不过,遗憾的是,似乎并没有这样一种精妙的数据结构支持以上的操作,一切的一切都要我们暴力地修改判断,也就是说,如果迟迟不出现 \(f(i,j)\le b_i\) 的情况,最终的复杂度仍然会达到 \(\mathcal O(n^2)\),这是不好的......

然而,这个转移有着它神奇的地方

具体来说,我们可以将每个状态看做 \((i,f(i,j))\) 的点,而转移则是两个向量 \((0,0),(1,a_i-b_i)\),由于是取 \(\min\),我们则可以将其类比为,当前的一个下凸壳在与两个向量构成的一条线作闵科夫斯基和,由于初始的 \(f\) 只是一个点,转移始终是一条线,最初显然是俩凸包,那么后面每个时刻都会是俩凸包!也就是说,\((i,f(i,j))\) 是具有凸性的,也即

\[f(i,j+1)-f(i,j)\ge f(i,j)-f(i,j-1) \]

于是,我们只需要使用优先队列维护差分即可,因为差分具有单调性,我们每次看看到队头的元素和 \(b_i\) 的关系即可。如果当前的元素比 \(b_i\) 小,那么可以弹出,因为它直到最后一定都是一个值。对于剩下的部分,首先他们一定都是大于 \(b_i\) 的,在这些部分中,在 \((b_i,a_i]\) 的部分可以直接继承上一个的值,剩下的部分应该加上 \(a_i-b_i\),观察转移 \(f(i-1,j)>f(i-1,j-1)-b_i+a_i\),其实际上就是 \(f(i-1,j)-f(i-1,j-1)>a_i-b_i\),这个转移其实还是差分与 \(a_i-b_i\) 比较,那这就好办了,我们只需要加入一个 \(a_i-b_i\) 的虚元素进去,在堆中它一定会找到自己应该在的位置,并且由于我们维护的是差分,所以它到该到的位置之后,就表示了将比他大的元素都加上 \(a_i-b_i\). 该复杂度为 \(\mathcal O(n\log n)\).

\(T\) 是我最后的归宿

这很好办,按照 \(a_i\le b_i\) 之后选择较小的 \(K\) 个即可。

\(S\)\(T\) 的统一

设在 \(T\) 中选择 \(i\) 个的最小初始值为 \(g(i)\),选完这些的增加量为 \(d(i)\),在 \(S\) 中选择 \(j\) 个的最小初始值为 \(f(j)\),最后的结果为 \(h(k)\).

显然最后我们会先在 \(T\) 中选择,再在 \(S\) 中选择,显然在 \([g(i),g(i+1))\) 这个区间中,我们最开始都只能选 \(i\) 个,于是我们可以找到 \(j\) 使得 \(f(j)\le g(i+1)-1+d(i)\),然后将 \(h(i+j)\) 更新为 \(\max\set{g(i),f(j)-d(i)}\). 看似是 \(\mathcal O(n^2)\) 的,不过 \(g,f\) 都是单调的,并不需要那么麻烦。

然后就做完了。

◆ 实现细节

  • 注意,由于我们的 \(f\) 是倒着定义的,所以排序时也应该倒着来;
  • 处理 \(S\) 时,我们在加入 \(a_i-b_i\) 之前,需要先整体减少 \(b_i\),因为我们下一次的标准会是当前的 \(b_i\),所以在堆中,如果有元素,那么应该先更新一下堆顶;

叁、参考代码 ¶

# 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 5000
    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=3e5;
const ll inf=1ll<<50;

int n, tmpa[maxn+5], tmpb[maxn+5];
struct node { int a, b; };
vector <node> S, T;

inline void input() {
    readin(n), S.clear(), T.clear();
    rep(i, 1, n) readin(tmpa[i]);
    rep(i, 1, n) readin(tmpb[i]);
    rep(i, 1, n) {
        int a=tmpa[i], b=tmpb[i];
        if(a <= b) T.emplace_back(node{a, b});
        else S.emplace_back(node{a, b});
    }
    sort(whole(S), [](const node& x, const node& y) { return x.b<y.b; } );
    sort(whole(T), [](const node& x, const node& y) { return x.a<y.a; } );
}

ll f[maxn+5];
priority_queue < ll, vector<ll>, greater<ll> > Q;
inline void solveS() {
    while(!Q.empty()) Q.pop();
    ll pre=0, cur; int id=0;
    for(auto x: S) {
        /// the part lower than @p b[i]
        while(!Q.empty()) {
            cur=Q.top()+pre;
            if(cur > x.b) break;
            pre=cur, Q.pop();
            f[++id]=pre;
        }
        /// we're to change @p low , so need to update the @p Q first
        if(!Q.empty()) {
            cur=Q.top()+pre-x.b;
            Q.pop(), Q.push(cur);
        }
        Q.push(x.a-x.b), pre=x.b;
    }
    for(; !Q.empty(); Q.pop()) f[++id]=(pre+=Q.top());
}

ll g[maxn+5], d[maxn+5];
inline void solveT() {
    int id=0;
    for(auto x: T) {
        ++id;
        g[id]=max(x.a-g[id-1]-d[id-1], 0ll)+g[id-1];
        d[id]=d[id-1]+x.b-x.a;
    }
    g[id+1]=inf; /// deal with the special situation
}

const ll mod=1e9+7;
ll h[maxn+5];
inline void combine() {
    int Tsz=int(T.size()), Ssz=int(S.size()), cur=0;
    for(int i=0, j=0; i <= Tsz; ++i) {
        if(g[i] == g[i+1]) continue; // same cost
        ll r=g[i+1]-1+d[i];
        while(j < Ssz && f[j+1] <= r) ++j;
        rep(k, cur+1, i+j) {
            if(k<i) h[k]=g[i];
            else h[k]=max(f[k-i]-d[i], g[i]);
        }
        cur=i+j;
    }
    ll ans=0;
    rep(k, 1, n) ans=(ans+1ll*h[k]%mod*k%mod)%mod;
    writc(ans);
}

inline void work() {
    input();
    solveS();
    solveT();
    combine();
}

signed main() {
    // freopen("hunting-monsters.in", "r", stdin);
    // freopen("hunting-monsters.out", "w", stdout);
    rep(_, 1, readret(1)) work();
    return 0;
}
posted @ 2021-09-28 22:59  Arextre  阅读(259)  评论(0编辑  收藏  举报