In mathematics you don't understand things, you just get used to them.

标签 dp 下的文章

link。

首先对原序列排序,考虑静态序列做法为:设 $f(n,k\in\{0,1\})$ 为对于前 $n$ 个数,第 $n$ 个数否 / 是已经决策完毕的最优方案,转移即

$$ \begin{cases} f(n,0)=f(n-1,1) \\ \displaystyle f(n,1)=\lvert a(n)-a(n-1)\rvert+\min\{f(n-1,\{0,1\})\} \end{cases} $$

再由于增 / 减数操作,我们使用数据结构来维护,如果使用维护区间的数据结构那么区间 DP 是最好的选择。把状态换为 $f(l,r,k_1\in\{0,1\},k_2\in\{0,1\})$ 表示考虑区间 $[l,r]$,左 / 右端点分别否 / 是已经决策完毕。转移即

$$ f(l,r,k_1,k_2)=\min_{a+b>1}\{f(l,m,k_1,a),f(m,r,b,k_2)\} $$

注意使用不同的数据结构转移会有不同,此处为平衡树情况的转移。不过代码写的是线段树,请注意区分。$m$ 为 $[l,r]$ 中一点,也需要注意 $r-l\leqslant2$ 情况的特判。

省去了一些东西,完整代码见此处,特征码 cc-strquer

const long long inf = 1e18;
namespace 😅😅 {
struct node {
  int ls, rs, num;
  long long l, r, mx, mn;
  long long dp[2][2];
  long long *const operator[](const long long i) { return dp[i]; }
} 😅[2 * 200000 * 60];
int tot;
int fuck😅(long long l, long long r) {
  int shit = ++tot;
  😅[shit].ls = 😅[shit].rs = 0;
  😅[shit].l = l, 😅[shit].r = r;
  😅[shit].num = 0;
  return shit;
}
void Merge(node &$😅, node x, node y) {
  node ap;
  bool flag = 0;
  if (!x.num) {
    ap = y;
    flag = 1;
  }
  if (!y.num) {
    ap = x;
    flag = 1;
  }
  if (flag) {
    $😅.mn = ap.mn;
    $😅.mx = ap.mx;
    for (int i = 0; i < 2; ++i) {
      for (int j = 0; j < 2; ++j) $😅[i][j] = ap[i][j];
    }
    return;
  }
  $😅.mn = x.mn;
  $😅.mx = y.mx;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 2; ++j) {
      $😅[i][j] = -inf;
      for (int a = 0; a < 2; ++a) {
        for (int b = 0; b < 2; ++b)
          Max($😅[i][j], x[i][a] + y[b][j] + a * b * (y.mn - x.mx));
      }
    }
  }
}
void add(int p, long long x, int v) {
  auto &l = 😅[p].l, &r = 😅[p].r;
  if (!p) p = ++tot;
  😅[p].num += v;
  if (l == r) {
    😅[p].mx = 😅[p].mn = x;
    😅[p][0][0] = 😅[p][1][1] = -inf;
    😅[p][0][1] = 😅[p][1][0] = 0;
    return;
  }
  long long mid = (l + r) >> 1;
  if (mid >= x) {
    if (!😅[p].ls) 😅[p].ls = fuck😅(l, mid);
    add(😅[p].ls, x, v);
  } else {
    if (!😅[p].rs) 😅[p].rs = fuck😅(mid + 1, r);
    add(😅[p].rs, x, v);
  }
  Merge(😅[p], 😅[😅[p].ls], 😅[😅[p].rs]);
}
}  // namespace 😅😅
signed main() {
  int T;
  for (cin > T; T; --T) {
    int n, q;
    cin > n > q;
    😅😅::fuck😅(0, inf);
    for (long long x; n; --n) {
      cin > x;
      😅😅::add(1, x, 1);
    }
    for (int t; q; --q) {
      long long x;
      cin > t > x;
      if (t == 0)
        😅😅::add(1, x, 1);
      else
        😅😅::add(1, x, -1);
      cout < 😅😅::😅[1].mx - 😅😅::😅[1].mn - imax(😅😅::😅[1][1][1], 0LL) < '\n';
    }
    😅😅::tot = 0;
  }
  return 0;
}

