#dp or 贪心+堆#CF704B Ant Man

题目


分析(dp)

考虑到对于一个排列单独抽出 \(1\sim i\) 可能会分成若干段,而贡献一定是固定的,不会影响之后的选择。

首先 \(a,c\) 加上 \(x\)\(b,d\) 减去 \(x\),那么贡献就转换为

\(\begin{cases}i<j,d_i+a_j\\i>j,c_i+b_j\end{cases}\)

同样考虑从小到大插入每一个数,

\(dp[i][j]\) 表示前 \(i\) 个数分成 \(j\) 段的最小权值。

\(i=St\),那么可以新开一段或者补到开头位置。

\(dp[i][j]=\min\{dp[i-1][j-1]+d_i,dp[i-1][j]+c_i(j>0)\}\)

\(i=Ed\),同理 \(dp[i][j]=\min\{dp[i-1][j-1]+b_i,dp[i-1][j]+a_i(j>0)\}\)

如果 \(i\) 不为开头或结尾,那么可以新开一段,合并两段,或者接在一段的开头或者结尾(但是不能替代 \(St\)\(Ed\)

新开一段就是 \(dp[i][j]=\min\{dp[i-1][j-1]+b_i+d_i\}\)

合并两段就是 \(dp[i][j]=\min\{dp[i-1][j+1]+a_i+c_i\}\)

接到开头就是 \(dp[i][j]=\min\{dp[i-1][j]+b_i+c_i\}(j>(i>St))\)

接到末尾就是 \(dp[i][j]=\min\{dp[i-1][j]+a_i+d_i\}(j>(i>Ed))\)

最后答案就是 \(dp[n][1]\)


代码

#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;
const int N=5011; typedef long long lll;
int n,st,ed,x[N],a[4][N]; lll dp[2][N];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void Min(lll &x,lll y){x=x<y?x:y;}
int main(){
	n=iut(),st=iut(),ed=iut();
	for (int i=1;i<=n;++i) x[i]=iut();
	for (int i=1;i<=n;++i) a[0][i]=iut()+x[i];
	for (int i=1;i<=n;++i) a[1][i]=iut()-x[i];
	for (int i=1;i<=n;++i) a[2][i]=iut()+x[i];
	for (int i=1;i<=n;++i) a[3][i]=iut()-x[i];
	memset(dp[0],0x3f,sizeof(dp[0])),dp[0][0]=0;
	for (int i=1;i<=n;++i){
		memset(dp[i&1],0x3f,sizeof(dp[i&1]));
		if (i==st){
			for (int j=(i>ed);j<i;++j){
				if (j) Min(dp[i&1][j],dp[(i&1)^1][j]+a[2][i]);
				Min(dp[i&1][j+1],dp[(i&1)^1][j]+a[3][i]);
			}
		}else if (i==ed){
			for (int j=(i>st);j<i;++j){
				if (j) Min(dp[i&1][j],dp[(i&1)^1][j]+a[0][i]);
				Min(dp[i&1][j+1],dp[(i&1)^1][j]+a[1][i]);
			}
		}else{
			for (int j=(i>st)+(i>ed);j<i;++j){
				if (j>1) Min(dp[i&1][j-1],dp[(i&1)^1][j]+a[0][i]+a[2][i]);
				Min(dp[i&1][j+1],dp[(i&1)^1][j]+a[1][i]+a[3][i]);
				if (j>(i>st)) Min(dp[i&1][j],dp[(i&1)^1][j]+a[2][i]+a[1][i]);
				if (j>(i>ed)) Min(dp[i&1][j],dp[(i&1)^1][j]+a[0][i]+a[3][i]);
			}
		}
	}
	return !printf("%lld",dp[n&1][1]);
}

分析(贪心)

首先填入首尾,为了有最小值出现,同时将 1 填入。

还是一样从小到大填数,不过先考虑加上 \(a_i+c_i\)

其它数必然是接在中间,考虑直接用选完的数接上,

那么就是要找到 \(b_i-a_i\) 或者是 \(d_i-c_i\) 的最小值,用堆维护即可。


代码

#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;
const int N=5011; typedef long long lll;
int n,st,ed,x[N],Cnt; lll ans,heap[N<<1],a[4][N];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void Swap(lll &x,lll &y){x^=y^=x^=y;}
void Push(lll W){
	heap[++Cnt]=W;
	for (int x=Cnt;x>1;x>>=1)
	if (heap[x]<heap[x>>1])
	    Swap(heap[x],heap[x>>1]);
	else return;
}
void Pop(){
	heap[1]=heap[Cnt--];
	for (int x=1;(x<<1)<=Cnt;){
		int y=x<<1|1;
		if (y>Cnt||heap[y]>=heap[y-1]) --y;
		if (heap[x]<=heap[y]) return;
		Swap(heap[x],heap[y]),x=y;
	}
}
int main(){
	n=iut(),st=iut(),ed=iut();
	for (int i=1;i<=n;++i) x[i]=iut();
	for (int i=1;i<=n;++i) a[0][i]=iut()+x[i];
	for (int i=1;i<=n;++i) a[1][i]=iut()-x[i];
	for (int i=1;i<=n;++i) a[2][i]=iut()+x[i];
	for (int i=1;i<=n;++i) a[3][i]=iut()-x[i];
	if (st>1&&ed>1) ans=a[2][st]+a[1][1]+a[3][1]+a[0][ed];
	    else ans=(st>ed)?(a[2][st]+a[1][ed]):(a[3][st]+a[0][ed]);
	for (int i=2;i<=n;++i)
	if (i==st) Push(a[3][i]-a[2][i]);
	else if (i==ed) Push(a[1][i]-a[0][i]);
	else{
	    ans+=a[0][i]+a[2][i];
	    if (i<st) Push(a[1][i]-a[0][i]);//可以与st接在一起
	    if (i<ed) Push(a[3][i]-a[2][i]);//可以与ed接在一起
	    ans+=heap[1],Pop();
	    if (i>st) Push(a[1][i]-a[0][i]);
	    if (i>ed) Push(a[3][i]-a[2][i]);
	}
	return !printf("%lld",ans);
}
posted @ 2022-01-11 16:57  lemondinosaur  阅读(28)  评论(0编辑  收藏  举报