Educational Codeforces Round 167 (Rated for Div. 2) D(dp,贪心)
前言
这场比较容易,但是D没在考场上做出来。考场上一直在找规律求解 \(f_i\) 数组,确没去想这是一个dp。第二天早上突然发现这题是dp。www。
A,B,C 太水了就不提了。
D
把这个问题简化为:给你 \(n\) 对关系,每对关系形如 \((a_i, a_i-b_i)\),意思是如果你现在手上的值 \(val\),\(val \ge a_i\) 那么可以使得 \(val -= a_i-b_i\) 从而收获 2 的贡献。((使用条件,步长))然后会给你 \(m\) 个初始值 \(c_j\),求你可以获得的最大贡献。(每个初始值相互独立)
数据范围:\(1 \le a_i,b_i \le 10^6\),\(1 \le c_j \le 10^9\)
由这个数据范围的特性容易想到先预处理 \(\max\{a_i\}\) 以内的,对于 \(c_j>\max\{a_i\}\) 的,用贪心走最短的步长(获得最大的贡献)走到 \(<\max\{a_i\}\)。
那么设 \(f_i\) 表示初始值为 \(i\) 可以走的最大贡献。将 \(n\) 对关系放入一个桶数组,下标表示使用条件(\(a_i\)),存最短的步长(\(\min\{a_i-b_i\}\))(最有可能使得贡献值最大),在状态转移前同时更新当前可用最短步长(\(s\)),那么状态转移方程为:
\(f[i] = f[i-s]+1\)
最后统计答案,对于每个 \(c_j\):如果 \(c_j \le \max\{a_i\}\),那么直接累计 \(f[c_j]\) 进入答案;如果 \(c_j > \max\{a_i\}\),先减去若干倍(\(cnt\))全局最小步长,得到一个最大的 \(k\),使得 \(k \le \max\{a_i\}\),那么对答案的贡献即 \(cnt+f[k]\)
时间复杂度 \(O(n)\)
Talk is cheap.Show me the code.
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
return x * f;
}
const int N = 1e6+7;
int n,m;
int a[N],b[N],f[N];
int t[N];
signed main()
{
n = read(), m = read();
int mx = 0;
for(int i=1;i<=n;++i) a[i] = read(), mx = max(mx, a[i]);
for(int i=1;i<=n;++i) b[i] = read();
for(int i=1;i<=n;++i) {
t[a[i]] = (!t[a[i]] ? a[i]-b[i] : min(t[a[i]], a[i]-b[i]));
}
int step = 0;
for(int i=1;i<=mx;++i) {
if(t[i]) step = (!step ? t[i] : min(step, t[i]));
if(step) f[i] = f[i-step] + 1;
}
int ans = 0;
while(m--) {
int c = read();
if(c > mx) {
int d = step;
int cnt = (c-mx-1)/d + 1;
ans += cnt + f[c-cnt*d];
} else {
ans += f[c];
}
}
printf("%lld\n",ans*2);
return 0;
}