Toyota Programming Contest 2023#7(AtCoder Beginner Contest 328)G. Cut and Reorder(状压dp)

题目

长为n(n<=22)的序列a和b,第i个数分别对应ai和bi(1<=ai<=1e15,1<=bi<=1e15)

你可以执行以下两种操作任意次:

1. 将a序列分成若干段,然后可以任意重排这些段,

如果将a序列分成了x段,则代价是(x-1)*c,

比如,可以分成a1,a2a3,a4a5a6,然后重排为a2a3,a4a5a6,a1,代价是2*c

2. 选择a的任意一个位置,对其加k,代价是k的绝对值

求最小的总代价,使得a和b序列完全相等

思路来源

liouzhou101 & 官方题解

乱搞AC

题解

首先,考虑如果a的位置和b的位置在排列的位置上一一映射上了,

那么,比如a[i]和b[j]映射上了,那么通过第二种操作相等的额外代价就是abs(a[i]-b[j])

也就是说,这个分成x段并重排的操作只需要进行一次,剩下的都用第二种操作

所以,状压这个第一种操作的顺序

然后发现,如果A|BC|DE被重排成了DE|BC|A, 

也就是B在新序列的位置和A的位置不再前后相邻,D在新序列的位置和A的位置不再前后相邻

所以,一个比较自然的做法是,增序考虑选a的过程

假设当前选到了第i个,那么S集合内1的个数一定是i,

选择a的第i个的时候,需要考虑a的第i-1个选的是什么,从而判断二者是否前后相邻

于是就有,dp[S][i]表示(当前选了a序列前bit[S]个数),当前选择的b的集合是S,

当前a的最后一个数对应的b中的数选的是第i个时,二者只考虑这些数时完全相同的最小代价

那么转移就是如果上一个选的和当前选的前后相邻,就不额外创建一段

否则需要新起一段,加上c的代价,暴力这样做的复杂度O(n*n*2^n)

然后发现,假设前驱转移有x个,那么只有一个是不额外创建的,x-1个是额外创建的

不妨松弛一下答案,

也就是当成x个是额外创建的,1个是不额外创建的,

维护一下这x个前驱的最小值mn

用mn更新一次额外创建的答案,用这一个前驱更新一次不额外创建的答案,转移就O(1)了

复杂度O(n*2^n)

插曲(卡空间之乱搞AC)

交的时候,发现MLE了,因为空间也开了O(n*2^n)

时间给了2.8s,空间给了512M,但是22*2的22次方大概是700多M,long long改不了

把long long改成动态开的指针和回收结果TLE了

然后考虑把dp状态改成可回收的节点,用的时候取一个节点,不用的时候放回去

把空间压了压,相当于根据位从少到多bfs,

少的位的状态用过之后就从队列里干掉,达到复用空间的效果,类似循环队列

但是这么做肯定不是题目想让的样子,于是看了官方题解&别人的提交

正解

dp[S]表示当前选的集合是S,一次决策一段,把一段对应的代价c加上去

也就是取一段连续的a的区间[l,r],往b当前的集合中,没取并且也连续的一段区间里放

由于长为k的转移会出现2^{n-k}次,所以复杂度还是O(n*2^n)

手玩的时候没细想,以为多一个n

由于状态是从0开始拓展的,也就是拓展第一段的时候也会多算一个c,所以最终答案减c

代码1(正解)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define scll(a) scanf("%lld",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=22,S=(1<<22)+5;
int n;
ll c,a[N],b[N],dp[S];
void upd(ll &x,ll y){
	x=min(x,y);
}
bool has(int i,int j){
	return i>>j&1;
}
int main(){
	sci(n),scll(c);
	rep(i,0,n-1){
		scll(a[i]);
	}
	rep(i,0,n-1){
		scll(b[i]);
	}
	memset(dp,0x3f,sizeof dp);
	int up=(1<<n)-1;
	dp[0]=0;
	rep(i,0,up-1){
		int bit=0;
		rep(j,0,n-1){
			if(i>>j&1)bit++;
		}
		rep(j,0,n-1){
			if(!has(i,j)){
				ll v=dp[i]+c;
				int nex=i,x=0;
				while(j+x<n && !has(i,j+x)){
					v+=abs(b[j+x]-a[bit+x]);
					nex|=(1<<(j+x));
					upd(dp[nex],v);
					x++;
				}
			}
		}
	}
	ptlle(dp[up]-c);
	return 0;
}

代码2(乱搞AC)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define scll(a) scanf("%lld",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=22,S=(1<<22)+5,Q=(1<<21)+5;
int n,id[S];
ll c,a[N],b[N],dp[Q][N];
vector<int>q;
void upd(ll &x,ll y){
	x=min(x,y);
}
int f(int v){
	if(~id[v])return id[v];
	int x=q.back();
	q.pop_back();
	memset(dp[x],0x3f,sizeof dp[x]);
	return id[v]=x;
}
void g(int x){
	q.push_back(id[x]);
}
bool has(int i,int j){
	return i>>j&1;
}
int main(){
	sci(n),scll(c);
	rep(i,0,n-1){
		scll(a[i]);
	}
	rep(i,0,n-1){
		scll(b[i]);
	}
	memset(id,-1,sizeof id);
	int up=(1<<n)-1;
	rep(i,0,Q-1)q.pb(i);
	rep(i,0,n-1){
		dp[f(1<<i)][i]=abs(b[i]-a[0]);
	}
	rep(i,1,up-1){
		ll mx=8e18;
		int bit=0;
		rep(j,0,n-1){
			if(has(i,j)){
				mx=min(mx,dp[f(i)][j]);
				bit++;
			}
		}
		rep(j,0,n-1){
			if(!has(i,j)){
				upd(dp[f(i|(1<<j))][j],mx+c+abs(b[j]-a[bit]));
				if(j-1>=0 && has(i,j-1)){
					upd(dp[f(i|(1<<j))][j],dp[f(i)][j-1]+abs(b[j]-a[bit]));
				}
			}
		}
		g(i);
	}
	ll ans=8e18;
	rep(j,0,n-1){
		ans=min(ans,dp[f(up)][j]);
	}
	ptlle(ans);
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值