情报传递者 (Harbingers.pas/c/cpp Time Limit:1s Memory Limit:256MB)
来源
CEOI 2009
分析
斜率优化+二分
用f[i]记从第i点到1号节点的最少时间,j表示i点的祖先节点
f[i]=min{f[j]+(d[i]−d[j])∗v[i]+s[i]};
将递推式展开为
f[i]=min{f[j]−d[j]∗v[i]}+d[i]∗c[i]+s[i]
,而d[i]*c[i]+s[i]是常量可以暂不考虑,因此v[i]*d[j]+f[i]=f[j]能够看作kx+b=y的一次函数形式,(d[j],f[j])看作平面直角坐标系中的点(画图理解),要取到
f[i]min
,想象一条斜率是v[i]的直线,从x轴下方向上移动,所经过的第一个点记为A(d[k],f[k]),满足
A左边直线斜率≤v[i]≤A右边直线斜率
,得到
f[i]min=f[k]−v[i]∗d[k]+d[i]∗c[i]+s[i]
,因此我们可以维护一个下凸壳,用二分实现移动选点过程。
同样需要用二分维护的还有插点的过程,首先二分找点,它是第一个满足其左边直线斜率小于该点与新点间连线斜率的点,记为B。用新点替换栈中B点的下一个点,并保存原状态信息以便返回时复原状态。
Attention:1.非递归遍历树,否则爆栈。
2. 注意各种二分的细节。
代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define x first
#define y second
using namespace std;
const int sm = 1e5+5;
typedef long long LL;
vector<pair<int,int > >e[sm];
pair<int,int > a[sm];
int N,t;
int q[sm],d[sm];
LL f[sm];
struct stack{
int x,fa,dep,k,_pos,_val,_t;
}st[sm];
char ch;
void read(int &x) {
x=0;ch=getchar();
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
}
void Initialize() {
read(N);
for(int i=1,x,y,z;i<N;++i) {
read(x);read(y);read(z);
e[x-1].push_back(make_pair(y-1,z));
e[y-1].push_back(make_pair(x-1,z));
}
for(int i=1;i<N;++i)
read(a[i].x),read(a[i].y);//要记得编号是-1了的
}
double cross(int x,int y) {
return (f[y]-f[x])*1.0/(d[y]-d[x]);
}
void work() {
int C=1;
st[C].fa = st[C].k = -1;
while(C) {
int x=st[C].x;
int fa=st[C].fa;
int dep=st[C].dep;
int &k=st[C].k;
int &_pos=st[C]._pos;//替换的位置编号
int &_val=st[C]._val;//替换的位置原值
int &_t=st[C]._t;//对于当前点的可二分范围
if(k==-1) {//不曾遍历过这个点 f[x]需要被更新
d[x]=dep;
f[x]=1ll*dep*a[x].y+a[x].x;
_t=t;
if(t>1) {
int p=q[0];
int l,r,mid;
if(cross(q[0],q[1])<a[x].y) {
for(l=0,r=t-2,mid=(l+r+1)>>1;l<r;mid=(l+r+1)>>1)//l:0 r:t-2 q[mid],q[mid+1]
if(cross(q[mid],q[mid+1])<a[x].y)
l=mid;
else r=mid-1;
p=q[l+1];
}
f[x]=min(f[x],f[p]+a[x].x+1ll*(dep-d[p])*a[x].y);
for(l=1,r=t-1,mid=(l+r+1)>>1;l<r;mid=(l+r+1)>>1)//l:1 r:t-1(t指向队尾的下一个编号)
if(cross(q[mid-1],q[mid])<cross(q[mid-1],x))l=mid;
else r=mid-1;
if(cross(q[l-1],q[l])>cross(q[l-1],x))--l;//换掉第二个点
_pos=l+1;_val=q[_pos];
t=l+2;q[t-1]=x;
}
else {//直接进队列 t>=1嘛
_pos=t;_val=q[_pos];
q[t++]=x;
}
++k;
}
else {
if(k<e[x].size()) {
if(e[x][k].x!=fa) {
st[++C].x=e[x][k].x;
st[C].fa=x;
st[C].dep=dep+e[x][k].y;
st[C].k=-1;//找另一个非父节点的子节点遍历
}
++k;
}
else {//返回时恢复状态
t=_t;
q[_pos]=_val;
C--;
}
}
}
}
int main() {
freopen("harbingers.in","r",stdin);
freopen("harbingers.out","w",stdout);
Initialize();//初始化
work();
for(int i=1;i<N;++i)
printf("%lld ",f[i]);
printf("\n");
return 0;
}
特别行动队(Commando.pas/c/cpp Time Limit:1s Memory Limit:256MB)
分析
单调队列+斜率优化
用f[i]表示1~i划分后的最大战斗力,则有
而若存在 H(d1,d2)>H(d2,d3) ,那么无论如何d2没有d1或d3优。可分类讨论如下:
1.若H(d1,d2)>H(d2,d3)>2*a*s[i],则d1优于d2,d2优于d3,d1最优。
2.若H(d1,d2)>2*a*s[i]>H(d2,d3),则d1d3均优于d2。
3.若2*a*s[i]>H(d1,d2)>H(d2,d3),则d2优于d1,d3优于d2,d3最优。
因次,可以利用这一性质将新元素压入单调队列。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int sm = 1e6+5;
int n,a,b,c,x;
int l,r,q[sm];
LL s[sm],f[sm];
LL across(int k,int j) {
return 1ll*((f[j]-f[k])+a*(s[j]*s[j]-s[k]*s[k])-b*(s[j]-s[k]));
}
LL calc(LL x) {
return a*x*x+b*x+c;
}
int main() {
freopen("commando.in","r",stdin);
freopen("commando.out","w",stdout);
scanf("%d%d%d%d",&n,&a,&b,&c);
for(int i=1;i<=n;++i)
scanf("%d",&x),s[i]=s[i-1]+x;
for(int i=1;i<=n;++i) {
while(l<r&&across(q[l],q[l+1])>2*a*s[i]*(s[q[l+1]]-s[q[l]]))l++;
int t=q[l];
f[i]=f[t]+calc(s[i]-s[t]);
while(l<r&&across(q[r-1],q[r])*(s[i]-s[q[r]])<across(q[r],i)*(s[q[r]]-s[q[r-1]]))r--;
q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}