动态规划测试test20170518

17 篇文章 0 订阅

这里写图片描述
这里写图片描述

题意:
一个长度为N的序列(每个元素是 (ai,bi) 这样的数对),连续地分成若干组。每组左右边界是 (l1,r1) , (l2,r2) ,⋯, (lp,rp) ,满足 li=ri1+1 , liri , l1=1 , rp=n 。分组必须满足两个条件:前面组的元素的b值比后面组元素的a值大;令 Mi 为第i个组内最大的a,所有 Mi 的和不超过 limit 。令Si为第i个组的元素的b的和,最小化 maxSi

题解:

二分答案。
能否破除b和a的关系呢?
考虑什么情况下必须捆绑在一起。
如果 ai>bj ,那么任意 bkbj ,都必须与 ai 捆绑在一起,否则不符合题意。
所以考虑排序 bj ,然后从大到小扫。
然后剩下的就没有限制了,于是问题就变成,给出一个数列,(每个组a的最大值)的和不能超过limit,(各组内b的的和)的最大值最小。
然后一个单调队列优化DP,具体细节看代码。
想交OJ的话可以看这个SPOJ.com - Problem SEQPAR2
然后lcy大神用线段树,我跑了8.72s,而Ta却只要4s,线段树题解看Ta的吧:http://blog.csdn.net/never_see/article/details/72802369

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int size = 50000+10;
const LL INF = 0x7fffffff;

int n,limit,q[size],a[size],b[size];
int dp[size],p1[size],p2[size];

bool check(int m) {
    int i,j=1,k,s=0,f=0,r=0;
    for(i=1; i<=n; i++) {
        s+=b[i];
        while(s>m) s-=b[j++];
        if(j>i) return 0;
        while(f<r and a[q[r-1]]<=a[i]) --r;
        while(f<r and q[f]<j) ++f;
        q[r++]=i;
        dp[i]=dp[j-1]+a[q[f]];
        for(k=f+1; k<r; k++)
            dp[i]=Min(dp[i],dp[q[k-1]]+a[q[k]]);
    }
    return dp[n]<=limit;
}

bool cmp(int x,int y) {
    return b[x]<b[y];
}

int main() {
    int i, j, l, r, mid;
    freopen("gold.in","r",stdin);
    freopen("gold.out","w",stdout);
    scanf("%d%d", &n, &limit);
    for (i = 1; i <= n; ++i)
        scanf("%d%d", &a[i], &b[i]), p1[i] = p2[i] = i;
    std::sort(p1 + 1, p1 + n + 1, cmp);
    for (j = 1, i = n; i >= 1; --i)
        for (; j <= n && b[p1[j]] <= a[i]; ++j)
            p2[p1[j]] = i;
    for (i = 1, j = 1; i <= n; i = l, ++j) {
        a[j] = a[i];
        b[j] = b[i];
        for (l = i + 1, r = Max(p2[i], i); l <= r; ++l) {
            a[j] = Max(a[j], a[l]);
            b[j] += b[l];
            r = Max(r, p2[l]);
        }
    }
    n = j - 1;
    for (l = 0, r = INF; l < r;) {
        mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%d\n", l);
    return 0;
}

这里写图片描述
这里写图片描述

题解:

这个题目有两种做法,一种直接用斜率优化,另一种在斜率优化的基础上进行点分治优化。
然后这个题目在NOI2014上被加强了为购票,题目和做法差不多,可以参见这个题目的做法。
代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
const int N = 100000+10;
const LL INF = 1LL<<60;

int n,g[N],nxt[N<<1],v[N<<1],w[N<<1],ok[N<<1],tot=1,son[N],f[N];
int size,now,fa[N],V[N];
int q[N],anc[N],t;
LL ans[N],d[N],W[N];

inline int read(int &in) {
    in=0;char ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar())
    in=in*10+ch-'0';
    return in;
}

inline void add_edge(int x,int y,int z) {
    v[++tot]=y;w[tot]=z;nxt[tot]=g[x];ok[tot]=1;g[x]=tot;
}

inline int up(LL &x,LL y) { if(x>y) x=y; }

inline double pos(int x,int y) {
    return (double)(ans[y]-ans[x])/(double)(d[y]-d[x]);
}

