[XJOI趣味賽]病毒研究

題目

傳送門 to XJOI(需要登錄)

題意概要
有一個未知的數字 x[1,an]x\in[1,a_n][1,an][1,a_n]a1,a2,a3,,an1a_1,a_2,a_3,\dots,a_{n-1} 切割成了 nn 段,具體來說,第 ii 段爲 (ai1,ai](a_{i-1},a_i] ,認爲 a0=0a_0=0 。你時刻可以知道 xx 位於哪一段。現在請問,如果 xx 取遍 [1,an][1,a_n] ,讓 xx 一定位於第一段,最小代價之和?

操作有 mm 種可選。第 ii 種:花費 viv_i ,讓 xx 減小 wiw_i

如果不能使 xx 一定位於第一段,輸出 1-1 即可。

數據範圍與提示
man2×103m\le a_n\le 2\times 10^3aa 嚴格遞增。vi106v_i\le 10^6

思路

動態規劃。用 f(l,r)f(l,r) 表示,已知 x[l,r]x\in[l,r]xx 取遍 [l,r][l,r] 的最小代價和。注意:並非“最小代價”之和,而是最小“代價和”。

轉移是很簡單的。如果 k[1,n)\exist k\in[1,n) 使得 lak<rl\le a_k<r ,那麼 f(l,r)=f(l,ak)+f(ak+1,r)f(l,r)=f(l,a_k)+f(a_k+1,r) ;否則,枚舉一個操作,f(l,r)=f(lwi,rwi)+(rl+1)vif(l,r)=f(l-w_i,r-w_i)+(r-l+1)v_i

爲什麼一定是最小“代價和”呢?因爲你並不知道 xx 究竟是誰,所以你得讓 平均值 最小,即“代價和”最小。平均值越小,期望就越小,因爲 xx 是等概率的落到 [l,r][l,r] 內的。

現在我們有了一個 O(n3)\mathcal O(n^3) 的算法,可以通過 64pts64pts

考慮優化。狀態數太多——有的狀態很沒用。我們進行操作的目的是獲得更多信息,所以我們要儘量讓新區間包含不同的段,就可以進一步區分。

完全揹包 求出轉移,我們規定轉移之後的 [l,r][l,r] 必須包含不同的段,或者全部在第一段。此時,有用的狀態只可能是某個段的前綴、後綴,所以總狀態數是 O(n)\mathcal O(n) 的。

然後就做完了。複雜度 O(n2)\mathcal O(n^2)

代碼

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint() {
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int_ x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
# define FOR(i,n) for(int i=0; i<(n); ++i)

const int_ infty = (1ll<<60)-1;
const int MaxN = 2005;
int a[MaxN], n, m;

int_ dp[MaxN][MaxN], v[MaxN];
int deep;
int_ work(int l,int r,int k){
	if(r <= a[1]) return 0;
	if(l <= a[k]){ // 已經確保 a[k] < r
		int_ tmp = work(l,a[k],k-1);
		tmp += work(a[k]+1,r,k);
		return min(infty,tmp); // 確保不超過infty
	}
	if(dp[l][r] != -1) return dp[l][r];
	int_ &d = dp[l][r] = infty;
	for(int i=1; i<l; ++i){
		if(r-i == a[k]) -- k;
		if(k and a[k] < l-i) continue; // 沒切開
		getMin(d,work(l-i,r-i,k)+(r-l+1)*v[i]);
	}
	return d;
}

int main(){
	for(int T=readint(); T; --T){
		n = readint(), m = readint();
		for(int i=1; i<=n; ++i)
			a[i] = readint();
		for(int j=1; j<=a[n]; ++j)
			v[j] = infty;
		for(int i=1; i<=m; ++i){
			int val = readint(), w = readint();
			for(int j=w; j<=a[n]; ++j)
				getMin(v[j],v[j-w]+val);
		}
		for(int i=1; i<=n; ++i){
			for(int j=a[i-1]+1; j<=a[i]; ++j)
				dp[j][a[i]] = -1;
			for(int j=a[i]+1; j<=a[i+1]; ++j)
				dp[a[i]+1][j] = -1;
		}
		int_ ans = 0;
		for(int i=1; i<=n and ans<infty; ++i){
			int_ tmp = work(a[i-1]+1,a[i],i-1);
			if(tmp >= infty) ans = infty;
			else ans += tmp;
		}
		if(ans >= infty)
			putchar('-'), putchar('1');
		else writeint(ans); putchar('\n');
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章