[日常训练] God Knows

来源:互联网 发布:大数据上市公司有几家 编辑:程序博客网 时间:2024/06/06 19:15

分析 DP + 线段树优化

40pts

  • 把问题转化为求极长不下降子序列(就是不能在选出的子序列中插入别的元素使序列更长,否则我们就无法删去所有边)中的最小花费
  • 于是我们可以设F[i]表示以i开头的极长不下降子序列的最小花费,则转移方程为F[i]=min(F[j]+ci)(i<jn)(为了方便下面的说明这里的i是从n开始倒着循环,且j满足不存在k(i<k<j),使得ci<ck<cj
  • 那么就可以从i+1开始转移,用一个变量记录当前大于ci的最小代价来判断是否存在上述的k,时间复杂度为O(n2)

100pts

  • 首先把每个二元组(pi,i)看做平面直角坐标系中的点,那么对于i合法的转移点就是横坐标范围为[pi+1,n],且随横坐标递增,纵坐标不断递减的点
  • 因此以pi为下标维护一棵线段树,分别记录每个区间的最小合法iy和最小合法Ff
  • 先考虑询问,每次会通过线段树把[pi+1,n]分成若干个区间,显然我们要从左往右合并信息来保证极长,那么如何合并呢?
  • 定义一个函数Ask(s,y),表示在线段树节点s的区间内,纵坐标小于yy是把之前的区间都合并完后的最小i值)的f值(为表示方便,令"+"为将两个区间yf的信息合并,即都取最小)
    • s为叶节点,直接判断是否可转移
    • ysL<y时,左区间存在可转移的点,则Ask(s,y)=Ask(sL,y)+Ask(sR,ysL)
    • ysL>y时,Ask(s,i)=Ask(sR,i)
  • 我们发现Ask(sR,ysL)y值无关,因此也作为区间维护的信息保存下来,就降低了复杂度
  • 接着我们考虑如何维护,记Ask(sR,ysL)返回信息为Q,原本我们维护的yfP,那么Ps=PsL+Qs
  • 最后对于修改,我们只要把对应pi上的P修改下,再按照上述操作更新相关区间的P,Q即可,总复杂度为O(nlog2n)

代码

#include <iostream>#include <cstdio>#include <cstring>#include <cctype>#include <algorithm>using namespace std;namespace INOUT{    const int S = 1 << 20;    char frd[S], *hed = frd + S;    const char *tal = hed;    inline char nxtChar()    {        if (hed == tal)            fread(frd, 1, S, stdin), hed = frd;        return *hed++;     }    inline int get()    {        char ch; int res = 0; bool flag = false;        while (!isdigit(ch = nxtChar()) && ch != '-');        (ch == '-' ? flag = true : res = ch ^ 48);        while (isdigit(ch = nxtChar()))            res = res * 10 + ch - 48;        return flag ? -res : res;     } };using namespace INOUT;inline int Min(int x, int y) {return x < y ? x : y;}struct section{    int y, f;    section() {}    section(const int &Y, const int &F):        y(Y), f(F) {}    friend inline section Merge(const section &a, const section &b)    {        return section(Min(a.y, b.y), Min(a.f, b.f));    }};const int N = 2e5 + 5, M = N << 2;section p[M], q[M], Ans; int f[N], C[N], P[N], n;const int Maxn = 0x3f3f3f3f;const section INF = section(Maxn, Maxn);#define sL s << 1#define sR s << 1 | 1inline section Ask(int s, int l, int r, int y){    if (l == r)         return p[s].y < y ? p[s] : INF;    int mid = l + r >> 1;     if (p[sL].y < y)         return Merge(Ask(sL, l, mid, y), q[s]);    else         return Ask(sR, mid + 1, r, y);}inline void Update(int s, int l, int r){    int mid = l + r >> 1;    q[s] = Ask(sR, mid + 1, r, p[sL].y);    p[s] = Merge(p[sL], q[s]);}inline void Modify(int s, int l, int r, int x, int y, int f){    if (l == r) return (void)(p[s] = section(y, f));    int mid = l + r >> 1;    if (x <= mid)         Modify(sL, l, mid, x, y, f);    else         Modify(sR, mid + 1, r, x, y, f);    Update(s, l, r);}inline void Query(int s, int l, int r, int x, int y){    if (l == x && r == y)         return (void)(Ans = Merge(Ans, Ask(s, l, r, Ans.y)));    int mid = l + r >> 1;    if (y <= mid)         Query(sL, l, mid, x, y);    else if (x > mid)        Query(sR, mid + 1, r, x, y);    else     {        Query(sL, l, mid, x, mid);        Query(sR, mid + 1, r, mid + 1, y);    }}inline void Build(int s, int l, int r){    p[s] = q[s] = INF;    if (l == r) return;    int mid = l + r >> 1;    Build(sL, l, mid);    Build(sR, mid + 1, r);}int main(){    freopen("knows.in", "r", stdin);    freopen("knows.out", "w", stdout);    n = get();    for (int i = 1; i <= n; ++i) P[i] = get();    for (int i = 1; i <= n; ++i) C[i] = get();    P[0] = 1; Build(1, 1, n);    for (int i = n; i >= 0; --i)    {        Ans = INF; Query(1, 1, n, P[i], n);         f[i] = Ans.f == Maxn ? C[i] : Ans.f + C[i];        Modify(1, 1, n, P[i], i, f[i]);    }    printf("%d\n", f[0]);    fclose(stdin); fclose(stdout);    return 0;} 
原创粉丝点击