【7.27】DP Better
矩阵加速优化
模板
#include <bits/stdc++.h>
#define MAXM 3
using namespace std;
struct Matrix{
int a[MAXM][MAXM];
Matrix() { memset(a,0,sizeof a); }
Matrix operator*(const Matrix &b) const{
Matrix res;
for (int i=1;i<=MAXM;i++){
for (int j=1;j<=MAXM;j++){
for (int k=1;k<=MAXM;k++){
res.a[i][j]=res.a[i][j]+a[i][k]*b.a[k][j];
}
}
}
return res;
}
}A,B;
void init(){
A.a[1][1]=1;B.a[1][1]=1;
}
int main(){
init();
Matrix C=A*B;
cout<<C.a[1][1];
return 0;
}
数据结构优化
例题
CF597C Subsequences
Solution
树状树组优化掉比较 \(a_i,a_j\) 大小的一维。
Code
#include <bits/stdc++.h>
#define MAXN 100003
#define MAXM 14
using namespace std;
int n,m;
/*-----------------FIO------------------*/
inline int read()
{
char ch = getchar();
long long res = 0;
while (!isdigit(ch))
{
ch = getchar();
}
while (isdigit(ch))
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res;
}
void write(long long x)
{
if (x < 10)
{
putchar(x + '0');
return;
}
write(x / 10);
putchar((x % 10) + '0');
}
/*--------------------------------------*/
struct NUM{
int num;
int id;
bool operator<(const NUM &b)const {
return num<b.num;
}
};
NUM tmp[MAXN];
int a[MAXN];
long long dp[MAXM][MAXN];
int cnt;
long long t[MAXM][MAXN];
int lowbit(int x){
return x&-x;
}
long long SUM(int x,int z){
long long res=0;
while (x>=1){
res=res+t[z][x];
x-=lowbit(x);
}
return res;
}
void ADD(int x,long long y,int z){
while (x<=cnt){
t[z][x]=t[z][x]+y;
x+=lowbit(x);
}
return;
}
int main(){
n=read(); m=read(); m++;
for (int i=1;i<=n;i++){
tmp[i].num=read();
tmp[i].id=i;
}
sort(tmp+1,tmp+1+n);
cnt=0;
for (int i=1;i<=n;i++){
if (tmp[i-1].num!=tmp[i].num){
cnt++;
}
a[tmp[i].id]=cnt;
}
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
dp[i][j]=0;
}
}
for (int i=1;i<=m;i++){
for (int j=1;j<=cnt;j++){
t[i][j]=0;
}
}
for (int j=1;j<=n;j++){
dp[1][j]=1;
ADD(a[j],1,1);
for (int i=2;i<=m;i++){
dp[i][j]=dp[i][j]+SUM(a[j]-1,i-1);
ADD(a[j],dp[i][j],i);
}
}
long long ans=0;
for (int j=1;j<=n;j++){
ans=ans+dp[m][j];
}
cout<<ans<<endl;
return 0;
}
UVA12983 The Battle of Chibi
单调队列优化
具体应用见基环树例题中的环上DP。
斜率优化
例题
P3195 [HNOI2008] 玩具装箱
P 教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。
P 教授有编号为 \(1 \cdots n\) 的 \(n\) 件玩具,第 \(i\) 件玩具经过压缩后的一维长度为 \(C_i\)。
为了方便整理,P教授要求:
-
在一个一维容器中的玩具编号是连续的。
-
同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。形式地说,如果将第 \(i\) 件玩具到第 \(j\) 个玩具放到一个容器中,那么容器的长度将为 \(x=j-i+\sum\limits_{k=i}^{j}C_k\)。
制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 \(x\),其制作费用为 \((x-L)^2\)。其中 \(L\) 是一个常量。P 教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 \(L\)。但他希望所有容器的总费用最小。
Solution
\(dp[i]\) 表示前 \(i\) 个玩具的最小费用, \(sum\) 为 \(C\) 的前缀和。状态转移方程为:
设 \(a[i]=sum[i]+i\) ,\(b[j]=sum[j]+j+1+L\) ,原式化为:
假定已经找到 \(j\) ,去掉 \(\min\) ,并化简:
将其化成 \(y=kx+b\) 的形式:
问题转化为求斜率为 \(2a[i]\) 且经过点 \((b[j],dp[j]+b[j]^2)(j<i)\) 的直线的最小的 \(b\)。
由 \(b[j]\) 的定义可知,\(b[j]\) 具有单调递增性,也就是 \(j\) 的增加意味着 \(b[j]\) (点 \((b[j],dp[j]+b[j]^2)\) 的横坐标) 也增加 (这条性质很重要)。
只需维护点集 \(\{(b[j],dp[j]+b[j]^2) \; | \; j<i\}\) 的下凸包,即可求出这条直线自下而上第一个接触到的点,从而求出最小 \(b\) 。图像如下:
由于下凸包点集具有单调性,可用单调队列维护。具体维护过程:
$1. $ 对队尾: 如果 \(slope(l_{r,i-1})<slope(l_{r-1,i-1})\) ,则弹队尾。
$2. $ 插入新点。
$3. $ 对队首: 如果 \(slope(l_{l,l+1})<2a[i]\) ,则弹队首。
$4. $ 此时队首为最优点,找到 \(j\)
然后根据方程转移。
Code
#include <bits/stdc++.h>
#define MAXN 50003
using namespace std;
int n,L;
int C[MAXN]; long long s[MAXN];
long long dp[MAXN];
long long a[MAXN],b[MAXN];
int q[MAXN];
double slope(int i,int j){
return 1.0*((dp[i]+b[i]*b[i])-(dp[j]+b[j]*b[j]))/(b[i]-b[j]);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>L;
for (int i=1;i<=n;i++) cin>>C[i];
for (int i=1;i<=n;i++) s[i]=s[i-1]+C[i];
/*dp[i]=min(dp[j]+(s[i]+i-sum[j]-j-1-L)^2)
a[i]=s[i]+i; b[j]=s[j]+j+1+L
dp[i]=min(dp[j]+(a[i]-b[j])^2)
dp[j]+b[j]^2=2a[i]*b[j]+dp[i]-a[i]^2
| y = k * x + b |
*/
for (int i=1;i<=n;i++) a[i]=s[i]+i; // a[i]单增
for (int i=0;i<=n;i++) b[i]=s[i]+i+1+L; // b[i]单增
int l=0,r=0; q[0]=0;
dp[1]=dp[0]+(a[1]-b[0])*(a[1]-b[0]);
for (int i=2;i<=n;i++){
while (l<r&&slope(i-1,q[r])<slope(i-1,q[r-1])) r--;
q[++r]=i-1;
while (l<r&&slope(q[l+1],q[l])<2.0*a[i]) l++;
int j=q[l];
dp[i]=dp[j]+(a[i]-b[j])*(a[i]-b[j]);
}
cout<<dp[n]<<endl;
return 0;
}
P5785 [SDOI2012] 任务安排 / P2365 [弱化版] 任务安排
机器上有 \(n\) 个需要处理的任务,它们构成了一个序列。这些任务被标号为 \(1\) 到 \(n\),因此序列的排列为 \(1 , 2 , 3 \cdots n\)。这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。从时刻 \(0\) 开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间是 \(T_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和。
注意,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 \(C_i\)。
请确定一个分组方案,使得总费用最小。
Solution
处理 \(qt\) 为 \(t\) 的前缀, \(qc\) 为 \(c\) 的前缀。状态转移方程:
设 \(a[i]=qt[i] \times qc[i] , b[j]=S \times qc[j]\)
单调队列维护下凸包,但因为 \(t[i]\) 可以为负, \(qt\) 不保证单调性,单调队列不能弹队首,同时队首也不再一定为最优点,这时需要使用二分求出最优点。
Code
#include <bits/stdc++.h>
#define MAXN 300003
using namespace std;
int n,s;
long long t[MAXN],c[MAXN];
long long tq[MAXN],cq[MAXN];
long long dp[MAXN];
long long a[MAXN],b[MAXN];
int Q[MAXN];
long double slope(int i,int j){
return (long double)((dp[i]-b[i])-(dp[j]-b[j]))/(cq[i]-cq[j]);
}
bool slope_pro(int i,int j,int i2,int j2){
return ((dp[i]-b[i])-(dp[j]-b[j]))*(cq[i2]-cq[j2])<=((dp[i2]-b[i2])-(dp[j2]-b[j2]))*(cq[i]-cq[j]);
}
int find(int l,int r,long long now_slope){
int mid;
while (l<=r){
mid=(l+r)>>1;
if (slope(Q[mid+1],Q[mid])<=now_slope) l=mid+1;
else r=mid-1;
}
return Q[l];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>s;
for (int i=1;i<=n;i++) cin>>t[i]>>c[i];
for (int i=1;i<=n;i++) tq[i]=tq[i-1]+t[i];
for (int i=1;i<=n;i++) cq[i]=cq[i-1]+c[i];
for (int i=1;i<=n;i++) a[i]=tq[i]*cq[i];
for (int i=1;i<=n;i++) b[i]=s*cq[i];
int l=0,r=0;
dp[1]=dp[0]+a[1]-b[0]-tq[1]*cq[0]+s*cq[n];
for (int i=2;i<=n;i++){
// while (l<r&&slope(i-1,Q[r])<slope(i-1,Q[r-1])) r--;
// while (l<r&&((dp[i-1]-b[i-1])-(dp[Q[r]]-b[Q[r]]))*(cq[i-1]-cq[Q[r-1]])<((dp[i-1]-b[i-1])-(dp[Q[r-1]]-b[Q[r-1]]))*(cq[i-1]-cq[Q[r]])) r--;
while (l<r&&slope_pro(i-1,Q[r],i-1,Q[r-1])) r--;
Q[++r]=i-1;
// while (l<r&&slope(Q[l+1],Q[l])<tq[i]) l++;
int j=find(0,r-1,tq[i]);
dp[i]=dp[j]+a[i]-b[j]-tq[i]*cq[j]+s*cq[n];
}
cout<<dp[n]<<endl;
return 0;
}
讨论区提到的所有坑这份代码几乎都踩过力,人调傻了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)