namespace my_self {
    inline void find_root(int x,int y) {
    son[x]=1;f[x]=0;
    for(int i=g[x];i;i=nxt[i])
        if(ok[i] and v[i]!=y) {
        find_root(v[i],x);son[x]+=son[v[i]];
        if(son[v[i]]>f[x]) f[x]=son[v[i]];
        }
    if(size-son[x]>f[x]) f[x]=size-son[x];
    if(f[x]<f[now]) now=x;
    }

    inline void erfen(int x) {
    int l=1,r=t-1,tmp=t,mid;
    while(l<=r) {
        mid=(l+r)>>1;
        if((double)V[x]<=pos(q[mid],q[mid+1]))
        r=(tmp=mid)-1;
        else l=mid+1;
    }
    up(ans[x],W[x]+ans[q[tmp]]-d[q[tmp]]*V[x]);
    }

    inline void dfs(int x) {
    W[x]+=d[x]*V[x];
    for(int i=g[x];i;i=nxt[i])
        if(v[i]!=fa[x])
        d[v[i]]=d[fa[v[i]]=x]+w[i],dfs(v[i]);
    }

    inline void cal(int x,int y) {
    erfen(x);
    for(int i=g[x];i;i=nxt[i])
        if(ok[i]&&v[i]!=y)cal(v[i],x);
    }

    inline void cal2(int x,int y) {
    up(ans[x],W[x]+ans[y]-d[y]*V[x]);
    for(int i=g[x];i;i=nxt[i])
        if(ok[i]&&v[i]!=fa[x])cal2(v[i],y);
    }

    inline void solve(int x) {
    f[0]=size=son[x];
    find_root(x,now=0);
    int root=now,ta;
    if(root!=x) {
        for(int i=g[root];i;i=nxt[i])
        if(v[i]==fa[root]) {
            ok[i]=ok[i^1]=0,solve(x);break;
        }

        int i;
        for(ta=0,i=fa[root];;i=fa[i]) {
        anc[++ta]=i;if(i==x) break;
        }
        for(t=0;ta;q[++t]=anc[ta--])
        while(t>1&&pos(anc[ta],q[t])<pos(q[t],q[t-1]))
            t--;
        if(root>1) erfen(root);
        while(t>1 and pos(root,q[t])<pos(q[t],q[t-1]))
          t--;
        q[++t]=root;
        for(int i=g[root];i;i=nxt[i]) if(ok[i]) cal(v[i],root);
        for(int i=g[root];i;i=nxt[i]) if(ok[i]) ok[i^1]=0,solve(v[i]);
    }
    else
        for(int i=g[x];i;i=nxt[i])
        if(ok[i]) ok[i^1]=0,cal2(v[i],x),solve(v[i]);
    }
}

int main() {
    freopen("harbingers.in","r",stdin);
    freopen("harbingers.out","w",stdout);
    read(n);
    for(int i=1;i<n;i++) {
    int x,y,z;read(x);read(y);read(z);
    add_edge(x,y,z);add_edge(y,x,z);
    }
    for(int i=2;i<=n;i++) {
    int x;read(x);read(V[i]);W[i]=x;ans[i]=INF;
    }
    son[1]=n;my_self::dfs(1);my_self::solve(1);
    for(int i=2;i<=n;i++)
    printf("%lld ",ans[i]);
    return 0;
}

这里写图片描述

原题APIO 2010特别行动队

斜率优化经典题

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
typedef long long ll;
const int maxn = 100000+10;
using namespace std;
int n,l,r;
ll a,b,c,q[maxn],f[maxn],s[maxn];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline double getk(int x,int y)
{
    return (double)((f[x]+a*s[x]*s[x]-b*s[x])-(f[y]+a*s[y]*s[y]-b*s[y]))/(double)(s[x]-s[y]);
}
int main()
{
    n=read();a=read();b=read();c=read();
    F(i,1,n) s[i]=s[i-1]+read();
    l=r=1;q[1]=0;
    F(i,1,n)
    {
        while (l<r&&getk(q[l],q[l+1])>a*s[i]*2) l++;
        f[i]=f[q[l]]+a*s[q[l]]*s[q[l]]-b*s[q[l]]-a*s[i]*s[q[l]]*2+a*s[i]*s[i]+b*s[i]+c;
        while (l<r&&getk(q[r-1],q[r])<getk(q[r],i)) r--;
        q[++r]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值