Online Judge:YCJSOI

Label:Dp,思维题,预处理,滚动优化

题目描述

乐乐现在掌管一个大公司,办公楼共有n层。为了增加员工的身体素质,他决定在每层楼都建立一个活动室,活动室分乒乓球和排球两种。

已知每层楼喜欢乒乓球和排球的人数。

每个人的行走楼层数是他去自己喜欢的运动室的楼层数。

请你帮乐乐算算,每层楼应该建乒乓球还是排球,使得所有人行走楼层数总和最小。

输入

第一行一个整数n,表示楼层数量。

接下来n行 ,每行两个整数a和b,表示喜欢乒乓球和排球的人数。

输出

输出一个整数,表示所有人行走楼层总和的最小值。

样例

Input#1

2
10 5
4 3

Output#1

9

Input#2

15
68 42
1 35
25 70
59 79
65 63
46 6
28 82
92 62
43 96
37 28
5 92
54 3
83 93
17 22
96 19

Output#2

506

Hint

第一层建乒乓球室,第二层建排球室。行走楼层为5+4=9

对于30%的数据,n的范围\([2,20]\);;

对于80%的数据,n的范围\([2,500]\);

对于100%的数据,n的范围\([2,4000]\),每层楼喜欢乒乓球和排球的人数范围\([1,10^5]\)。

题解

看数据范围应该是个Dp。

Ⅰ状态定义

一开始看题,不难定义出这样一个状态\(dp[co=0/1][i][j]\),意义是,已经决策完了前i层建什么,且\([j+1,i]\)这些层建的都是\(co\),也就是\(j\)这一层建的是\(co\)^\(1\),此时的最小代价。

但有一个问题,\([j+1,i]\)这些层喜欢\(co\)^\(1\)的人,他们一定是去\(j\)这层吗?不一定,可能还可以去i后面的某点,所以我们无法在当前状态中得知那个最小代价,我们能求得的只有\([1,j]\)这些层所有人的代价+\([j+1,i]\)这些层中喜欢\(co\)的人的代价(为0)。

说人话就是,如果我在\([j+1,i]\)这些层都建乒乓球室,1.那么\([j+1,i]\)这些层中打乒乓球的人都不用移动在本层打就好了(代价为0),2.在\([1,j]\)这些层的人不论他喜欢乒乓球还是排球都可以在i之前做出决策,3.在\([j+1,i]\)这些层中喜欢排球的人无法在当前层状态中做出决策,因为他们可以选择去第\(j\)层打排球,但如果楼上还有更近的排球管呢?我们在当前状态中无法预知。

所以改一下意义,它表示的是[1,j]这些层所有人的代价+[j+1,i]这些层中喜欢co​的人的代价


Ⅱ转移

初始化如下

memset(dp,-1,sizeof(dp));//相当于赋一个极大值
dp[0][1][0]=dp[1][1][0]=0;

转移采用填表方式,更新最小值。

for(i=1;i<n;++i)for(j=0;j<i;++j)for(x=0;x<=1;++x)if(~dp[x][i][j]){
//枚举当前层i,[j+1,i]都建x
for(y=0;y<=1;++y){//枚举第i+1层建什么
if(x==y)Do(dp[y][i+1][j],dp[x][i][j]);//如果建的和i相同
else{
ll val=calc(y,j+1,i);
if(~val)Do(dp[y][i+1][i],dp[x][i][j]+val);
}
}
}

主要看到\(x!=y\)时的情况,我们此时在中间\([j+1,i]\)建了\(x\),在两端\(j,i+1\)建了\(y(!x)\),那个\(calc(co,l,r)\)函数,求的就是\([l,r]\)这些层中喜欢\(co\)的人,分别去到\(l-1,r+1\)的最小代价。

很明显以中点为边界,左半边的去\(l-1\),右半边的去\(r+1\)最优吧。

最low的求法应该就是下面这种\(O(N)\)的:

虽然非常暴力,但一定要注意下面几处特判。

inline ll calc(int co,int l,int r){
ll sum=0;
register int i,mid=(l+r)>>1;
if(l==1&&r==n)return 1e9;//左右都不能去
if(l==1){//左边不能去
for(i=l;i<=r;++i)sum+=(r-i+1)*a[co][i];
return sum;
}
if(r==n){//右边不能去
for(i=l;i<=r;i++)sum+=(i-l+1)*a[co][i];
return sum;
}
for(i=l;i<=mid;++i)sum+=(i-l+1)*a[co][i];//左半边的去l-1层
for(i=mid+1;i<=r;++i)sum+=(r-i+1)*a[co][i];//右半边的去r+1层
return sum;
}

最后的统计答案如下。别忘了还要加上最后一段代价。

for(x=0;x<=1;++x)for(j=1;j<n;++j){
if(~dp[x][n][j])Do(ans,dp[x][n][j]+calc(x^1,j+1,n));
}

这样,大致的算法流程就结束了。但是也许在上面代码中你就发现了,空间、时间对于100%数据都承受不了(时间复杂度为\(O(4\cdot N^3)\)),只能过掉80%数据。


Ⅲ优化·空间

空间的话,主要是原来的\(dp[2][N][N]\)数组内存好像有点玄,所以求稳优化一波。由于每个\(i\)只用到上一层状态,所以直接滚动就好了,变成dp[2][2][N]

Ⅲ优化·时间

时间呢,感觉那个calc(co,l,r)函数非常可优化的样子。在草稿纸上列了一下,发现可以利用差分思想,\(O(N)\)预处理前缀/后缀,然后\(O(1)\)计算出结果。

