CF710E Generate a String

题面

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;
}
原文地址:https://www.cnblogs.com/Arielzz/p/15488525.html