【NOIP2018普及组】摆渡车

@摆渡车@

    • @题目描述@
    • @绝对不可能是正解的题解@
    • @代码 - 1@
    • @看起来比较像正解的题解@
    • @代码 - 2@
    • @End@


@题目描述@

有 n 名同学要乘坐摆渡车从人大附中前往人民大学,第 i 位同学在第 ti 分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 m 分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。
凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?
注意:摆渡车回到人大附中后可以即刻出发。

输入
第一行包含两个正整数 n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含 n 个正整数,相邻两数之间以一个空格分隔,第 i 个非负整数 ti代表第 i 个同学到达车站的时刻。
输出
输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

输入样例 1
5 1
3 4 4 3 5
输入样例 2
5 5
11 13 1 5 5

输出样例 1
0
输出样例 2
4

输入输出样例 1 说明
同学 1 和同学 4 在第 3 分钟开始等车,等待 0 分钟,在第 3 分钟乘坐摆渡车出发。摆渡车在第 4 分钟回到人大附中。
同学 2 和同学 3 在第 4 分钟开始等车,等待 0 分钟,在第 4 分钟乘坐摆渡车出发。摆渡车在第 5 分钟回到人大附中。
同学 5 在第 5 分钟开始等车,等待 0 分。

输入输出样例 2 说明
同学 3 在第 1 分钟开始等车,等待 0 分钟,在第 1 分钟乘坐摆渡车出发。摆渡车在第 6 分钟回到人大附中。
同学 4 和同学 5 在第 5 分钟开始等车,等待 1 分钟,在第 6 分钟乘坐摆渡车出发。摆渡车在第 11 分钟回到人大附中。
同学 1 在第 11 分钟开始等车,等待 2 分钟;同学 2 在第 13 分钟开始等车,等待 0 分钟。他/她们在第 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。总等待时间为 4。可以证明,没有总等待时间小于 4 的方案。

数据规模与约定
对于 10% 的数据,n ≤ 10,m = 1,0 ≤ ti ≤ 100。
对于 30% 的数据,n ≤ 20,m≤ 2,0 ≤ ti ≤ 100。
对于 50% 的数据,n ≤ 500,m ≤ 100,0 ≤ ti ≤ 10^4。
另有 20% 的数据,n ≤ 500,m ≤ 10,0 ≤ ti ≤ 4×10^6。
对于 100% 的数据,n ≤ 500,m ≤ 100,0 ≤ ti ≤ 4×10^6。

@绝对不可能是正解的题解@

搜到了不少的题解,一个比一个强 orz。
这个算法应该是一个比较容易“想到”的算法(因为题目中很多的性质都没有用到)
但是有用到斜率优化……
定义 a [ i ] a[i] a[i] 表示第 i 个时刻到达进行等车的人数。
定义 d p [ i ] dp[i] dp[i] 表示在第 i 个时刻发车,前面的同学最小等待时间之和。
则有:
d p [ i ] = min ⁡ ( d p [ j ] + ( ∑ k = j + 1 k ≤ i a [ k ] ∗ ( i − k ) ) ) ( i − j > = m ) dp[i] = \min(dp[j] + (\sum_{k=j+1}^{k\le i}a[k]*(i-k)))(i-j>=m) dp[i]=min(dp[j]+(k=j+1kia[k](ik)))(ij>=m)
拆开得到:
d p [ i ] = min ⁡ ( d p [ j ] + i ∗ ( ∑ k = j + 1 k ≤ i a [ k ] ) − ( ∑ k = j + 1 k ≤ i a [ k ] ∗ k ) ) ( i − j > = m ) dp[i] = \min(dp[j] + i*(\sum_{k=j+1}^{k\le i}a[k])-(\sum_{k=j+1}^{k\le i}a[k]*k))(i-j>=m) dp[i]=min(dp[j]+i(k=j+1kia[k])(k=j+1kia[k]k))(ij>=m)
c n t [ i ] = ∑ j = 1 j ≤ i a [ j ] cnt[i]=\sum_{j=1}^{j\le i}a[j] cnt[i]=j=1jia[j] s u m [ i ] = ∑ j = 1 j ≤ i a [ j ] ∗ j sum[i]=\sum_{j=1}^{j\le i}a[j]*j sum[i]=j=1jia[j]j。这两货可以前缀和O(n)处理。