举个例子:对于求\([l,r]\)到它\(l-1\)的代价

比如\(l=4,r=7\)的情况:

列式为\(cost=a[4]*1+a[5]*2+a[6]*3+a[7]*4\)。

我们预处理一个前缀值:isum[i]=\((a[1]*1+a[2]*2+a[3]*3+..a[i]*i)\);

然后再处理一个普通的前缀和:s[i]=(a[1]+a[2]+a[3]+..a[i])

那么上式\(cost=isum[7]-(s[7]-s[3])*3\)。

推广一下可以得到:

inline ll getL(int co,int l,int r){//求[l,r]这些层中喜欢co的人跑到第l-1层的代价
ll sum=0;
sum=(isum[0][co][r]-isum[0][co][l-1])-1ll*(l-1)*(s[co][r]-s[co][l-1]);
return sum;
}

类似的,去右端的情况,预处理一个后缀值即可,下面不再赘述。

综上,时间复杂度为\(O(N^2)\)。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4002;
ll ans=-1;
int a[2][N],n;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
inline void Do(ll &x,ll y){if(x==-1||x>y)x=y;}
ll s[2][N],isum[2][2][N];//isum:sum of i*val
void pre(){
for(int i=1;i<=n;i++){
for(int x=0;x<=1;x++){
s[x][i]=s[x][i-1]+a[x][i];
isum[0][x][i]=isum[0][x][i-1]+1ll*a[x][i]*i;
}
}
for(int i=n;i>=1;i--){
for(int x=0;x<=1;x++){
isum[1][x][i]=isum[1][x][i+1]+1ll*a[x][i]*(n-i+1);
}
}
}
inline ll getL(int co,int l,int r){
ll sum=0;
sum=(isum[0][co][r]-isum[0][co][l-1])-1ll*(l-1)*(s[co][r]-s[co][l-1]);
return sum;
}
inline ll getR(int co,int l,int r){
ll sum=0;int id=n-r;
sum=(isum[1][co][l]-isum[1][co][r+1])-1ll*id*(s[co][r]-s[co][l-1]);
return sum;
}
inline ll calc(int co,int l,int r){//[l,r]可以去l-1,r+1
register int i,mid=(l+r)>>1;
if(l==1&&r==n)return -1;
if(l==1)return getR(co,l,r);
if(r==n)return getL(co,l,r);
return getL(co,l,mid)+getR(co,mid+1,r);
}
ll dp[2][2][N];
namespace p100{
void solve(){
pre();
memset(dp,-1,sizeof(dp));
register int i,j,x,y,g=0;
memset(dp[g],-1,sizeof(dp[g]));
dp[0][g][0]=dp[1][g][0]=0;
for(i=1;i<n;++i){
g^=1;
memset(dp[0][g],-1,sizeof(dp[0][g]));
memset(dp[1][g],-1,sizeof(dp[1][g]));
for(j=0;j<i;++j)for(x=0;x<=1;++x)if(~dp[x][g^1][j]){
for(y=0;y<=1;++y){
if(x==y)Do(dp[y][g][j],dp[x][g^1][j]);
else{
ll val=calc(y,j+1,i);
if(~val)Do(dp[y][g][i],dp[x][g^1][j]+val);
}
}
}
}
for(x=0;x<=1;++x)for(j=1;j<n;++j){
if(~dp[x][g][j])Do(ans,dp[x][g][j]+calc(x^1,j+1,n));
}
printf("%lld\n",ans);
}
}
int main(){
n=read();
for(int i=1;i<=n;++i)a[0][i]=read(),a[1][i]=read();
p100::solve();
}

upd:由于上面代码比较丑,所以在博客文件里还放了几位学长的解析。

最新文章

  1. 【UWP开源】图片编辑器,带贴图、滤镜、涂鸦等功能
  2. CSS调试小技巧 —— 调试DOM元素hover,focus,actived的样式
  3. “error LNK2019: 无法解析的外部符号”之分析
  4. 关于插件管理器Alcatraz
  5. 在win10中创建开机自动登陆的网络驱动器
  6. scala - Enumeration 诡异问题
  7. Centos7上使用官方YUM源安装Mysql
  8. Mac环境下用Java(Sikuli+Robot)实现页游自动化
  9. 通过本地加载ga.js文件提高Google Anlytics性能
  10. Django - staticfiles,STATIC_ROOT, STATIC_URL,STATICFILES_DIRS
  11. 去除win8.1这台电脑中的6个库文件夹
  12. php 关了浏览器也可以自动运行脚本
  13. Java 集合框架之set用法
  14. Django 类方式view进行进行用户验证
  15. Eclipse简介和使用技巧快捷方式
  16. C 语言内存区域分配(进程的各个段)详解
  17. Cookie 用法
  18. SpringBoot(一)初遇
  19. 用NI的数据采集卡实现简单电子测试之2——绘制三极管输出特性曲线(面)图
  20. e672. 缩放,剪取,移动,翻转缓冲图像

热门文章

  1. Ngui之UI框架的层级处理
  2. Docker Api 实测
  3. kubeadm 安装k8s
  4. css中的zoom的作用
  5. vue axios springBoot 跨域session丢失
  6. ES6 学习 -- Set和Map数据结构
  7. navicat远程连接报1045 access denied for user&#39;root&#39;@&#39;ip&#39;(using pasword:yes&quot;.............
  8. 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w
  9. android发编译
  10. 安装xampp之后报错XAMPP: Starting Apache...fail.