Description

  Link.

  有 $n$ 棵树,每棵的高度为 $a(i)$,看到一棵树对答案的贡献为 $a(i-1)+a(i)+a(i+1)$(未定义范围为 $0$),求使得答案最小的砍树顺序的数量。

Solution

  口胡瑇师。不过这个 F 比上次的 Lagrange 插值阳间多了。

  考虑每一个元素的贡献次数。发现这个次数的区间是 $[1,3]$,对应树 $i$ 在树 $i-1/i+1$ 之前 / 之后砍倒的情况。

  那么我们直接贪心,使得答案最小的砍树顺序一定是:

  • $a(i)<a(i+1)$ 先砍 $i+1$,再砍 $i$;
  • otherwise:先砍 $i$,再砍 $i+1$。

  然后就可以 DP 仂。设 $f(i,j)$ 为树 $i$ 在是第 $j$ 个被砍的排列数,注意这里的 $j$ 是相对的

  • $a(i-1)=a(i)$:$f(i,j)=\sum_{k=1}^{i}f(i-1,k)$;
  • $a(i-1)<a(i)$:$f(i,j)=\sum_{k=j}^{i}f(i-1,k)$;
  • $a(i-1)>a(i)$:$f(i,j)=\sum_{k=1}^{j}f(i-1,k)$。

  使用前缀和优化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
    ll x=0,f=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+(ch&15),ch=getchar();
    return f?-x:x;
}
const int N=4100,MOD=1e9+7;
ll dp[N][N],sum[N],a[N];
signed main() {
    int n=read();
    for(int i=1; i<=n; ++i) a[i]=read();
    dp[1][1]=1;
    for(int i=2; i<=n; ++i) {
        for(int j=1; j<i; ++j) (sum[j]=sum[j-1]+dp[i-1][j])%=MOD;
        for(int j=1; j<=i; ++j)
            if(a[i]==a[i-1]) dp[i][j]=sum[i-1];
            else if(a[i]>a[i-1]) dp[i][j]=(sum[i-1]-sum[j-1]+MOD)%MOD;
            else dp[i][j]=sum[j-1];
    }
    ll ans=0;
    for(int i=1; i<=n; ++i) (ans+=dp[n][i])%=MOD;
    printf("%lld\n",ans);
    return 0;
}

Description

  Link.

  有 $n$ 棵树,每棵的高度为 $a(i)$,看到一棵树对答案的贡献为 $a(i-1)+a(i)+a(i+1)$(未定义范围为 $0$),求使得答案最小的砍树顺序的数量。

Solution

  口胡瑇师。不过这个 F 比上次的 Lagrange 插值阳间多了。

  考虑每一个元素的贡献次数。发现这个次数的区间是 $[1,3]$,对应树 $i$ 在树 $i-1/i+1$ 之前 / 之后砍倒的情况。

  那么我们直接贪心,使得答案最小的砍树顺序一定是:

  • $a(i)<a(i+1)$ 先砍 $i+1$,再砍 $i$;
  • otherwise:先砍 $i$,再砍 $i+1$。

  然后就可以 DP 仂。设 $f(i,j)$ 为树 $i$ 在是第 $j$ 个被砍的排列数,注意这里的 $j$ 是相对的

  • $a(i-1)=a(i)$:$f(i,j)=\sum_{k=1}^{i}f(i-1,k)$;
  • $a(i-1)<a(i)$:$f(i,j)=\sum_{k=j}^{i}f(i-1,k)$;
  • $a(i-1)>a(i)$:$f(i,j)=\sum_{k=1}^{j}f(i-1,k)$。

  使用前缀和优化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
    ll x=0,f=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+(ch&15),ch=getchar();
    return f?-x:x;
}
const int N=4100,MOD=1e9+7;
ll dp[N][N],sum[N],a[N];
signed main() {
    int n=read();
    for(int i=1; i<=n; ++i) a[i]=read();
    dp[1][1]=1;
    for(int i=2; i<=n; ++i) {
        for(int j=1; j<i; ++j) (sum[j]=sum[j-1]+dp[i-1][j])%=MOD;
        for(int j=1; j<=i; ++j)
            if(a[i]==a[i-1]) dp[i][j]=sum[i-1];
            else if(a[i]>a[i-1]) dp[i][j]=(sum[i-1]-sum[j-1]+MOD)%MOD;
            else dp[i][j]=sum[j-1];
    }
    ll ans=0;
    for(int i=1; i<=n; ++i) (ans+=dp[n][i])%=MOD;
    printf("%lld\n",ans);
    return 0;
}

