solution
有很多做法的一道题。
根据题意,一个很显然的 (dp)。
设 (f_{i}) 表示生成长度为 (i) 的序列的最小代价。
转移:
若 (i) 是奇数:
(f_{i} = min{f_{i - 1} + x, f_{i + 1} + x})
若 (i) 是偶数:
(f_{i} = min{f_{i - 1} + x, f_{i + 1} + x, f_{i / 2} + x})
solution1
你会发现这个 (dp) 转移成环,所以没法转移。
考虑把状态看作点,转移看作状态之间连边,跑最短路。
题解代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define mp make_pair
using namespace std;
typedef long long LL;
const int maxn=20000005;//可能会先超过再删到N.
LL dis[maxn];
int N,x,y,tot;
bool vis[maxn];
priority_queue<pair<LL,int> > Q;
LL Dij()
{
memset(dis,0x3f,sizeof(dis));
dis[1]=x;
Q.push(mp(-dis[1],1));
while(!Q.empty())
{
int q=Q.top().second;Q.pop(),vis[q]=true;
if(q==N) return dis[N];
if(q>1&&!vis[q-1]&&dis[q-1]>dis[q]+x) Q.push(mp(-(dis[q-1]=dis[q]+x),q-1));
if(q<N&&!vis[q+1]&&dis[q+1]>dis[q]+x) Q.push(mp(-(dis[q+1]=dis[q]+x),q+1));
if(q<N&&!vis[q<<1]&&dis[q<<1]>dis[q]+y) Q.push(mp(-(dis[q<<1]=dis[q]+y),q<<1));
while(!Q.empty()&&vis[Q.top().second]) Q.pop();
}
return dis[N];
}
int main()
{
scanf("%d%d%d",&N,&x,&y);
printf("%I64d",Dij());
return 0;
}
solution2
分析一下会发现,删除操作只有在进行翻倍操作之后,翻超了才会进行删除操作。
然后就可以改一下转移方程了:
(f_{i} = min{f_{i - 1} + x, f_{k} + (2 imes k - i) imes x + y})
其中 (2 imes k > i && k < i)
把上面的柿子展开
[f_{i} = min{f_{i - 1} + x, f_k + 2kx - ix + y}
]
你会发现 (f_k + 2kx) 这一项是只和 (k) 有关的柿子,这个用单调队列维护就好了。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e7 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, x, y, f[MAXN], head = 1, tail, q[MAXN];
void Delet(int i) {
while(head <= tail && q[head] * 2 < i) head++;
}
void Insert(int i) {
while(head <= tail && f[q[tail]] + 2ll * q[tail] * x >= f[i] + 2ll * i * x) tail--;
q[++tail] = i;
}
signed main() {
n = read(), x = read(), y = read();
f[0] = 0;
for (int i = 1; i <= n; i++) {
Delet(i);
f[i] = f[i - 1] + x;
if(head <= tail) f[i] = min(f[q[head]] + (2ll * q[head] - i) * x + y, f[i]);
Insert(i);
}
cout<<f[n];
return 0;
}
solution3
你再仔细分析一下。
每个删除操作不会连续进行两次。
因为进行两次删除操作不如在翻倍前进行一次删除操作更优。
所以当 (i) 是偶数的话不会由删除操作转移:
(f_i = min{f_{i - 1} + x, f_{i / 2} + y})
当 (i) 是奇数的话:
(f_i = min{f_{i - 1} + x, f_{(i + 1) / 2} + y + x})
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e7 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, x, y, f[MAXN];
signed main(){
n = read(), x = read(), y = read();
f[0] = 0;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + x;
if(!(i & 1)) f[i] = min(f[i / 2] + y, f[i]);
else f[i] = min(f[(i + 1) / 2] + x + y, f[i]);
}
cout<<f[n];
return 0;
}