D. Armchairs——Educational Codeforces Round 109 (Rated for Div. 2)

https://codeforces.com/contest/1525/problem/D
D. Armchairs
time limit per test2 seconds
memory limit per test512 megabytes
inputstandard input
outputstandard output
There are n armchairs, numbered from 1 to n from left to right. Some armchairs are occupied by people (at most one person per armchair), others are not. The number of occupied armchairs is not greater than n2.

For some reason, you would like to tell people to move from their armchairs to some other ones. If the i-th armchair is occupied by someone and the j-th armchair is not, you can tell the person sitting in the i-th armchair to move to the j-th armchair. The time it takes a person to move from the i-th armchair to the j-th one is |i−j| minutes. You may perform this operation any number of times, but these operations must be done sequentially, i. e. you cannot tell a person to move until the person you asked to move in the last operation has finished moving to their destination armchair.

You want to achieve the following situation: every seat that was initially occupied must be free. What is the minimum time you need to do it?

Input
The first line contains one integer n (2≤n≤5000) — the number of armchairs.

The second line contains n integers a1,a2,…,an (0≤ai≤1). ai=1 means that the i-th armchair is initially occupied, ai=0 means that it is initially free. The number of occupied armchairs is at most n2.

Output
Print one integer — the minimum number of minutes you have to spend to achieve the following situation: every seat that was initially occupied must be free.

Examples
inputCopy
7
1 0 0 1 0 0 1
outputCopy
3
inputCopy
6
1 1 1 0 0 0
outputCopy
9
inputCopy
5
0 0 0 0 0
outputCopy
0
Note
In the first test, you can perform the following sequence:

ask a person to move from armchair 1 to armchair 2, it takes 1 minute;
ask a person to move from armchair 7 to armchair 6, it takes 1 minute;
ask a person to move from armchair 4 to armchair 5, it takes 1 minute.
In the second test, you can perform the following sequence:

ask a person to move from armchair 1 to armchair 4, it takes 3 minutes;
ask a person to move from armchair 2 to armchair 6, it takes 4 minutes;
ask a person to move from armchair 4 to armchair 5, it takes 1 minute;
ask a person to move from armchair 3 to armchair 4, it takes 1 minute.
In the third test, no seat is occupied so your goal is achieved instantly.

分析:
明显是DP啊!!!!!往这方面去想

所谓的DP,就是剪枝暴力
从最开始的斐波那契数列就是这样子

首先,dp变量,这里的两个变量是人数和空位数,它们的相对距离可以abs(i-j)求。
(存一个b[m]方便看人位置,其实要存一个c[n]看空位位置也可以,但不能一起没有)
二重循环,正确就可以

刚开始就觉得是了,有旅行商问题的感觉,其实是的,找到特定唯一状态,打表求解
(这里的特定状态f[j]是指 当加第i个空位,把第j个人放到第i个空位,j<=i &&j<=m,
当i还很小,j就一个一个贴着i,用到了不可能j在i前面,不然显然有优化的)
(比较的是f[j],f[j-1]+abs(b[j]-i),
即是不是要把前j-1个人安排在前i-1个空位的基础上把第j个人安排在第i位
这和背包很像,但是话说回来所有的dp都是建立在之前的基础上的)
——有序,可以代替排列。有序加空位,在其中有序安排前若干个人。
(若有序加人,在其中有序安排空位,就有空位利用不全的问题,若所有安排空位,就是我想不出来的原因了。
所以说,二重循环换个方向换个顺序,可能就会了。这里(人数<空位数),人数有序无妨,反正也都用到了。)

注意在每加第i个空位看第j个人时,要用到前j-1个人的在没加这第i个空位的状态
——这决定了循环顺序:1~ i内j~1,防止第j个以前被更新

状态转移方程:
f[j]=min(f[j],f[j-1]+abs(b[j]-i))
b[j]存的是第j个人的位置

毕竟是游戏,开心就好
我们联合,两面包夹芝士!!~~

由于我发现自己总是补题不熟悉,所以我决定打两遍!
极简版:

#include<bits/stdc++.h>
using namespace std;
int c[5005],b[5005],f[5005];
int main(){
	int n;
	scanf("%d",&n);
	int te,cb=0,cc=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&te);
		(te?b[++cb]:c[++cc])=i;
	}
	memset(f+1,0x3f,sizeof(int)*cb); 
	for(int i=1;i<=cc;i++){
		for(int j=min(cb,i);j>=1;j--){
			f[j]=min(f[j],f[j-1]+abs(b[j]-c[i]));
		}
	}
	printf("%d",f[cb]);
}

第一版:

//#pragma GCC optimize(3,"Ofast","inline")
//#pragma GCC optimize(2)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<iostream>
#include<algorithm>
#include<bitset>
//#include<string>
//#include<sstream>
#include<vector>
#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
//#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define vl vector<ll>
#define st first
#define nd second
#define pb push_back
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
#define _mem(a,b,c) memset(a,b,sizeof(a[0])*c)
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define _forauto(a,b) for(auto &(a):(b))
#define _forautome(a,b,c) for(auto (a) = (b); (a) != (c); (a)++)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
//参考:set<int>::iterator iter = vis.begin();
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
//#define int ll

/*
明显是DP啊!!!!!往这方面去想

所谓的DP,就是剪枝暴力
从最开始的斐波那契数列就是这样子 

首先,dp变量,这里的两个变量是人数和空位数,它们的相对距离可以abs(i-j)求。
(存一个b[m]方便看人位置,其实要存一个c[n]看空位位置也可以,但不能一起没有) 
二重循环,正确就可以 

刚开始就觉得是了,有旅行商问题的感觉,其实是的,找到特定唯一状态,打表求解
(这里的特定状态f[j]是指 当加第i个空位,把第j个人放到第i个空位,j<=i &&j<=m,
当i还很小,j就一个一个贴着i,用到了不可能j在i前面,不然显然有优化的) 
(比较的是f[j],f[j-1]+abs(b[j]-i),
即是不是要把前j-1个人安排在前i-1个空位的基础上把第j个人安排在第i位
这和背包很像,但是话说回来所有的dp都是建立在之前的基础上的) 
——有序,可以代替排列。有序加空位,在其中有序安排前若干个人。
(若有序加人,在其中有序安排空位,就有空位利用不全的问题,若所有安排空位,就是我想不出来的原因了。
所以说,二重循环换个方向换个顺序,可能就会了。这里(人数<空位数),人数有序无妨,反正也都用到了。) 

注意在每加第i个空位看第j个人时,要用到前j-1个人的在没加这第i个空位的状态
——这决定了循环顺序:1~i内j~1,防止第j个以前被更新 

状态转移方程:
f[j]=min(f[j],f[j-1]+abs(b[j]-i))
b[j]存的是第j个人的位置 

毕竟是游戏,开心就好 
*/ 
bool a[5005];
int b[5005],c[5005],f[5005];
int32_t main(){
	fastio
	int n;
	cin>>n;
	int cb=0,cc=0;
	_forplus(i,1,n){
		cin>>a[i];
		if(a[i])b[++cb]=i;
		else c[++cc]=i;
	}
	_forplus(i,1,cc){
		f[i]=INF;//刚开始f[0]为0,不是全局变量就不能不写该初始条件,即安排0个人,无论多少空位 
		_forsub(j,min(cb,i),1){
			f[j]=min(f[j],f[j-1]+abs(b[j]-c[i]));
		}
	}
	cout<<f[cb]<<'\n';
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值