则转移方程式变为:
d p [ i ] = min ⁡ ( d p [ j ] + i ∗ ( c n t [ i ] − c n t [ j ] ) − ( s u m [ i ] − s u m [ j ] ) ) = min ⁡ ( d p [ j ] + i ∗ c n t [ i ] − i ∗ c n t [ j ] − s u m [ i ] + s u m [ j ] ) = min ⁡ ( ( i ∗ c n t [ i ] − s u m [ i ] ) + ( d p [ j ] + s u m [ j ] ) − i ∗ c n t [ j ] ) ( i − j > = m ) dp[i] = \min(dp[j] + i*(cnt[i]-cnt[j])-(sum[i]-sum[j]))\\=\min(dp[j] + i*cnt[i]-i*cnt[j]-sum[i]+sum[j])\\=\min( (i*cnt[i]-sum[i]) + (dp[j]+sum[j]) - i*cnt[j]) (i-j>=m) dp[i]=min(dp[j]+i(cnt[i]cnt[j])(sum[i]sum[j]))=min(dp[j]+icnt[i]icnt[j]sum[i]+sum[j])=min((icnt[i]sum[i])+(dp[j]+sum[j])icnt[j])(ij>=m)
f ( i ) = ( i ∗ c n t [ i ] − s u m [ i ] ) , g ( j ) = ( d p [ j ] + s u m [ j ] ) , h ( j ) = c n t [ j ] f(i)=(i*cnt[i]-sum[i]), g(j)=(dp[j]+sum[j]), h(j)=cnt[j] f(i)=(icnt[i]sum[i]),g(j)=(dp[j]+sum[j]),h(j)=cnt[j]
d p [ i ] = min ⁡ ( f ( i ) + g ( j ) − i ∗ h ( j ) ) ( j < = i − m ) dp[i]=\min(f(i) + g(j) - i*h(j)) (j<=i-m) dp[i]=min(f(i)+g(j)ih(j))(j<=im)

典型的不能再典型的斜率优化式…… i 和 h(j) 还具有单调性……

@代码 - 1@

虽然当初我去都没去普及组。
不过普及组(涉嫌)考到斜率优化倒是令我极其感兴趣,就写了一下 qwq。

#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int, ll> pil;
const int INF = (1<<30);
const int MAXN = 4000100;
int cnt[MAXN + 5];
ll sum[MAXN + 5], dp[MAXN + 5];
ll f(int i) {
    return cnt[i]*i - sum[i];
}
ll g(int j) {
    return dp[j]+sum[j];
}
ll h(int j) {
    return cnt[j];
}
double slope(int x, int y) {
	if( h(x) == h(y) ) {
		if( g(x) > g(y) ) return -INF;
		else if( g(x) < g(y) ) return INF;
	}
	else return 1.0*(g(y) - g(x))/(h(y) - h(x));
}
int que[MAXN + 5];
int main() {
    int n, m, x, lim = 0;
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++) {
        scanf("%d", &x);
        cnt[x]++; lim = max(lim, x);
    }
    lim += m;
    for(int i=1;i<=lim;i++) {
        sum[i] = sum[i-1] + 1LL*cnt[i]*i;
        cnt[i] += cnt[i-1];
    }
    int s = 1, t = 0;
    que[++t] = 0;
    for(int i=1;i<=lim;i++) {
        if( i > m ) {
            while( s < t && slope(que[t-1], que[t]) >= slope(que[t], i-m) )
				t--;
            if( h(i-m) != h(que[t]) || g(i-m) != g(que[t]) )
                que[++t] = i-m;
        }
        while( s < t && slope(que[s], que[s+1]) <= i )
            s++;
        dp[i] = f(i) + g(que[s]) - h(que[s])*i;
    }
    ll ans = INF;
    for(int i=lim-m;i<=lim;i++)
    	ans = min(ans, dp[i]);
    printf("%lld\n", ans);
}

