前言
模板一套就AC了...
題目
guardians.cpp 1S/128M
你負責將監獄的警衛指派到最瘋狂的罪犯所在的監獄。 一共有N間牢房排列成一行,編號從1~N。 第i間牢房恰好容納了一個瘋狂程度爲C[i]的罪犯。
每個罪犯都應該有一個警衛監視他/她。 理想情況下,應該讓一名警衛監視一個罪犯。 然而,由於預算限制,你只能分配G個警衛。 爲了最大程度地降低有人逃脫的總風險,你必須指定每個警衛應該監視哪些罪犯。當然,你應該將每個警衛分配給一組相鄰的牢房。
第i個罪犯可能逃脫的風險R[i]由下式給出:
R[i] = C[i] * 指派監視他的警衛監視的罪犯數量
請你分配一個最佳的方案,使所有罪犯的風險之和最小。
輸入
第1行:2個整數N和G (1 <= N <= 8000, 1 <= G <= 800)
第2行:N個整數,表示C[i] ( 1 <= C[i] <= 10^9)
輸出
第1行:1個整數,表示答案
Sample Input
6 3
11
11
11
24
26
100
Sample Output
299
Explain
第1個警衛監視1~3,第2個警衛監視4~5,第3個警衛監視6
分析
詳細的關於該優化的講解我有寫博客:決策單調性分治優化
經觀察可以發現本題基本模型爲:分組+花費最小
可以寫出DP定義與轉移方程:
dp[ i ][ j ]:1~j個犯人被第i個獄長監管
對於最後一個獄長i,假設從第k到j的犯人被最後第i獄長監管
dp[ i ][ j ]=min( dp[ i ][ j ],dp[ i-1 ][ k-1 ]+( s[ j ] - s[ k ] ) * ( j - k + 1 ) ) ;
s[ i ]:1~i的前綴和
初始化:dp[ 1 ][ 1~n ]=INF
然後直接套用【決策單調性分治優化】或【四邊形不等式優化】即可,詳見代碼
決策單調性分治優化-代碼(含暴力DP)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=8000,MAXG=800;
#define INF (1LL << 60)
ll c[MAXN+5],s[MAXN+5],dp[MAXG+5][MAXN+5];
ll n,g;
void DP(ll d,ll i,ll j,ll optl,ll optr)//決策優化
{
if(i>j)
return ;
ll mid=(i+j)/2;
ll opt=INF,id;
//暴力計算二分點的dp值opt與最小決策點id
for(int k=optl;k<=min(mid,optr);k++)
{
ll cur=dp[d-1][k-1]+(mid-k+1)*(s[mid]-s[k-1]);
if(cur<opt)
opt=cur,id=k;
}
dp[d][mid]=opt;
//遞歸求解決策點左右兩部分的dp值
DP(d,i,mid-1,optl,id);
DP(d,mid+1,j,id,optr);
}
void Solve1()
{
for(int i=1;i<=n;i++)
dp[0][i]=INF;
for(int i=1;i<=g;i++)
DP(i,1,n,1,n);
printf("%lld\n",dp[g][n]);
}
void Solve2()//普通DP(沒調出來,奇怪)
{
/*
dp[i][j]:1~j個犯人被第i個獄長監管
對於最後一個獄長i,假設從第k到j的犯人被最後第i獄長監管
dp[i][j]=min(dp[i][j],dp[i-1][k-1]+(s[j]-s[k])*(j-k+1));
*/
for(int i=1;i<=n;i++)
dp[1][i]=INF;
//dp[0][0]=0;
for(int i=1;i<=g;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=j;k++)
{
//printf("(%d,%d):%lld\n",i,j,dp[i][j]);
dp[i][j]=min(dp[i][j],dp[i-1][k-1]+(j-k+1)*(s[j]-s[k-1]));
//printf(" %lld\n",dp[i][j]);
}
printf("%lld\n",dp[g][n]);
}
int main()
{
//freopen("guardians.in","r",stdin);
//freopen("guardians.out","w",stdout);
scanf("%lld%lld",&n,&g);
for(int i=1;i<=n;i++)
{
scanf("%lld",&c[i]);
s[i]=s[i-1]+c[i];
}
Solve1();
//Solve2();
return 0;
}
四邊形不等式優化-代碼
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=8000,MAXG=800;
#define INF (1LL<<60)
ll c[MAXN+5],s[MAXN+5],dp[MAXG+5][MAXN+5];
ll n,g;
void Solve()
{
for(int i=0;i<=g;i++)
for(int j=0;j<=n;j++)
dp[i][j]=INF;
dp[0][0]=0;
for(int i=1;i<=g;i++)
{
ll opt=0;
for(int j=i;j<=n;j++)
for(int k=opt;k<=j;k++)
{
ll tmp=1ll*(j-k+1)*(s[j]-s[k-1]);
if(dp[i-1][k-1]+tmp<=dp[i][j])
dp[i][j]=dp[i-1][k-1]+tmp,opt=k;
}
}
/*打表驗證單調性
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
w[i][j]=(j-i+1)*(s[j]-s[i-1]);
//printf("%d %d %lld\n",i,j,w[i][j]);
}
for(int a=1;a<=n;a++)
for(int b=a+1;b<=n;b++)
for(int c=b+1;c<=n;c++)
for(int d=c+1;d<=n;d++)
printf("%d %d %d %d %lld %lld\n",a,b,c,d,w[a][c]+w[b][d],w[a][d]+w[b][c]);
*/
printf("%lld\n",dp[g][n]);
}
int main()
{
scanf("%lld%lld",&n,&g);
for(int i=1;i<=n;i++)
{
scanf("%lld",&c[i]);
s[i]=s[i-1]+c[i];
}
Solve();
return 0;
}