NOI2014 购票

题目描述

题解:

这位仁兄您点进来的题解是cdq+点分+斜率优化的。

吐草:细节是真多……

先推一波式子:

$dp[i]=min(dp[j]+(dis[i]-dis[j])*p[i]+q[i])=dis[i]*p[i]+q[i]+min(dp[j]-dis[j]*p[i])$

$min()$里面那个明显是斜率优化。

这个转移要求更新i时1~i路径上的所有点均已经是最优解。

然后你就想到了$cdq$分治。

$cdq$分治一段区间操作:分治左区间,处理左区间->右区间,分治右区间;

毒瘤题购票树上操作:处理深度浅的,处理浅的->深的,处理深度深的。

这好像是一样的。

所以我们可以把$cdq$中的$mid$换成点分中的重心。

然后重心上面的更新重心和重心下面的,再让重心更新重心下面的。

然后就过了。

~~~

有几个大坑:

1.叉积爆$long long$,要用$double$除法。

2.由于让$b$值最小我们应该维护下凸包,但是$dis$是倒序加入。不妨将$x,y$都取反,然后维护正向加入的上凸包。

代码:

#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200050
#define ll long long 
#define db long double
#define nd (ve.size()-1)
const int inf = 0x3f3f3f3f;
const db eps = 1e-8;
inline ll rd()
{
    ll f=1,c=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
    return f*c;
}
int n,t,hed[N],cnt;
struct pnt
{
    int fa;
    ll p,q,l;
}c[N];
struct EG
{
    int to,nxt;
    ll v;
}e[2*N];
void ae(int f,int t,ll v)
{
    e[++cnt].to = t;
    e[cnt].nxt = hed[f];
    e[cnt].v = v;
    hed[f] = cnt;
}
ll dis[N];
void dfs(int u,int fa)
{
    for(int j=hed[u];j;j=e[j].nxt)
    {
        int to = e[j].to;
        if(to==fa)continue;
        dis[to] = dis[u] + e[j].v;
        dfs(to,u);
    }
}
int rt,sum,mrk[N],w[N],siz[N];
void get_rt(int u,int fa)
{
    w[u] = 0,siz[u] = 1;
    for(int j=hed[u];j;j=e[j].nxt)
    {
        int to = e[j].to;
        if(to==fa||mrk[to])continue;
        get_rt(to,u);
        siz[u]+=siz[to];
        if(siz[to]>w[u])w[u] = siz[to];
    }
    w[u] = max(w[u],sum-siz[u]);
    if(w[u]<w[rt])rt=u;
}
ll dp[N];
int pu[N],pd[N];
bool cmp(int x,int y)
{
    return dis[x]-c[x].l>dis[y]-c[y].l;
}
int tot1,tot2;
void push_up(int p1,int p2)
{
    tot1 = 0;
    while(p2!=p1)
    {
        p2 = c[p2].fa;
        pu[++tot1] = p2;
    }
}
void fill(int u)
{
    pd[++tot2] = u;
    for(int j=hed[u];j;j=e[j].nxt)
    {
        int to = e[j].to;
        if(to==c[u].fa||mrk[to])continue;
        fill(to);
    }
}
void push_down(int p2)
{
    tot2 = 0;fill(p2);
    sort(pd+1,pd+1+tot2,cmp);
}
double slop(int a,int b)
{
    return (double)(dp[a]-dp[b])/(dis[a]-dis[b]);
}
int fd(db k,vector<int>&ve)
{
    int l = 0,r = nd-1,ans = nd;
    while(l<=r)
    {
        int mid = (l+r)>>1;
        if(slop(pu[ve[mid]],pu[ve[mid+1]])<k)
        {
            ans = mid;
            r = mid-1;
        }else l = mid+1;
    }
    return ans;
}
void cdq(int p1,int p2)
{
    mrk[p2] = 1;
    if(p1!=p2)
    {
        rt = 0,sum = siz[p1] - siz[p2];
        get_rt(p1,0);
        cdq(p1,rt);
    }
    push_up(p1,p2);
    push_down(p2);
    int l = 1,r = 1;
    vector<int>ve;
    for(;r<=tot2;r++)
    {
        while(l<=tot1&&dis[pu[l]]>=dis[pd[r]]-c[pd[r]].l)
        {
            while(ve.size()>=2&&slop(pu[l],pu[ve[nd]])>=slop(pu[ve[nd]],pu[ve[nd-1]]))
                ve.pop_back();
            ve.push_back(l);
            l++;
        }
        if(!ve.size())continue;
        int u = pu[ve[fd(c[pd[r]].p,ve)]],v = pd[r];
        dp[v] = min(dp[v],dp[u]-dis[u]*c[v].p+c[v].q);
    }
    for(r=tot2;dis[pd[r]]-c[pd[r]].l<=dis[p2]&&r>=1;r--)
    {
        int v = pd[r],u = p2;
        dp[v] = min(dp[v],dp[u]-dis[u]*c[v].p+c[v].q);
    }
    for(int j=hed[p2];j;j=e[j].nxt)
    {
        int to = e[j].to;
        if(to==c[p2].fa||mrk[to])continue;
        rt=0,sum=siz[to];
        get_rt(to,0);
        cdq(to,rt);
    }
}
int main()
{
//    freopen("1.in","r",stdin);
    n = rd(),t = rd();ll s;
    for(int f,i=2;i<=n;i++)
    {
        f = rd(),s = rd(),c[i].p = rd(),c[i].q = rd(),c[i].l = rd();
        ae(f,i,s),ae(i,f,s);
        c[i].fa = f;
    }
    dfs(1,0);
    for(int i=2;i<=n;i++)c[i].q+=dis[i]*c[i].p;
    memset(dp,0x3f,sizeof(dp));
    w[0] = inf;dp[1]=0;
    rt=0,sum=n;
    get_rt(1,0);
    cdq(1,rt);
    for(int i=2;i<=n;i++)
        printf("%lld\n",dp[i]);
    return 0;
}

 

转载于:https://www.cnblogs.com/LiGuanlin1124/p/10190196.html

P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值