@看起来比较像正解的题解@

考虑到n, m如此之小,我们以它们俩作为状态进行 dp。

首先有这样一个比较关键的性质:车辆停留的时间不会超过 m ,否则它可以往返一次再回来,这样不会影响后面的状态且有可能更优秀。

先按到达时间给人排序。然后定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示第 i 个人等了 j 分钟,前 i 个人的最小等待时间和。因为上面那个性质,有 j <= 2*m。抵到上界的唯一情况 t[i] = t[i-1],且转移时于 t[i-1] 时刻发车,回来停留 m 分钟。

根据状态定义,可以得到转移方程:
( 1 ) d p [ i ] [ j ] = min ⁡ ( d p [ i − 1 ] [ k ] + j ) ( t [ i − 1 ] + k = t [ i ] + j ) ( 2 ) d p [ i ] [ j ] = min ⁡ ( d p [ i − 1 ] [ k ] + j ) ( t [ i − 1 ] + k + m < = t [ i ] + j ) (1)dp[i][j] = \min(dp[i-1][k] + j)(t[i-1]+k=t[i]+j)\\(2)dp[i][j] = \min(dp[i-1][k] + j)( t[i-1] + k + m <= t[i] + j ) (1)dp[i][j]=min(dp[i1][k]+j)(t[i1]+k=t[i]+j)(2)dp[i][j]=min(dp[i1][k]+j)(t[i1]+k+m<=t[i]+j)
对于(1),第 i 个人与第 i-1 个人同车。
对于(2),第 i 个人与第 i-1 个人不同车。
以上都要满足 0 <= j, k <= 2*m,1<=i<=n。

现在算法时间复杂度为 O(n*m^2) ,虽然已经足以通过此题,但是还可以做到更优秀。
第(1)类转移是O(1)的,不去管它。
第(2)类转移,变换条件得到:k <= t[i]+j-t[i-1]-m。可以用前缀最小值快速求解。
时间复杂度O(n*m)。以及代码复杂度比上面那个斜率优化不知道高到哪里去了。

@代码 - 2@

考场上同学们好像做得很炸……
不过分析下来其实难点也不算多。只是可能状态定义不太好想。

#include
#include
#include
using namespace std;
const int MAXN = 500;
const int MAXM = 100;
const int INF = (1<<30);
int t[MAXN + 5];
int dp[MAXN + 5][MAXM*2 + 5];
int mn[MAXN + 5][MAXM*2 + 5];
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++)
        scanf("%d", &t[i]);
    sort(t+1, t+n+1);
    for(int i=0;i<=2*m;i++) {
    	dp[1][i] = i;
    	mn[1][i] = (i == 0) ? dp[1][i] : min(dp[1][i], mn[1][i-1]);
    }
    for(int i=2;i<=n;i++) {
    	for(int j=0;j<=2*m;j++) {
    		dp[i][j] = INF;
    		if( 0 <= t[i] - t[i-1] + j && t[i] - t[i-1] + j <= 2*m )
    			dp[i][j] = min(dp[i][j], dp[i-1][t[i]-t[i-1]+j] + j);
    		if( 0 <= t[i] - t[i-1] + j - m )
				dp[i][j] = min(dp[i][j], mn[i-1][min(t[i]-t[i-1]+j-m, 2*m)] + j);
			mn[i][j] = (j == 0) ? dp[i][j] : min(dp[i][j], mn[i][j-1]);
		}
	}
    int ans = INF;
    for(int i=0;i<=2*m;i++)
    	ans = min(ans, dp[n][i]);
    printf("%d\n", ans);
}

@End@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。

你可能感兴趣的:(@动态规划看这里!@,--斜率优化!--)