标签:意思 复杂度 ons fine 故事 print include lin 情况
30pts做法:暴力or你的dp写挂(我就dp写挂了)
50pts做法:在dp里面注意一点,\(2^{cnt}\)可能会爆long long!那些求出来的答案明显爆long long的直接用整个序列的和\(10^{10}\)取min。
满分做法:
\(\lceil log_2{10^10} \rceil = 34\),意思是第34次以及以后,不用魔法,都大于整个序列的最大和。所以后面一定要用魔法了,不用肯定不优秀。
所以设\(dp[i]\)为用了\(i\)次魔法的最短时间。这里滚动掉了一维,所以枚举用魔法的次数要倒推枚举。
所以魔法枚举到\(33\)为止,后面的直接视为一种情况,直接在原第33次使用魔法的时间基础上加上\(\sum_{i=34}{i+log_2{a_i}}\),这个东西可以\(O(1)\)地求出来。
所以复杂度是\(O(nlogn)\)的。。。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
const int maxn = 100005;
const ll maxv = 1e10;
const int lim = 34;
ll a[maxn], n;
ll luog[maxn];
ll dp[maxn];
ll ans = 1e18;
ll read()
{
ll ans = 0, s = 1;
char ch = getchar();
while(ch > ‘9‘ || ch < ‘0‘){ if(ch == ‘-‘) s = -1; ch = getchar(); }
while(ch >= ‘0‘ && ch <= ‘9‘) ans = ans * 10 + ch - ‘0‘, ch = getchar();
return s * ans;
}
int main()
{
memset(dp, 0x3f, sizeof dp);
n = read();
for(int i = 1; i <= n; i++)
{
a[i] = read();
luog[i] = luog[i - 1] + (int)(log2(a[i]));
ans += a[i];
}
dp[0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = lim - 1; j >= 1; j--)
{
if((maxv >> j) >= a[i]) dp[j] = std::min(dp[j] + (a[i] << j), dp[j - 1] + (int)(log2(a[i])) + j - 1);
else dp[j] = dp[j - 1] + (int)(log2(a[i])) + j - 1;
}
dp[0] += a[i];
ans = std::min(ans, dp[lim - 1] + luog[n] - luog[i] + (n - i) * (2 * lim + n - i - 3) / 2);
}
for(int i = 0; i < lim; i++) ans = std::min(ans, dp[i]);
printf("%lld\n", ans);
return 0;
}
标签:意思 复杂度 ons fine 故事 print include lin 情况
原文地址:https://www.cnblogs.com/Garen-Wang/p/9873668.html