正题
题目链接:https://www.luogu.com.cn/problem/AT4519
题目大意
给出一个长度为nnn的排列,每次可以选择一个区间,然后花费AAA的代价向左旋转(最左边的丢到最右边)或者花费BBB的代价向右旋转。
排升序序的最小花费。
1≤n≤50001\leq n\leq 50001≤n≤5000
解题思路
相当于向右丢和向左丢。因为位置不固定非常麻烦,我们可以考虑统计那些顺序固定的。
设fif_ifi表示做到第iii个且第iii个不动的最小花费,然后考虑fjf_jfj转移到fif_ifi时的代价,那么显然我们要把中间数都变成在aja_jaj到aia_iai之间,所以把其中所有大于aia_iai的往右丢,小于aja_jaj的往左丢。
这样可以做到O(n3)O(n^3)O(n3)或者用数据结构做到O(n2logn)O(n^2\log n)O(n2logn)
但是我们考虑到小于aja_jaj的同时也是小于aia_iai的,如果我们把所有小于aia_iai的都往左丢,这样不会出现更小的答案,所以不会被统计到里面。
这样就只和aia_iai有关了,倒序枚举jjj然后O(n2)O(n^2)O(n2)转移即可。
用线段树可以做到O(nlogn)O(n\log n)O(nlogn)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5100;
ll n,A,B,a[N],f[N];
signed main()
{scanf("%lld%lld%lld",&n,&A,&B);for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);memset(f,0x3f,sizeof(f));a[0]=-1;++n;a[n]=n;f[0]=0;for(ll i=1;i<=n;i++){ll up=0,dn=0;for(ll j=i-1;j>=0;j--){if(a[j]<a[i])f[i]=min(f[i],f[j]+up*A+dn*B);if(a[j]>a[i])up++;else dn++;}}printf("%lld\n",f[n]);
}