「CF 1525A」Potion-making

Link.

显然。

#include<bits/stdc++.h>
typedef long long ll;
template<typename T>void sf(T &x){x=0;T f=0;char c=getchar();for(;c<'0'||c>'9';c=getchar())if(c=='-')f=1;for(;c>='0'&&c<='9';c=getchar())x=(x<<3)+(x<<1)+(c^'0');if(f)x=-x;}
template<typename T>void pf(T x,char l='\n'){static T s[100],t;if(x<0)putchar('-'),x=-x;do s[++t]=x%10,x/=10;while(x);while(t)putchar(s[t--]^'0');putchar(l);}
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int main()
{
    int T,k;
    sf(T);
    while(T-->0)
    {
        sf(k);
        int p1=k,p2=100-k,x=gcd(p1,p2);
        p1/=x;
        p2/=x;
        pf(p1+p2);
    }
    return 0;
}

「CF 1525B」Permutation Sort

Link.

注意到答案只有 $0/1/2/3$,分类讨论即可。

吃了三发罚时

#include<bits/stdc++.h>
typedef long long ll;
template<typename T>void sf(T &x){x=0;T f=0;char c=getchar();for(;c<'0'||c>'9';c=getchar())if(c=='-')f=1;for(;c>='0'&&c<='9';c=getchar())x=(x<<3)+(x<<1)+(c^'0');if(f)x=-x;}
template<typename T>void pf(T x,char l='\n'){static T s[100],t;if(x<0)putchar('-'),x=-x;do s[++t]=x%10,x/=10;while(x);while(t)putchar(s[t--]^'0');putchar(l);}
int T,n,a[60];
int main()
{
    for(sf(T);T;--T)
    {
        sf(n);
        for(int i=1;i<=n;++i)    sf(a[i]);
        int flag=1;
        for(int i=2;i<=n;++i)
        {
            if(a[i]!=a[i-1]+1)
            {
                flag=0;
                break;
            }
        }
        if(!flag)
        {
            if(a[1]==n && a[n]==1)    puts("3");
            else if(a[n]==n || a[1]==1)    puts("1");
            else    puts("2");
        }
        else    puts("0");
    }
    return 0;
}

「CF 1525C」Robot Collisions

Link.

