T2960 全民健身 思維Dp,預處理,差分優化

2022-06-03 06:51:11 字數 3488 閱讀 7361

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\),此時的最小代價。

所以改一下意義,它表示的是[1,j]這些層所有人的代價+[j+1,i]這些層中喜歡co​的人的代價

ⅱ轉移初始化如下

memset(dp,-1,sizeof(dp));//相當於賦乙個極大值	

dp[0][1][0]=dp[1][1][0]=0;

轉移採用填表方式,更新最小值。

for(i=1;i主要看到\(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)

if(r==n)

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這樣,大致的演算法流程就結束了。但是也許在上面**中你就發現了,空間、時間對於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)

類似的,去右端的情況,預處理乙個字尾值即可,下面不再贅述。

綜上,時間複雜度為\(o(n^2)\)。

完整**如下:

#includeusing namespace std;

typedef long long ll;

const int n=4002;

ll ans=-1;

int a[2][n],n;

inline int read()

inline void do(ll &x,ll y)

ll s[2][n],isum[2][2][n];//isum:sum of i*val

void pre()

} for(int i=n;i>=1;i--) }}

inline ll getl(int co,int l,int r)

inline ll getr(int co,int l,int r)

inline ll calc(int co,int l,int 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;iupd:由於上面**比較醜,所以在部落格檔案裡還放了幾位學長的解析。