【DP】TEST20170518

10 篇文章 0 订阅

情报传递者 (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划分后的最大战斗力,则有

f[i]=max0<j<i{f[i],f[j]+a(s[i]s[j])2+b(s[i]s[j])+c}
    对于决策i的两种转移状态j,k,若j>k且j优于k,则有
f[j]+a(s[i]s[j])2+b(s[i]s[j])+c>f[k]+a(s[i]s[k])2+b(s[i]s[k])+c
    经变形后得到
(f[j]f[k]+a(s[j]2s[k]2)b(s[j]s[k]))/(s[j]s[k])>2as[i]
    若满足上式,记左式为H(j,k),s[i]单增,a<0,则H(j,k)将一直会满足上式,j一直优于k,可用这一性质更新队首元素。
    而若存在 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值