\(\sf{Description}\)

传送门

\(\sf{Solution}\)

\(\text{Subtask 1}\)

保证 \(m=1\)

定义 \(f[i][j]\) 为前 \(i\) 棵竹子砍了 \(j\) 次剩下的最高竹子的最小值,就有一个 \(\mathcal O(nk^2)\)\(\mathtt{dp}\)

\[f[i][j]=\min\{\max\{f[i-1][k],h[i]+a[i]-(j-k)\times p\}\} \]

\(\text{Subtask 2}\)

最大值最小应该想到二分。

因为有竹子会砍没的情况,所以不好贪心。

正难则反(话说自己根本想不到好吗),倒着考虑:一开始竹子高度全为 \(\rm mid\),每天竹子会长矮 \(a_i\),你可以选择 \(k\) 个拔高 \(p\),要求 过程中 不能 \(< 0\) 且最终所有竹子均 \(\geq h_i\)(倒着来就是先长矮再拔高)。

为什么不能 \(< 0\)?这相当于从土地里面长出来,意味着高度被砍成负数。所以当竹子高度 \(<0\) 时,设此时刻 加一 的时刻为 \(t\),我们必须在 \([t,m]\) 时刻区间内将竹子拔高。另外,从这里也可以看出越后面的拔高次数越珍贵,我们需要尽量拖延到 "非拔不可" 的时候拔高。所以每棵竹子的生长函数也是 唯一 的。

为什么保证最终所有竹子均 \(\geq h_i\)?可以画一张图,将天数为 \(x\) 坐标,将高度为 \(y\) 坐标,把满足以上条件的 \(i\) 竹子的生长函数画出来(还要保证以 \((m,\rm mid)\) 为终点)。如果生长函数的初始位置 \((0,y)\)\(y<h_i\),就需要将整个函数向上平移到 \((0,h_i)\) 才是这棵竹子的生长轨迹,这样你会发现它最终比 \(\rm mid\) 高了,这是不合法的。 所以,在计算出生长函数后,我们需要再拔高竹子使得 \(y\ge h_i\),这时使用的拔高次数就可以使用 \(m\) 天中的任何一天了。

你可能会疑惑。我又 tm 在这里搞了很久,考场上硬是打了一半没打了。

为什么计算出每棵竹子的生长函数后,可以用这个生长函数来生长这棵竹子呢?毕竟,保证了 \(y\ge h_i\),也就意味着将生长函数应用在这棵竹子上,竹子的生长轨迹是生长函数向下平移的图像。而题目中要求砍竹子时要和 \(0\)\(\max\),不会出问题吗?事实上,当把竹子砍成 \(0\) 时,我们就让它变成 \(0\),这肯定比同样时刻生长函数的 \(y\) 坐标更优了!

具体实现时,可以朴素地 \(\mathcal O(nm\log V)\) 来计算。更好的解法是将 \(n\) 棵竹子的 \(m\) 天都放在一起算:因为总共砍伐次数只有 \(mk\),开 \(m\) 个队列,第 \(i\) 个队列记录第 \(m-i+2\) 天必须砍的竹子。这是 \(\mathcal O((n+mk)\cdot \log V)\) 的。

\(\sf{Code}\)

#include <cstdio>

#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define print(x,y) write(x),putchar(y)

typedef long long ll;

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}

#include <queue>
#include <iostream>
using namespace std;
typedef pair <int,int> Pair;

const int maxn=1e5+5,maxm=5005;

int n,m,k,p,h[maxn],a[maxn];
ll ans;
queue <Pair> q[maxm]; 

bool ok(ll x) {
	ll t; Pair u;
	rep(i,1,m) while(!q[i].empty()) q[i].pop();
	rep(i,1,n) {
		t=x/a[i]+1;
		if(t<=m) q[t].push(make_pair(i,0));
	}
	for(int i=1,Cut=0;i<=m;++i,Cut+=k) {
		while(!q[i].empty()) {
			if(Cut) --Cut;
			else return 0;
			u=q[i].front(); q[i].pop(); ++u.second;
			t=(x+1ll*p*u.second)/a[u.first]+1;
			if(t<=m) q[t].push(u);
		}
	}
	t=0;
	rep(i,1,n)
		t+=(max(0ll,1ll*a[i]*m+h[i]-x)+p-1)/p;
	return t<=1ll*m*k;
}

int main() {
	n=read(9),m=read(9),k=read(9),p=read(9);
	rep(i,1,n) h[i]=read(9),a[i]=read(9);
	ll l=0,r=0,mid;
	rep(i,1,n) r=max(r,1ll*a[i]*m+h[i]);
	while(l<=r) {
		mid=l+r>>1;
		if(ok(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	print(ans,'\n');
	return 0;
}
posted on 2021-09-18 23:06  Oxide  阅读(37)  评论(0编辑  收藏  举报