版权属于ZYXZYXZYX,想要引用此题(包括题面)的朋友请联系博主
题目来源:[CEOI 2004] Two sawmills
分析:
首先要吐槽题面。。。
其次要注意:我们只在山上建两个额外的加工厂
显然是斜率优化dp
xue微复习一下斜率优化dp吧
方法一
显然有状态:
f[i][j]
f
[
i
]
[
j
]
表示在位置
j
j
建立第座加工厂
f[i][j]=min(f[i−1][k]+SUM(k+1,j))
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
k
]
+
S
U
M
(
k
+
1
,
j
)
)
SUM(k+1,j)
S
U
M
(
k
+
1
,
j
)
表示
(k+1,j)
(
k
+
1
,
j
)
的树运送到位置
j
j
的加工厂的花费
首先我们要明确如何计算
dis
d
i
s
是距离前缀和
sum
s
u
m
是树叶重量前缀和
ds[j]=∑ji=1sum[i]dis[i]
d
s
[
j
]
=
∑
i
=
1
j
s
u
m
[
i
]
d
i
s
[
i
]
ds′[j]=∑ji=1sum[i−1]dis[i]
d
s
′
[
j
]
=
∑
i
=
1
j
s
u
m
[
i
−
1
]
d
i
s
[
i
]
如果我们在 j j 和分别建立两个加工厂,那么 (j+1,j+2) ( j + 1 , j + 2 ) 的树就应该运送到 i i 加工厂
=(sum[j+1]−sum[j])∗(dis[i]−dis[j+1])+(sum[j+2]−sum[j+1])∗(dis[i]−dis[j+2]) = ( s u m [ j + 1 ] − s u m [ j ] ) ∗ ( d i s [ i ] − d i s [ j + 1 ] ) + ( s u m [ j + 2 ] − s u m [ j + 1 ] ) ∗ ( d i s [ i ] − d i s [ j + 2 ] )
=sum[j+1]dis[i]−sum[j]dis[i]−sum[j+1]dis[j+1]+sum[j]dis[j+1]
=
s
u
m
[
j
+
1
]
d
i
s
[
i
]
−
s
u
m
[
j
]
d
i
s
[
i
]
−
s
u
m
[
j
+
1
]
d
i
s
[
j
+
1
]
+
s
u
m
[
j
]
d
i
s
[
j
+
1
]
+sum[j+2]dis[i]−sum[j+1]dis[i]−sum[j+2]dis[j+2]+sum[j+1]dis[j+2]
+
s
u
m
[
j
+
2
]
d
i
s
[
i
]
−
s
u
m
[
j
+
1
]
d
i
s
[
i
]
−
s
u
m
[
j
+
2
]
d
i
s
[
j
+
2
]
+
s
u
m
[
j
+
1
]
d
i
s
[
j
+
2
]
=dis[i](sum[j+2]−sum[j])−(sum[j+1]dis[j+1]+sum[j+2]dis[j+2])
=
d
i
s
[
i
]
(
s
u
m
[
j
+
2
]
−
s
u
m
[
j
]
)
−
(
s
u
m
[
j
+
1
]
d
i
s
[
j
+
1
]
+
s
u
m
[
j
+
2
]
d
i
s
[
j
+
2
]
)
+(sum[j]dis[j+1]+sum[j+1]dis[j+2])
+
(
s
u
m
[
j
]
d
i
s
[
j
+
1
]
+
s
u
m
[
j
+
1
]
d
i
s
[
j
+
2
]
)
所以我们可以得到以下的计算函数:
ll SUM(int x,int y) {
return dis[y]*(sum[y-1]-sum[x])-(ds[y-1]-ds[x])+(_ds[y-1]-_ds[x]);
}
现在关键来了
我们现在需要化式子完成斜率优化
假设现在转移的是
i
i
点,有两个转移点,其中
k
k
点更优,则有
f[j]+ds[j]−ds′[j]−(f[k]+ds[k]−ds′[k])>dis[i](sum[j]−sum[k]) f [ j ] + d s [ j ] − d s ′ [ j ] − ( f [ k ] + d s [ k ] − d s ′ [ k ] ) > d i s [ i ] ( s u m [ j ] − s u m [ k ] )
f[j]+ds[j]−ds′[j]−(f[k]+ds[k]−ds′[k])sum[j]−sum[k]>dis[i] f [ j ] + d s [ j ] − d s ′ [ j ] − ( f [ k ] + d s [ k ] − d s ′ [ k ] ) s u m [ j ] − s u m [ k ] > d i s [ i ]
get(j,k)=f[j]+ds[j]−ds′[j]−(f[k]+ds[k]−ds′[k])sum[j]−sum[k] g e t ( j , k ) = f [ j ] + d s [ j ] − d s ′ [ j ] − ( f [ k ] + d s [ k ] − d s ′ [ k ] ) s u m [ j ] − s u m [ k ]
做法总结如下:
- 用一个单调队列来维护解集
- 转移:
假设队列中从头到尾已经有元素 a,b,c a , b , c
当要转移 i i 点的时候,如果,那么说明 b b 点比点更优, a a 点可以排除,于是出队
直到 get(x,y)>=dis[i] g e t ( x , y ) >= d i s [ i ] ,当前点就从 x x 转移 - 入队:
假设队列的尾部已经有元素
那么当 d d 要入队的时候,我们维护队列的上凸性质,
即如果,那么就将 c c 点删除
(我们要使,显然 get(d,c) g e t ( d , c ) 较小,是不会成为优秀转移点的)
直到找到 get(d,x)>=get(x,y) g e t ( d , x ) >= g e t ( x , y ) 为止,并将 d d 点加入在该位置中
tip
注意:我们只在山上建两个额外的加工厂
所以我们要枚举加工厂个数转移
是转移数组,
f
f
是答案数组,是辅助
g
g
数组的队列,是辅助
f
f
数组的队列
注意get_p和get_q的运用
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=20005;
ll dis[N],sum[N],ds[N],_ds[N];
ll f[N],g[N];
int n,Q[N],P[N],tq=0,wq=0,tp=0,wp=0;
ll SUM(int x,int y) {
return dis[y]*(sum[y-1]-sum[x])-(ds[y-1]-ds[x])+(_ds[y-1]-_ds[x]);
}
double get_q(int j,int k) {
return (double)(g[j]+ds[j]-_ds[j]-(g[k]+ds[k]-_ds[k]))/(double)(sum[j]-sum[k]);
}
double get_p(int j,int k) {
return (double)(f[j]+ds[j]-_ds[j]-(f[k]+ds[k]-_ds[k]))/(double)(sum[j]-sum[k]);
}
int main() {
scanf("%d",&n);
for (int i=1;i<=n;i++) {
int x,y;
scanf("%d%d",&x,&y);
sum[i]=sum[i-1]+(ll)x;
dis[i+1]=dis[i]+(ll)y;
}
n++; sum[n]=sum[n-1]; dis[n+1]=dis[n];
for (int i=1;i<=n;i++) {
ds[i]=ds[i-1]+sum[i]*dis[i];
_ds[i]=_ds[i-1]+dis[i]*sum[i-1];
}
memset(g,0,sizeof(g));
for (int i=1;i<=3;i++) {
for (int j=1;j<=n;j++) {
while(tq<wq&&get_q(Q[tq],Q[tq+1])<dis[j]) tq++;
f[j]=g[Q[tq]]+SUM(Q[tq],j);
while(tp<wp&&get_p(P[wp],j)<get_p(P[wp-1],j)) wp--;
P[++wp]=j;
}
memcpy(Q,P,sizeof(P));
tq=tp; wq=wp;
tp=wp=0;
memcpy(g,f,sizeof(g));
}
printf("%lld\n",f[n]);
return 0;
}
方法二
随机化,参考自《浅谈随机化在信息学竞赛中的应用》(刘家骅)
我们建立一个矩阵,
P[X,Y]
P
[
X
,
Y
]
表示第一个锯木场建立在
X
X
,第二个锯木场建立在时的总运费
一开始时,矩阵的边长为
N
N
我们随机寻找一定数量的点
(如下左图所示,取点数量应该充分利用时限并且注意效率,由于矩阵的大小一直在变化,推荐使用矩阵大小的定比确定取点数量)
计算出ta们的值,取其最小点,以这个点为新矩阵的中心,以现在矩阵的边长的的长度为新矩形的边长
(如下右图所示)
从原来的矩阵中取出一块作为新矩阵的范围
(若新矩阵的范围出了原矩阵的边界就将其向里移动到原矩阵内)
然后继续在新矩阵中重复这样的操作,直至新矩阵足够小时,我们即可枚举新矩阵上的每一个点,取其中最小值作为答案
随意看一下代码吧
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<ctime>
#define ll long long
using namespace std;
const int N=20005;
ll n,w[N],d[N],dis[N],sum[N],co[N];
ll rx[210],ry[210],ans[210];
ll get(ll i,ll j) {
return co[n+1]-sum[j]*(dis[i]-dis[j])-sum[i]*(dis[n+1]-dis[i]);
}
void solve() {
double T=n*1.1;
while (T>1) {
for (ll i=1;i<=30;i++) {
for (ll j=1;j<=80;j++) {
ll nx=(ll)rx[i]*(1-(double)((rand()%((ll)(T+0.5)+1))/(double)n)+(double)((rand()%((ll)(T+0.5)+1))/(double)n));
ll ny=(ll)ry[i]*(1-(double)((rand()%((ll)(T+0.5)+1))/(double)n)+(double)((rand()%((ll)(T+0.5)+1))/(double)n));
while (ny==nx)
ny=(ll)ry[i]*(1-(double)((rand()%((ll)(T+0.5)+1))/(double)n)+(double)((rand()%((ll)(T+0.5)+1))/(double)n));
if (nx<1||nx>n||ny<1||ny>n) continue;
ll tmp=get(nx,ny);
if (tmp<ans[i]) {
ans[i]=tmp;
rx[i]=nx;
ry[i]=ny;
}
}
}
T*=0.2;
}
}
int main()
{
scanf("%d",&n);
srand(time(NULL));
for (ll i=1;i<=n;i++) {
scanf("%lld%lld",&w[i],&d[i]);
dis[i]=dis[i-1]+d[i-1];
sum[i]=sum[i-1]+w[i];
co[i]=co[i-1]+d[i-1]*sum[i-1];
}
sum[n+1]=sum[n];
dis[n+1]=dis[n]+d[n];
co[n+1]=co[n]+sum[n]*d[n];
for (ll i=1;i<=80;i++) {
rx[i]=rand()%n+1; //随机的点
ry[i]=rand()%n+1;
while (ry[i]==rx[i]) ry[i]=rand()%n+1;
ans[i]=get(rx[i],ry[i]);
}
solve();
ll Ans=2e9+7;
for (ll i=1;i<=30;i++) Ans=min(Ans,ans[i]);
printf("%lld",Ans);
}