【序列分段】【区间dp】[UVA12991] Game Rooms
【序列分段】【区间dp】[UVA12991] Game Rooms
一个 N 层的大楼,每层只有一个游戏室,可以设置一个乒乓球桌或游泳池。第 i 层有 Ti 个人喜欢乒乓球和 Pi 个人喜欢游泳。
现在要求使每个人到最近的喜欢的类型的活动室的距离的和最小,且这栋大楼要有至少一个乒乓球桌和至少一个游泳池。
思路
考虑到要不乒乓球桌,要不游泳池。所以只有0/1两种情况(很明显不建白不建)
那么我们一整座大楼,从下往上(从第一层开始)就可以表示成一段01串。这些01串有连续的,有单独的。
那么我们也可以把单独的看成长度是1的连续串。
于是乎我们dpi,tpe∈{0,1}表示强制i建tpe。那么以i结尾一定会有一个连续都是tpe的串。我们找到这个tpe的极大连续后缀,假设为[k,i],那么很明显首先k≠1,否则另一种就没地方建了。
然后因为[k,i]是极大联通子串,因此k−1和i+1一定是tpe⊕1,那么我们就可以从dpk−1,tpe⊕1转移过来。
同时[k,i]全部为tpe的代价我们也是可以知道的,假设为cst(k,i,tpe)
由于我们的状态转移已经用去O(n2)了,因此我们的cst只能在O(1)内求出。
因为[k,i]全部是tpe,所以peptpe,[k,i](people)是不需要考虑的了。同时因为懒惰所以mid=(k+i)/2以上的peptpe⊕1都去i+1,以下的都去k−1,因此我们也可以得出cst的表达式
先考虑j≤mid这一段,他像是一个阶梯式累加的结果1a1+2a2+3a3+…不同的就是他不是从1开始的,他是1ak+2ak+1+3ak+2+…,那么我们可以定义前缀和bi=∑ij=1ai,ci=∑ij=1i⋅ai那么就有
然后代入i=mid就可以求出这半段的花费cmid−ck−1−(k−1)(bmid−bk−1)。
至于后半段,他是一个逆着的,所以我们可以定义di=∑ij=1bi=∑ij=1∑jk=1ak=∑ij=1(i−j+1)aj,这样这个阶梯就是逆着的了。具体推一下
然后代入k=mid+1,可以得到后半段为di−(i−mid+1)bmid−dmid−1。
总结一下,
注意要开long long
,以及特判i=n,k=1两种情况,这两种情况并不能向两边走,只能朝某一头走。
代码中把游戏室的类型放在第一维了。
View Code
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N=4096;
using std::min;
long long int pep[2][N],b[2][N],c[2][N],d[2][N];
long long int dp[2][N];
int T,n;
inline void init()
{
scanf("%d",&n);
for(register int i=1;i<=n;++i) scanf("%lld%lld",&pep[0][i],&pep[1][i]);
memset(dp,0x6f,sizeof(dp));
for(register long long int i=1;i<=n;++i) b[0][i]=b[0][i-1]+pep[0][i], b[1][i]=b[1][i-1]+pep[1][i];
for(register long long int i=1;i<=n;++i) c[0][i]=c[0][i-1]+pep[0][i]*i, c[1][i]=c[1][i-1]+pep[1][i]*i;
for(register long long int i=1;i<=n;++i) d[0][i]=d[0][i-1]+b[0][i], d[1][i]=d[1][i-1]+b[1][i];
dp[0][0]=dp[1][0]=0;
}
inline long long int cst(int k,int i,int tpe)
{
tpe^=1;
if(k==i) return pep[tpe][k];
if(i==n) return c[tpe][i]-c[tpe][k-1]-(k-1)*(b[tpe][i]-b[tpe][k-1]);
if(k==1) return d[tpe][i];
static long long int res=0;
int mid=(i+k)>>1;
res=c[tpe][mid]-c[tpe][k-1]-(k-1)*(b[tpe][mid]-b[tpe][k-1]);
res+=(d[tpe][i]-(i-mid+1)*b[tpe][mid]-d[tpe][mid-1]);
return res;
}
inline void solve()
{
static int cases=0;
init();
printf("Case #%d: ",++cases);
for(register int i=1;i<=n;++i)
{
for(register int k=i==n?2:1;k<=i;++k)
{
dp[0][i]=min(dp[0][i],dp[1][k-1]+cst(k,i,0));
dp[1][i]=min(dp[1][i],dp[0][k-1]+cst(k,i,1));
}
}
printf("%lld\n",min(dp[0][n],dp[1][n]));
}
int main()
{
scanf("%d",&T);
while(T--) solve();
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个适用于 .NET 的开源整洁架构项目模板
· AI Editor 真的被惊到了
· API 风格选对了,文档写好了,项目就成功了一半!
· 【开源】C#上位机必备高效数据转换助手
· .NET 9.0 使用 Vulkan API 编写跨平台图形应用