显然我不会。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
template<typename T>void sf(T &x) {
    x = 0;
    T f = 0;
    char c = getchar();

    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-')
            f = 1;

    for (; c >= '0' && c <= '9'; c = getchar())
        x = (x << 3) + (x << 1) + (c^'0');

    if (f)
        x = -x;
}
template<typename T>void pf(T x, char l = '\n') {
    static T s[100], t;

    if (x < 0)
        putchar('-'), x = -x;

    do
        s[++t] = x % 10, x /= 10;

    while (x)
        ;

    while (t)
        putchar(s[t--]^'0');

    putchar(l);
}
struct st {
    int a, b, id;
} Sts[300005], Uji[300005], Qql[300005];
bool cmp(st A, st B) {
    return A.a < B.a;
}
int reait[300005];
int n, m;
int U(int x, int y) {
    int res = 0;

    if (Sts[x].a < Sts[y].a) {
        if (Sts[x].b == 0)
            res += Sts[x].a, Sts[x].a = 0;

        if (Sts[y].b == 1)
            res += (m - Sts[y].a), Sts[y].a = m;

        return (res + abs(Sts[x].a - Sts[y].a)) / 2;
    } else {
        if (Sts[x].b == 1)
            res += (m - Sts[x].a), Sts[x].a = m;

        if (Sts[y].b == 0)
            res += Sts[y].a, Sts[y].a = 0;

        return (res + abs(Sts[x].a - Sts[y].a)) / 2;
    }
}
signed main() {
    int T;
    sf(T);

    while (T--) {
        sf(n), sf(m);

        for (int i = 1; i <= n; i++)
            reait[i] = -1;

        int rks = 0, tss = 0;

        for (int i = 1; i <= n; i++) {
            sf(Sts[i].a);
            Sts[i].id = i;
        }

        for (int i = 1; i <= n; i++) {
            char res = getchar();

            while (res != 'L' && res != 'R')
                res = getchar();

            Sts[i].b = res == 'R';
        }

        for (int i = 1; i <= n; i++) {
            if (Sts[i].a & 1)
                Qql[++tss] = Sts[i];
            else
                Uji[++rks] = Sts[i];
        }

        sort(Uji + 1, Uji + rks + 1, cmp);
        sort(Qql + 1, Qql + tss + 1, cmp);
        stack <int> Q;
        Q.push(1);

        for (int i = 2; i <= rks; i++) {
            if (Uji[i].b == 0 && !Q.empty()) {
                int u = Q.top();
                Q.pop();
                reait[Uji[i].id] = reait[Uji[u].id] = U(Uji[i].id, Uji[u].id);
            } else
                Q.push(i);
        }

        while (!Q.empty()) {
            int u = Q.top();
            Q.pop();

            if (!Q.empty()) {
                int v = Q.top();
                Q.pop();
                reait[Uji[u].id] = reait[Uji[v].id] = U(Uji[u].id, Uji[v].id);
            }
        }

        Q.push(1);

        for (int i = 2; i <= tss; i++) {
            if (Qql[i].b == 0 && !Q.empty()) {
                int u = Q.top();
                Q.pop();
                reait[Qql[i].id] = reait[Qql[u].id] = U(Qql[i].id, Qql[u].id);
            } else
                Q.push(i);
        }

        while (!Q.empty()) {
            int u = Q.top();
            Q.pop();

            if (!Q.empty()) {
                int v = Q.top();
                Q.pop();
                reait[Qql[u].id] = reait[Qql[v].id] = U(Qql[u].id, Qql[v].id);
            }
        }

        for (int i = 1; i <= n; i++)
            printf("%lld ", reait[i]);

        printf("\n");
    }

    return 0;
}

「CF 1525D」Armchairs

Link.

设 $f(i,j)$ 表示考虑把第 $j$ 个人放进前 $i$ 个座位里的方案,转移。

多久没写 DP 了啊,这种水平的 DP 都写漏一堆东西。

#include<bits/stdc++.h>
typedef long long ll;
template<typename T>void sf(T &x){x=0;T f=0;char c=getchar();for(;c<'0'||c>'9';c=getchar())if(c=='-')f=1;for(;c>='0'&&c<='9';c=getchar())x=(x<<3)+(x<<1)+(c^'0');if(f)x=-x;}
template<typename T>void pf(T x,char l='\n'){static T s[100],t;if(x<0)putchar('-'),x=-x;do s[++t]=x%10,x/=10;while(x);while(t)putchar(s[t--]^'0');putchar(l);}
int n,a[5010],fr[5010],t0,oc[5010],t1,op[5010];
ll dp[5010][5010],ans=std::numeric_limits<ll>::max();
inline int fs(int x){return x<0?-x:x;}
int main()
{
    sf(n);
    for(int i=1;i<=n;++i)
    {
        sf(a[i]);
        if(a[i])    oc[++t1]=i;
        else    fr[++t0]=i;
    }
    for(int i=1;i<=t1;++i)    for(int j=0;j<=n;++j)    dp[i][j]=1e18;
    for(int i=1;i<=t1;++i)    for(int j=1;j<=n;++j) {
        dp[i][j]=dp[i][j-1];
        if(!a[j])    dp[i][j]=std::min(dp[i][j],dp[i-1][j-1]+fs(oc[i]-j));
    }
    pf(dp[t1][n]);
    return 0;
}

「CF 1525E」Assimilation IV

Link.

// Oops, something went wrong.

「CF 1525F」Goblins And Gnomes

Link.

// Oops, something went wrong.

Description

Link.

一共 $n$ 天,每天可以卖出或者买入两种股票 $A$ 和 $B$。这两种股票在第 $i$ 天的价值为 $A_i$ 和 $B_i$。

每天可以花所有的现金买入股票,这些股票中 $A$ 股与 $B$ 股的数量比为 $rate_i$。每天也可以把所有的股票以当天的价值卖出,获得现金。已知接下来 $n$ 天的 $A_i,B_i,rate_i$,求出 $n$ 天后能够获得的最大价值。

Solution

设 $f(i)$ 为第 $i$ 天的最大钱数,$g_{1}(i)$ 为 A 券的第 $i$ 天能换的数量,$g_{2}(i)$ 则为 B 券。

转移可以解方程得:

$$ f(i)=\max\{f(i-1),a(i)g_{1}(j),b(i)g_{2}(j)\},j\in[1,i) \\ g_{1}(i)\frac{f(i)rate(i)}{a(i)rate(i)+b(i)} \\ g_{2}(i)=\frac{f(i)}{rate(i)\times a(i)+b(i)} \\ $$

两个 $g$ 都没啥问题,主要来看 $f(i)$。提一下可得:

$$ f(i)=\max\{b(i)\max_{j=1}^{i-1}\{\frac{a(i)}{b(i)}g_{1}(j)+g_{2}(j)\},f(i-1)\} $$

斜率优化的形式,但斜率并无单调性。那个 $f(i-1)$ 可以最后来线性做,所以可以先不管。然后就是 li-chao tree 的板子。

#include<bits/stdc++.h>
std::vector<double> pri;
int n,nodes[400010];
double g[5][100010],f[100010],a[100010],b[100010],rate[100010],slp[100010];
double _f(int i,int j){return pri[i-1]*g[1][j]+g[2][j];}
void ins(int l,int r,int x,int i)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(_f(mid,i)>_f(mid,nodes[x]))    std::swap(nodes[x],i);
        if(_f(l,i)>_f(l,nodes[x]))    ins(l,mid,x<<1,i);
        else    ins(mid+1,r,x<<1|1,i);
    }
    else if(_f(l,i)>_f(l,nodes[x]))    nodes[x]=i;
}
double find(int l,int r,int x,int i)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(mid>=i)    return std::max(find(l,mid,x<<1,i),_f(i,nodes[x]));
        else    return std::max(find(mid+1,r,x<<1|1,i),_f(i,nodes[x]));
    }
    else    return _f(i,nodes[x]);
}
#define All(x) (x).begin(),(x).end()
int main()
{
    double pref=0,nowf=0;
    scanf("%d %lf",&n,&nowf);
    pref=nowf;
    for(int i=1;i<=n;++i)
    {
        scanf("%lf %lf %lf",&a[i],&b[i],&rate[i]);
        slp[i]=a[i]/b[i];
        pri.emplace_back(slp[i]);
    }
    std::sort(All(pri));
    for(int i=1;i<=n;++i)
    {
        nowf=std::max(pref,b[i]*find(1,n,1,std::lower_bound(All(pri),slp[i])-pri.begin()+1));
        g[1][i]=nowf*rate[i]/(a[i]*rate[i]+b[i]);
        g[2][i]=nowf/(a[i]*rate[i]+b[i]);
        ins(1,n,1,i);
        pref=nowf;
    }
    printf("%.3f\n",nowf);
    return 0;
}