【CSP-S2019】D2T2 劃分

CSP-S2019 D2T2 劃分

題目

題目描述

2048 年,第三十屆 CSP 認證的考場上,作爲選手的小明打開了第一題。這個題的樣例有 nn 組數據,數據從 1n1 \sim n 編號,ii 號數據的規模爲 aia_i​。

小明對該題設計出了一個暴力程序,對於一組規模爲 uu 的數據,該程序的運行時間爲 u2u^2。然而這個程序運行完一組規模爲 uu 的數據之後,它將在任何一組規模小於 uu 的數據上運行錯誤。樣例中的 aia_i​ 不一定遞增,但小明又想在不修改程序的情況下正確運行樣例,於是小明決定使用一種非常原始的解決方案:將所有數據劃分成若干個數據段,段內數據編號連續,接着將同一段內的數據合併成新數據,其規模等於段內原數據的規模之和,小明將讓新數據的規模能夠遞增。

也就是說,小明需要找到一些分界點 1k1<k2<<kp<n1 \leq k_1 \lt k_2 \lt \cdots \lt k_p \lt n,使得

i=1k1aii=k1+1k2aii=kp+1nai \sum\limits_{i=1}^{k_1}a_i \le \sum\limits_{i = k_1 + 1}^{k_2}a_i\le \cdots \le \sum\limits_{i = k_p + 1}^{n}a_i

注意 pp 可以爲 00 且此時 k0=0k_0 = 0,也就是小明可以將所有數據合併在一起運行。

小明希望他的程序在正確運行樣例情況下,運行時間也能儘量小,也就是最小化

(i=1k1ai)2+(i=k1+1k2ai)2++(i=p+1nai)2 \left( \sum\limits_{i = 1}^{k_1}a_i \right)^2 + \left( \sum\limits_{i = k_1 + 1}^{k_2}a_i \right)^2 + \cdots + \left( \sum\limits_{i = p + 1}^{n}a_i \right)^2

小明覺得這個問題非常有趣,並向你請教:給定 nnaia_i​,請你求出最優劃分方案下,小明的程序的最小運行時間。

分析

結論: 最後取出的一段的長度要儘可能地小。

證明: 不會。。。如果想要看理性的證明可以去看 matthew99 的博客

然後這道題就變成水題了。。。


我還是說一下我的感性理解吧。。。

sis_iaia_i 的前綴和。

首先不難想到一個 36 分的 DP:(這是我考場上想到的)

f(i,j)f(i, j) 爲把前 ii 個數分成 jj 段所產生的最小代價,g(i,j)g(i, j) 爲最後一段的右端點的位置。那麼顯然有轉移:

f(i,j)=min(k<i)(sksg(k,j1)sisk){f(k,j1)+(sisk)2} f(i, j) = \min\limits_{(k < i) \land (s_k - s_{g(k, j - 1)} \le s_i - s_k)} \{ f(k, j - 1) + (s_i - s_k)^2 \}

最後答案爲 min1inf(n,i)\min\limits_{1 \le i \le n} f(n, i)

然後我們發現題目中對分成的段數並沒有限制,於是去掉第二維,狀態定義變成 f(i)f(i) 爲表示前 ii 個數分成若干段的最小答案。轉移和上面那個差不多。

然而這種方法似乎精度很容易爆炸。。。我們必須換一種想法。

我們令 bib_i 等於一個合法的劃分中每段 aia_i 的和。根據我們初中的數學知識可以得到,對於一個數列 bb 中的任意一個子區間 [l,r][l, r],有:

(i=lrbi)2>i=lrbi2 \left( \sum\limits_{i=l}^rb_i \right)^2 > \sum\limits_{i=l}^{r}{b_i}^2

意思就是把一段長的區間分成儘可能多的短的區間之後,答案一定會變優。感性理解一下就是了。。。 這意味着我們的每次選的最後一個區間要儘可能地短。

然後我們記 g(i)g(i) 爲最後一個區間所選的右端點的位置,顯然有:

g(i)=max{j(j<i)(sjsg(j)sisj)} g(i) = \max \{ j | (j < i) \land (s_j - s_{g(j)} \le s_i - s_j)\}

最後我們再沿着 g(i)g(i) 倒回去算就可以了,當然也可以邊轉移邊算。

然而這個轉移是 O(n2)O(n^2) 的,無法通過這道題。我們考慮優化。

看到轉移式子後面的那個不等式,我們將它變一下形就可以得到:

si2sjsg(j) s_i \geq 2s_j - s_{g(j)}

不難 看出這個式子有單調性,於是用一個單調隊列優化一下就是了。

然而我們並不能邊轉移邊算了,因爲這道題要用到 高精度 ,這樣一做顯然是要炸空間的。所以我們必須先處理出整個 g(i)g(i) 再倒回去算。

總時間複雜度爲 O(n)O(n),另注意這道題卡空間,數組要想辦法壓掉幾個。。。高精度也要做壓位的。 不壓位的我也不知道能不能過。。。

參考代碼

其實這份代碼的長度主要還是高精度。。。核心程序也就那麼幾行。。。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 4e7;

struct BigInteger {
	static const int BASE = 100000000;
	int num[15], len;
	BigInteger (ll x = 0) {
		*this = x;
	}
	inline void clear(BigInteger &x) {
		while(x.num[x.len] == 0 && x.len > 1)
			x.len--;
	}
	BigInteger operator = (ll rhs) {
		memset(num, 0, sizeof num);
		len = 0;
		do {
			num[++len] = rhs % BASE;
			rhs /= BASE;
		} while(rhs != 0);
		return *this;
	}
	BigInteger operator + (const BigInteger &rhs) {
		BigInteger ret;
		memset(ret.num, 0, sizeof ret.num), ret.len = 0;
		for(int i = 1, tmp = 0; tmp != 0 || i <= len || i <= rhs.len; i++) {
			tmp += num[i] + rhs.num[i];
			ret.num[++ret.len] = tmp % BASE;
			tmp /= BASE;
		}
		return ret;
	}
	BigInteger operator * (const BigInteger &rhs) {
		BigInteger ret;
		memset(ret.num, 0, sizeof ret.num), ret.len = len + rhs.len;
		for(int i = 1; i <= len; i++) {
			ll tmp = 0;
			for(int j = 1; j <= rhs.len || tmp != 0; j++) {
				tmp += 1LL * num[i] * rhs.num[j] + ret.num[i + j - 1];
				ret.num[i + j - 1] = tmp % BASE;
				tmp /= BASE;
			}
		}
		clear(ret);
		return ret;
	}
	BigInteger operator += (const BigInteger &rhs) {
		*this = *this + rhs;
		return *this;
	}
	BigInteger operator *= (const BigInteger &rhs) {
		*this = *this * rhs;
		return *this;
	}
};
ostream &operator << (ostream &out, const BigInteger &x) {
	out << x.num[x.len];
	for(int i = x.len - 1; i >= 1; i--) {
		char buf[20];
		sprintf(buf, "%08d", x.num[i]);
		out << buf;
	}
	return out;
}

int N;
ll sum[Maxn + 5];

ll B[Maxn + 5];
const ll mod = (1 << 30);

inline void ReadIn() {
	int typ;
	scanf("%d %d", &N, &typ);
	if(typ == 0) {
		for(int i = 1; i <= N; i++)
			scanf("%lld", &sum[i]);
	} else {
		ll x, y, z;
		scanf("%lld %lld %lld %lld %lld", &x, &y, &z, &B[1], &B[2]);
		for(int i = 3; i <= N; i++)
			B[i] = ((x * B[i - 1] % mod) + (y * B[i - 2] % mod) + z) % mod;
		int m, las = 0, p;
		scanf("%d", &m);
		for(int i = 1; i <= m; i++) {
			int l, r;
			scanf("%d %d %d", &p, &l, &r);
			for(int j = las + 1; j <= p; j++)
				sum[j] = (B[j] % (r - l + 1)) + l;
			las = p;
		}
	}
}

int f[Maxn + 5];
inline ll calc_val(int pos) {
	return sum[pos] * 2 - sum[f[pos]];
}

int q[Maxn + 5], head, tail;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	ReadIn();
	for(int i = 1; i <= N; i++)
		sum[i] += sum[i - 1];
	head = tail = 1;
	for(int i = 1; i <= N; i++) {
		while(head < tail && calc_val(q[head + 1]) <= sum[i])
			head++;
		f[i] = q[head];
		while(head <= tail && calc_val(q[tail]) >= calc_val(i))
			tail--;
		q[++tail] = i;
	}
	BigInteger ans = 0;
	int p = N;
	while(p != 0) {
		BigInteger tmp(sum[p] - sum[f[p]]);
		ans += (tmp * tmp);
		p = f[p];
	}
	cout << ans;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章