NOIP2018 擺渡車


擺渡車

定義,無比重要

題意

顯然的,我們有一個dp的想法,類似於遞降子序列的想法

我們顯然的,這是一個一維dp,所以,我們定義\(f[i]\)函數,為\(0\)\(-i\)的時間內,所出現顧客的等待時間的最小值

但是,有問題,這樣的狀態,沒有dp轉移方程。。。。。因為,狀態描述不清,你有一個發車間隔為\(m\)

所以我們吧\(dp\)的狀態強化,為\(dp[i]\),表示在\(i\)時刻發車\(0-i\)時出現的乘客等待時間的的最小值

這樣我們就把\(dp\)的狀態描述清楚了我們再來推\(dp\)的轉移方程

\[\cal f_i= \begin{cases} Min_{j=1}^i\{f_j+\sum_{j<t_k\leq i}i-t_k\} &i\ge m\\ \sum _{t_i<=i} (i-t_k)&i<m \end{cases} \]

我們照着這個直接抄就好了

需要注意的是,我們在這里要做一個前綴和,讓轉移的復雜度降下來,但那也是\(O(n^2)\)的會超時

不妨,我們即令\(cnt_i\)\(0-i\)中出現的人數,而\(sum_i\),為其出現時間\(t_k\)之和

\(50pts\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;
int n,m,ti,t,ans=0x7fffffff,cnt[MaxT],sum[MaxT],f[MaxT];
int main() {
	n=read();
	m=read();
	for(int i=1;i<=n;i++){
		ti=read();
		t=max(ti,t);
		cnt[ti]++;
		sum[ti]+=ti;
	}
	for(int i=1;i<t+m;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=0;i<t+m;i++){
		f[i]=cnt[i]*i-sum[i];
		for(int j=0;j<=i-m;j++){
			f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
		}
	}
	for(int i=t;i<t+m;i++){
		ans=min(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

這能得到50ps的高分

\(70pts\)

我們再來想想優化,事實上我們枚舉j時不需令\(j\)從0開始,我們減去無用的轉移從

事實上,從貪心的角度出發,在這一段\(>2m\)的長度里,發一次車,一定比不發車要好,所以

我們令\(i-2m<j\le i-m\)

這樣根據寫法,可以獲得\(70\)\(75\),分數

時間復雜度為\(O(tm)\)

\(100pts\)

我們其實稍微看看,聯想一下過河那個題,考慮到路徑壓縮,因為讓人很稀疏\(t\)很大,事實上無論如果你是循環枚舉決策,只能是\(O(tm)\);

然而在這個題里,考慮路徑壓縮的一個思想,但是那個題模1000,真的是無語了,解法不漂亮,在這個題里,數組是夠的,然而你不能模一個東西,因為與\(m\)有關

\([i,i+m)\),里如果沒有讓人,那我們把這一段往右移令,\(f_i\)=\(f_{i-m}\),j時間復雜度為\(O(n^2m+t)\)

至於為什么有右移,我們的\(dp\),定義為在第\(i\)時間發車的等待時間最小值,而\([i,i+m)\)已經沒有人了,在做\(i+m\)時上一輛的回來時間至近為\(i\),若不是

我們先證明一個引理:

  1. 任何顧客的等待時間不超過m(小於)

由此

得出我們剪枝的正確性。。。。

其實我也不確定。。但是對了?

![car1](C:\Users\任世鑫\Desktop\car1.jpg)#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;

int n,m,ti,t,ans=0x7fffffff,cnt[MaxT],sum[MaxT],f[MaxT];
int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x; 
}

int main() {
//	freopen("1.in","r",stdin);
	n=read();
	m=read();
	for(int i=1;i<=n;i++){
		ti=read();
		t=max(ti,t);
		cnt[ti]++;
		sum[ti]+=ti;
	}
	for(int i=1;i<t+m;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=0;i<t+m;i++){
		if(i>=m&&cnt[i-m]==cnt[i]){
			f[i]=f[i-m];
			continue;
		}
		f[i]=cnt[i]*i-sum[i];
		for(int j=max(i-2*m+1,0);j<=i-m;j++){
			f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
		}
	}
	for(int i=t;i<t+m;i++){
		ans=min(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

但是,我們知道,這樣子一點也沒有\(bg\),對吧,要優美、

考慮,之前做過的玩具裝箱,不是一樣的嗎?

所以我們考慮斜率優化,,,

接下來是數學的操作

手稿

下面時正文推導

首先,為了書寫方便令\(j\)為最優決策點

我們把dp方程做變形有

\[f_{J}=i*cnt_j+({f_i+sum_i-cnt_i*i} ) \]

於是等號左邊為\(y\)\(i\)\(k\),\(cnt_j\)\(x\),\(({f_i+sum_i-cnt_i*i} )\)\(b\),我們要求\(b\)

配個小圖

不妨建立一個平面直角坐標系令每個點坐標為\((cnt_i,f_j+sum_j)\),這就很明顯了,過這個點做一條斜率為\(i\)的直線,求截距最小值,再兩個點斜率小於i時隊首++,然后i點入隊(提前要把一些不是凸包的點初隊)

我們通過維護一個雙端隊列,來完成這個操作。

程序寫的時候注意細節

#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;

int n,m,ti,t,ans=0x7fffffff,tail,head,cnt[MaxT],sum[MaxT],f[MaxT],que[MaxT];
double slope(int i,int j){
	if(cnt[i]==cnt[j]) return double(f[j]+sum[j]-f[i]-sum[i])/1e-9;
	else return double(f[j]+sum[j]-f[i]-sum[i])/double(cnt[j]-cnt[i]);
}

int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x; 
}

int main() {
//	freopen("1.in","r",stdin);
	n=read();
	m=read();
	for(int i=1;i<=n;i++){
		ti=read();
		t=max(ti,t);
		cnt[ti]++;
		sum[ti]+=ti;
	}
	for(int i=1;i<t+m;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	/*
	for(int i=0;i<t+m;i++){
		if(i>=m&&cnt[i-m]==cnt[i]){
			f[i]=f[i-m];
			continue;
		}
		f[i]=cnt[i]*i-sum[i];
		for(int j=max(i-2*m,0);j<=i-m;j++){
			f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
		}
	}
	*/
	head=1;tail=0;
	for(int i=0;i<t+m;i++){
		if(i<m){
			f[i]=cnt[i]*i-sum[i];
			continue;
		}
		while(head<tail&&slope(que[tail-1],que[tail])>=slope(que[tail-1],i-m))
			tail--;
		tail++;
		que[tail]=i-m;
		while(head<tail&&slope(que[head],que[head+1])<=i) head++;
		f[i]=f[que[head]]+(cnt[i]-cnt[que[head]])*i-(sum[i]-sum[que[head]]);
		
	}/*
	for(int i=0;i<t+m;i++){
		printf("%d      %d\n",i,f[i]);
	}*/
	for(int i=t;i<t+m;i++){
		ans=min(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

時間復雜度\(O(t)\),我覺得很優秀了,不是嗎。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM