[LibreOJ 2137][ZJOI2015]诸神眷顾的幻想乡

复习SAM了呢~

那个度数为1的点至多有20个的条件非常神奇……让我们想想怎么用。

我们发现,(钦定根后)在树上有一条路径是弯的这种情况非常不好统计,但如果是直着下来就很好说(把所有从根到一个点的路径扔到SAM里,然后是经典题)。但是,任何一条路一定会在某个度数为1的点为根的情况下是直的(可以意会一下吧(逃

然后我们从那20个点每个点当根DFS一遍,把搞出来的每一条从根开始的路径放前缀Trie里。然后对前缀Trie构一个SAM就行啦~

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#include <vector>
const int BUFSIZE = 128 * 1024 * 1024;
char buf[BUFSIZE];
void *alloc(size_t size) {
  static char *cur = buf;
  if(cur - buf + size > BUFSIZE) {
    return malloc(size);
  } else {
    char *ret = cur; cur += size;
    return ret;
  }
}

const int maxn = 100005;
std::vector<int> G[maxn];
int deg[maxn];

int ssiz;
struct TNode {
  TNode *ch[10];
};
TNode *alloc_tn() {
  auto ret = (TNode*)alloc(sizeof(TNode));
  memset(ret -> ch, 0, sizeof(ret -> ch));
  return ret;
}
TNode *step(TNode *o, int c) {
  if(!(o -> ch[c])) {
    o -> ch[c] = alloc_tn();
  }
  return (o -> ch[c]);
}
TNode *trt;
int col[maxn];
void dfs(int x, int fa, TNode *last) {
  TNode *np = step(last, col[x]);
  for(auto v : G[x]) {
    if(v != fa) {
      dfs(v, x, np);
    }
  }
}

struct Node {
  int len; Node *fa;
  Node *ch[10];
};
std::vector<Node*> pool;
Node *alloc_node(int len = 0, Node *fa = NULL) {
  Node *ret = (Node*)alloc(sizeof(Node));
  ret -> len = len; ret -> fa = fa;
  memset(ret -> ch, 0, sizeof(ret -> ch));
  pool.push_back(ret);
  return ret;
}
Node *rt;
Node *extend(int c, Node *last) {
  Node *np = alloc_node(last -> len + 1);
  Node *p = last;
  while(p != NULL && p -> ch[c] == NULL) {
    p -> ch[c] = np; p = p -> fa;
  }
  if(p == NULL) {
    np -> fa = rt;
  } else {
    Node *q = p -> ch[c];
    if(q -> len == p -> len + 1) {
      np -> fa = q;
    } else {
      Node *nq = alloc_node(p -> len + 1, q -> fa);
      memcpy(nq -> ch, q -> ch, sizeof(q -> ch));
      q -> fa = np -> fa = nq;
      while(p != NULL && p -> ch[c] == q) {
        p -> ch[c] = nq; p = p -> fa;
      }
    }
  }
  return np;
}
void dfs_2(TNode *o, Node *last) {
  for(int i = 0; i < ssiz; i ++) {
    TNode *v = o -> ch[i];
    if(!v) continue;
    Node *np = extend(i, last);
    dfs_2(v, np);
  }
}

using ll = long long;
int main() {
  int n; scanf("%d%d", &n, &ssiz);
  for(int i = 1; i <= n; i ++) {
    scanf("%d", &col[i]);
  }
  trt = alloc_tn();
  for(int i = 1; i <= n - 1; i ++) {
    int u, v; scanf("%d%d", &u, &v);
    G[u].push_back(v); G[v].push_back(u);
    deg[u] ++; deg[v] ++;
  }
  for(int i = 1; i <= n; i ++) {
    if(deg[i] == 1) {
      dfs(i, 0, trt);
    }
  }
  rt = alloc_node();
  dfs_2(trt, rt);
  ll ans = 0;
  for(auto p : pool) {
    if(p -> fa) {
      ans += p -> len - p -> fa -> len;
    }
  }
  printf("%lld\n", ans);
  return 0;
}

[LibreOJ 2034][SDOI2016]排列计数

我这种池沼只会做水题陶冶身心了……

考虑用\(f_i\)表示长为\(i\)的完全错位全排列的个数,那么有\(i\)个错位的长度为\(n\)的全排列的数量就是\(\binom{n}{i} f_i\)。从这一点出发,可以得到一个式子(枚举错位的有几个):

\[n! = \sum_{i = 0}^n \binom{n}{i} f_i\]

考虑使用二项式反演,得到:

\[
\begin{aligned}
f_n &= \sum_{i = 0}^n (-1)^{n - i}\binom{n}{i} i!\\
&= \sum_{i = 0}^n (-1)^{n - i}\frac{n!}{(n - i)!}\\
&= n!\sum_{i = 0}^n \frac{(-1)^{n - i}}{(n - i)!}\\
&= n!\sum_{i = 0}^n \frac{(-1)^i}{i!}
\end{aligned}
\]

后面的和式可以预处理,然后就做完啦~

代码:

#include <cstdio>
#include <cstring>
using ll = long long;
const ll ha = 1000000007LL;
inline ll pow_mod(const ll &a, ll b) {
  ll ans = 1LL, res = a;
  while(b) {
    if(1LL & b) ans = (ans * res) % ha;
    res = (res * res) % ha;
    b >>= 1;
  }
  return ans;
}
inline ll inv(ll x) {
  return pow_mod(x, ha - 2LL);
}

const int N = 1000000;
const int maxn = N + 5;
ll fac[maxn], f[maxn];
inline void process() {
  fac[0] = 1LL;
  for(int i = 1; i <= N; i ++) {
    fac[i] = (fac[i - 1] * (ll(i))) % ha;
  }
  for(int i = 0; i <= N; i ++) {
    f[i] = (i % 2 == 1) ? (ha - 1LL) : 1LL;
    f[i] = (f[i] * inv(fac[i])) % ha;
    if(i > 0) f[i] = (f[i - 1] + f[i]) % ha;
  }
}

int main() {
  process();
  int T; scanf("%d", &T);
  while(T --) {
    int n, m; scanf("%d%d", &n, &m);
    int p = n - m;
    ll bs = (fac[p] * f[p]) % ha;
    ll C = (fac[n] * inv((fac[p] * fac[m]) % ha)) % ha;
    printf("%lld\n", (bs * C) % ha);
  }
  return 0;
}

[LibreOJ 6024]XLkxc

拉格朗日插值的模板题qwq

考虑\(f(n) = \sum_{i = 1}^n i^k\),这显然是一个\(k + 1\)次多项式(通过差分之类的手段可以证明),然后我们发现\(g(n) = \sum_{i = 1}^n f(n)\)可以用类似手段证明是\(k + 2\)次多项式。类似的,我们会发现,答案是一个\(k + 3\)次多项式。

分别对\(g\)以及答案插值即可。

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#include <climits>
typedef long long ll;
const ll ha = 1234567891LL;
int k;
ll a, n, d;
ll pf[205], pg[205], fac[205];
ll pow_mod(const ll &a, ll b) {
  if(!b) return 1LL;
  ll ans = pow_mod(a, b >> 1);
  ans = (ans * ans) % ha;
  if(b & 1LL) ans = (ans * a) % ha;
  return ans;
}
ll inv(ll v) {
  return pow_mod(v, ha - 2LL);
}

void process() {
  fac[0] = 1LL;
  for(int i = 1; i <= k + 5; i ++) {
    fac[i] = (fac[i - 1] * inv(i)) % ha;
#ifdef LOCAL
    printf("fac[%d] : %lld\n", i, fac[i]);
#endif
  }
  for(int i = 1; i <= k + 3; i ++) {
    pf[i] = (pf[i - 1] + pow_mod(i, k)) % ha;
#ifdef LOCAL
    printf("pf[%d] : %lld\n", i, pf[i]);
#endif
  }
  for(int i = 1; i <= k + 3; i ++) {
    pg[i] = (pg[i - 1] + pf[i]) % ha;
#ifdef LOCAL
    printf("pg[%d] : %lld\n", i, pg[i]);
#endif
  }
#ifdef LOCAL
  puts("Processing finished!");
#endif
}

ll g(ll x) {
  static ll sim_f[205], sim_g[205];
  sim_f[0] = 1LL;
  for(int i = 1; i <= k + 3; i ++) {
    sim_f[i] = (x - (ll(i)) + ha) % ha;
    sim_f[i] = (sim_f[i] * sim_f[i - 1]) % ha;
  }
  sim_g[k + 4] = 1LL;
  for(int i = k + 3; i >= 1; i --) {
    sim_g[i] = (x - (ll(i)) + ha) % ha;
    sim_g[i] = (sim_g[i] * sim_g[i + 1]) % ha;
  }
  ll ret = 0;
  for(int i = 1; i <= k + 3; i ++) {
    ll ans = (((pg[i] * sim_f[i - 1]) % ha) * sim_g[i + 1]) % ha;
    ans = (ans * fac[i - 1]) % ha;
    ans = (ans * fac[k + 3 - i]) % ha;
    if(1 & (k + 3 - i)) {
      ret = (ret - ans + ha) % ha;
    } else {
      ret = (ret + ans) % ha;
    }
  }
#ifdef LOCAL
  printf("g(%lld) : %lld\n", x, ret);
#endif
  return ret;
}
ll h(ll x) {
  static ll ph[205];
  ph[0] = g(a);
  for(int i = 1; i <= k + 4; i ++) {
    ph[i] = (ph[i - 1] + g(a + (ll(i)) * d)) % ha;
  }
  static ll sim_f[205], sim_g[205];
  sim_f[0] = 1LL;
  for(int i = 1; i <= k + 4; i ++) {
    sim_f[i] = (x - (ll(i)) + ha) % ha;
    sim_f[i] = (sim_f[i] * sim_f[i - 1]) % ha;
  }
  sim_g[k + 5] = 1LL;
  for(int i = k + 4; i >= 1; i --) {
    sim_g[i] = (x - (ll(i)) + ha) % ha;
    sim_g[i] = (sim_g[i] * sim_g[i + 1]) % ha;
  }
  ll ret = 0;
  for(int i = 1; i <= k + 4; i ++) {
    ll ans = (((ph[i] * sim_f[i - 1]) % ha) * sim_g[i + 1]) % ha;
    ans = (ans * fac[i - 1]) % ha;
    ans = (ans * fac[k + 4 - i]) % ha;
    if(1 & (k + 4 - i)) {
      ret = (ret - ans + ha) % ha;
    } else {
      ret = (ret + ans) % ha;
    }
  }
  return ret;
}

int main() {
  int T; scanf("%d", &T);
  while(T --) {
    scanf("%d%lld%lld%lld", &k, &a, &n, &d);
    process();
    printf("%lld\n", h(n));
  }
  return 0;
}

[LibreOJ 2249][NOI2014]购票

在紧张刺激的等待之后终于肝掉了这道题……

本题的DP方程长成这样(其中\(a\)指\(v\)的某个满足距离限制的祖先,\(d_v\)指\(v\)到根的路径长):

\[f_v = min(f_a + p_v(d_v - d_a) + q_v)\]

化简之后发现:

\[f_v = q_v + p_v d_v + min(f_a - p_v d_a)\]

利用\(min\)中那一块很容易发现是截距式……但是问题在于,我们的转移来源是树上的连续一段祖先,怎样维护他们的凸包?

答案很狂暴啊……用树链剖分套上向量集那题的线段树套凸包,然后完了……

(注意一点细节:本题因为数据范围过大,故可能存在两个向量叉乘爆long long,所以在求凸包时如果直接用叉积判断是否需要删点会炸掉,建议用斜率判断)

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <queue>
#include <deque>
#include <cmath>
#include <set>
#include <climits>
using ll = long long;
using T = ll;
using R = long double;
const R eps = 1e-8;
int sign(R x) {
  if(fabsl(x) < eps) {
    return 0;
  } else {
    if(x > (R(0.00))) {
      return 1;
    } else {
      return -1;
    }
  }
}

struct Point {
  T x, y;
  Point(T qx = 0LL, T qy = 0LL) {
    x = qx; y = qy;
  }
};
using Vector = Point;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(const Vector &a, T lam) {
  return Vector(a.x * lam, a.y * lam);
}
Vector operator *(T lam, const Vector &a) {
  return Vector(a.x * lam, a.y * lam);
}
inline T dot(const Vector &a, const Vector &b) {
  return (a.x * b.x + a.y * b.y);
}
inline T times(const Vector &a, const Vector &b) {
  return (a.x * b.y - a.y * b.x);
}
inline bool cmp(const Point &a, const Point &b) {
  if(a.x == b.x) {
    return a.y < b.y;
  } else {
    return a.x < b.x;
  }
}
inline R slope(const Vector &a) {
  R dx = a.x, dy = a.y;
  return (dy / dx);
}

inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) {
  std::sort(P + 1, P + 1 + L, cmp);
  for(int i = 1; i <= L; i ++) {
    if(i != 1 && (P[i].x == P[i - 1].x)) continue;
    while(bot.size() > 1 && sign(slope(P[i] - bot.back()) - slope(bot.back() - bot[bot.size() - 2])) <= 0) {
      bot.pop_back();
    }
    bot.push_back(P[i]);
  }
  for(int i = L; i >= 1; i --) {
    if(i != L && (P[i].x == P[i + 1].x)) continue;
    while(top.size() > 1 && sign(slope(P[i] - top.back()) - slope(top.back() - top[top.size() - 2])) <= 0) {
      top.pop_back();
    }
    top.push_back(P[i]);
  }
  std::reverse(top.begin(), top.end());
}

const int maxn = 200005;
const int maxno = maxn << 2;
const int N = 200000;
bool zen[maxno];
std::vector<Point> bot[maxno], top[maxno];
Point P[maxn];
inline void maintain(int o, int L, int R) {
  static Point tmp[maxn];
  const int lc = o << 1, rc = o << 1 | 1;
  const bool used = zen[o];
  zen[o] = (zen[lc] && zen[rc]);
  if(zen[o] != used) {
    std::copy(P + L, P + R + 1, tmp + 1);
    int len = R - L + 1;
    andrew(tmp, len, bot[o], top[o]);
  }
}
void modify(int o, int L, int R, const int &p, const Point &v) {
  if(L == R) {
    zen[o] = true;
    P[L] = v;
    bot[o].push_back(v); top[o].push_back(v);
  } else {
    const int M = (L + R) / 2;
    if(p <= M) {
      modify(o << 1, L, M, p, v);
    } else {
      modify(o << 1 | 1, M + 1, R, p, v);
    }
    maintain(o, L, R);
  }
}
inline T calc_ans(T k, const Point &v) {
  return v.y - k * v.x;
}
inline T search(const std::vector<Point> &vec, const T &k) {
  int l = 0, r = vec.size() - 1;
  while(r - l > 2) {
    int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3;
    if((calc_ans(k, vec[lm]) > calc_ans(k, vec[rm]))) {
      l = lm;
    } else {
      r = rm;
    }
  }
  T ans = LLONG_MAX;
  for(int i = l; i <= r; i ++) {
    ans = std::min(ans, calc_ans(k, vec[i]));
  }
  return ans;
}
T query(int o, int L, int R, const int &ql, const int &qr, const T &k) {
  if(ql <= L && R <= qr) {
    return search(bot[o], k);
  } else {
    int M = (L + R) / 2;
    T ans = LLONG_MAX;
    if(ql <= M) {
      ans = std::min(ans, query(o << 1, L, M, ql, qr, k));
    }
    if(qr > M) {
      ans = std::min(ans, query(o << 1 | 1, M + 1, R, ql, qr, k));
    }
    return ans;
  }
}

int first[maxn];
int next[maxn << 1], to[maxn << 1];
ll dist[maxn << 1];
void add_edge(int u, int v, ll d) {
  static int cnt = 0;
  cnt ++;
  next[cnt] = first[u];
  first[u] = cnt;
  to[cnt] = v;
  dist[cnt] = d;
}

int fa[maxn], dep[maxn], hson[maxn];
ll d[maxn];
int siz[maxn];
int bs[maxn][18];
void dfs_1(int x, int f = -1, int depth = 1) {
  fa[x] = bs[x][0] = f; dep[x] = depth;
  siz[x] = 1;
  int max_siz = 0;
  for(int i = first[x]; i; i = next[i]) {
    int v = to[i];
    if(v != f) {
      d[v] = d[x] + dist[i];
      dfs_1(v, x, depth + 1);
      siz[x] += siz[v];
      if(siz[v] > max_siz) {
        hson[x] = v; max_siz = siz[v];
      }
    }
  }
}
int dfn[maxn], tid[maxn], up[maxn];
void dfs_2(int x, int a = 1, int f = 0) {
  static int cnt = 0;
  dfn[x] = ++ cnt; tid[cnt] = x;
  up[x] = a;
  if(hson[x]) {
    dfs_2(hson[x], a, x);
  } else {
    return;
  }
  for(int i = first[x]; i; i = next[i]) {
    int v = to[i];
    if(v != f && v != hson[x]) {
      dfs_2(v, v, x);
    }
  }
}
int k_anc(int x, ll k) {
  int yx = x;
  for(int j = 17; j >= 0; j --) {
    int a = bs[x][j];
    if(a != -1 && d[yx] - d[a] <= k) {
      x = a;
    }
  }
#ifdef LOCAL
  printf("%d's %lld-th anc : %d\n", yx, k, x);
#endif
  return x;
}
int n;
ll get_up(int x, int anc, ll k) {
  ll ans = LLONG_MAX;
  while(up[x] != up[anc]) {
    ans = std::min(ans, query(1, 1, n, dfn[up[x]], dfn[x], k));
    x = fa[up[x]];
  }
  return std::min(ans, query(1, 1, n, dfn[anc], dfn[x], k));
}

ll p[maxn], q[maxn], l[maxn];
ll f[maxn];
void dp(int x) {
#ifdef LOCAL
  printf("processing %d...\n", x);
  printf("d : %lld\n", d[x]);
#endif
  if(x != 1) {
#ifdef LOCAL
    printf("b : %lld\n", get_up(fa[x], k_anc(x, l[x]), p[x]));
#endif
    f[x] = get_up(fa[x], k_anc(x, l[x]), p[x]) + d[x] * p[x] + q[x];
  } else {
    f[x] = 0;
  }
#ifdef LOCAL
  printf("ans : %lld\n", f[x]);
#endif
  modify(1, 1, n, dfn[x], Point(d[x], f[x]));
  for(int i = first[x]; i; i = next[i]) {
    int v = to[i];
    dp(v);
  }
}

int main() {
  int t; scanf("%d%d", &n, &t);
  for(int i = 2; i <= n; i ++) {
    int father; T s;
    scanf("%d%lld%lld%lld%lld", &father, &s, &p[i], &q[i], &l[i]);
    add_edge(father, i, s);
  }
  memset(bs, -1, sizeof(bs));
  dfs_1(1); dfs_2(1);
  for(int j = 1; (1 << j) < n; j ++) {
    for(int i = 1; i <= n; i ++) {
      int a = bs[i][j - 1];
      if(a != -1) {
        bs[i][j] = bs[a][j - 1];
      }
    }
  }
  dp(1);
  for(int i = 2; i <= n; i ++) {
    printf("%lld\n", f[i]);
  }
  return 0;
}

[LibreOJ 2353][NOI2007]货币兑换

emmm做了一下这道神题……(我可能是少有的用动态凸包苟的?

首先DP方程长这样:

\[f_i = max(f_{i - 1}, f_j\cdot\frac{A_iR_j+B_i}{A_jR_j+B_j})\]

然后这个方程炒鸡复杂……首先\(f_{i - 1}\)不要管了,然后设\(a_i = \frac{f_i}{A_iR_i + B_i}\)。在xjb推了一番之后我们终于得到了截距式……

\[-a_j R_j \frac{A_i}{B_i} + \frac{f_i}{B_i} = a_j\]

但是这玩意太毒瘤了……斜率不可能单调的,这还好,在凸壳上二分/三分一下即可。但问题在于,横坐标也不单调……

这个时候就需要动态维护凸包了(其实是我不会CDQ),我直接把我向量集那题的二进制分组线段树搬了过来……(逃

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <cmath>
#include <climits>
#include <deque>
#include <cassert>
using R = double;
const R eps = 1e-8;
int sign(R x) {
  if(fabs(x) < eps) {
    return 0;
  } else {
    if(x > 0.00) {
      return 1;
    } else {
      return -1;
    }
  }
}

struct Point {
  R x, y;
  Point(R qx = 0, R qy = 0) {
    x = qx; y = qy;
  }
};
using Vector = Point;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(b.x - a.x, b.y - a.y);
}
Vector operator *(const Vector &a, R lam) {
  return Vector(a.x * lam, a.y * lam);
}
Vector operator *(R lam, const Vector &a) {
  return Vector(a.x * lam, a.y * lam);
}
inline R dot(const Vector &a, const Vector &b) {
  return (a.x * b.x + a.y * b.y);
}
inline R times(const Vector &a, const Vector &b) {
  return (a.x * b.y - a.y * b.x);
}
inline bool cmp(const Point &a, const Point &b) {
  if(sign(a.x - b.x) == 0) {
    return a.y < b.y;
  } else {
    return a.x < b.x;
  }
}
inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) {
  std::sort(P + 1, P + 1 + L, cmp);
  for(int i = 1; i <= L; i ++) {
    if(i != 1 && sign(P[i].x - P[i - 1].x) == 0) continue;
    while(bot.size() > 1 && sign(times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0) {
      bot.pop_back();
    }
    bot.push_back(P[i]);
  }
  for(int i = L; i >= 1; i --) {
    if(i != L && sign(P[i].x - P[i + 1].x) == 0) continue;
    while(top.size() > 1 && sign(times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0) {
      top.pop_back();
    }
    top.push_back(P[i]);
  }
  std::reverse(top.begin(), top.end());
}

const int maxn = 1000005;
const int N = 1000000;
const int maxno = maxn << 2;
bool zen[maxno];
std::vector<Point> bot[maxno], top[maxno];
Point P[maxn];
inline void maintain(int o, int L, int R) {
  static Point tmp[maxn];
  const int lc = o << 1, rc = o << 1 | 1;
  const bool used = zen[o];
  zen[o] = (zen[lc] && zen[rc]);
  if(zen[o] != used) {
    std::copy(P + L, P + R + 1, tmp + 1);
    int len = R - L + 1;
    andrew(tmp, len, bot[o], top[o]);
  }
}
void modify(int o, int L, int R, const int &p, const Point &v) {
  if(L == R) {
    zen[o] = true;
    P[L] = v;
    bot[o].push_back(v); top[o].push_back(v);
  } else {
    const int M = (L + R) / 2;
    if(p <= M) {
      modify(o << 1, L, M, p, v);
    } else {
      modify(o << 1 | 1, M + 1, R, p, v);
    }
    maintain(o, L, R);
  }
}
inline R calc_ans(R k, const Point &v) {
  return v.y - k * v.x;
}
inline R search(const std::vector<Point> &vec, const R &k) {
  int l = 0, r = vec.size() - 1;
  while(r - l > 2) {
    int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3;
    if(sign(calc_ans(k, vec[lm]) - calc_ans(k, vec[rm])) == 1) {
      r = rm;
    } else {
      l = lm;
    }
  }
  R ans = INT_MIN;
  for(int i = l; i <= r; i ++) {
    ans = std::max(ans, calc_ans(k, vec[i]));
  }
  return ans;
}
R query(int o, int L, int R, const int &ql, const int &qr, const double &k) {
  if(ql <= L && R <= qr) {
    return search(top[o], k);
  } else {
    int M = (L + R) / 2;
    double ans = INT_MIN;
    if(ql <= M) {
      ans = std::max(ans, query(o << 1, L, M, ql, qr, k));
    }
    if(qr > M) {
      ans = std::max(ans, query(o << 1 | 1, M + 1, R, ql, qr, k));
    }
    return ans;
  }
}

int n, s;
R A[maxn], B[maxn], Rate[maxn];
R f[maxn];
R dp() {
  static double a[maxn];
  f[0] = s; f[1] = s; a[1] = f[1] / (A[1] * Rate[1] + B[1]);
  modify(1, 1, n, 1, Point(a[1] * Rate[1], a[1]));
  for(int i = 2; i <= n; i ++) {
    f[i] = query(1, 1, n, 1, i - 1, -A[i] / B[i]) * B[i];
    f[i] = std::max(f[i], f[i - 1]);
    a[i] = f[i] / (A[i] * Rate[i] + B[i]);
    if(i < n) modify(1, 1, n, i, Point(a[i] * Rate[i], a[i]));
  }
  return f[n];
}

int main() {
  scanf("%d%d", &n, &s);
  for(int i = 1; i <= n; i ++) {
    scanf("%lf%lf%lf", &A[i], &B[i], &Rate[i]);
  }
  printf("%.3lf\n", dp());
  return 0;
}

[LibreOJ 2035][SDOI2016]征途

又做了一道适合我这种沙茶做的题qwq

考虑方差,它满足这个式子:

\[Var(X) = E[X^2] - (E[X])^2\]

对于这个题展开,发现后半部分是一个常量(\((\frac{s_n}{m})^2\))。最小化的就是前半部分,前半部分相当于要求你求一个序列划分,使得各部分平方和的平均值最小。这个值乘上\(m^2\)之后就变成了各部分平方和乘上\(m\)。

然后就很简单了……DP方程化简之后是这样的:

\[f_{t, i} = ms_i^2 + max(f_{t - 1, j} + ms_j^2 - 2ms_i s_j)\]

求截距式形式,得:

\[2ms_i s_j + d_i = f_j + ms_j^2\]

然后用类似于上上题的方法搞就行。还有我想想啥时候补一下斜率优化的tutorial……

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <deque>
#include <cmath>
#include <climits>
typedef long long ll;
typedef ll T;
struct Point {
  T x, y;
  Point(T qx = 0LL, T qy = 0LL) {
    x = qx; y = qy;
  }
};
typedef Point Vector;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(const Vector &a, T lam) {
  return Vector(a.x * lam, a.y * lam);
}
Vector operator *(T lam, const Vector &a) {
  return Vector(a.x * lam, a.y * lam);
}
inline T dot(const Vector &a, const Vector &b) {
  return (a.x * b.x + a.y * b.y);
}
inline T times(const Vector &a, const Vector &b) {
  return (a.x * b.y - a.y * b.x);
}
 
const int maxn = 3005;
T f[maxn][maxn];
T S[maxn];
int n; T m;
void dp() {
  for(int t = 1; t <= m; t ++) {
    std::deque<Point> Q;
    Q.push_back(Point(0LL, 0LL));
    for(int i = 1; i <= n; i ++) {
      ll k = 2 * m * S[i];
      Vector st(1LL, k);
      while(Q.size() > 1 && times(Q[1] - Q[0], st) > 0LL) {
        Q.pop_front();
      }
      f[t][i] = m * S[i] * S[i];
      f[t][i] += Q.front().y - Q.front().x * k;
#ifdef LOCAL
      printf("f[%d][%d] : %lld\n", t, i, f[t][i]);
#endif
      if(t == 1) continue;
      Vector ins(S[i], f[t - 1][i] + m * S[i] * S[i]);
      while(Q.size() > 1 && times(ins - Q.back(), Q.back() - Q[Q.size() - 2]) > 0LL) {
        Q.pop_back();
      }
      Q.push_back(ins);
    }
  }
}

int main() {
  scanf("%d%lld", &n, &m);
  for(int i = 1; i <= n; i ++) {
    scanf("%lld", &S[i]);
  }
  for(int i = 1; i <= n; i ++) {
    S[i] += S[i - 1];
  }
  dp();
  printf("%lld\n", f[m][n] - S[n] * S[n]);
  return 0;
}

[LibreOJ 2197][SDOI2014]向量集

xjb写了写……我评测时候心脏跳得贼快(逃

考虑如果知道了那一段区间的凸包那么怎么做。首先如果向量是往上指的话,一定在上凸壳上找点比较好,反之则在下凸壳上找点比较好(放到坐标系里脑补一下?)。然后我们观察一点,在上凸壳上的最优解往两边的点会越来越劣,所以这玩意是个上凸函数,可以三分答案(我才学的整数三分啊)。

但区间凸包求起来复杂度很爆炸啊……考虑用线段树搞?观察到一点,我们区间查询所使用的线段树节点一定是只包含了已经加进来的点。所以说,一个线段树节点的凸包需要被求的情况只有一种,那就是这个节点完全已加入点被覆盖了。那每次修改之后看是否一个节点完全被已加入点覆盖,如果被完全覆盖的话才去求它的凸包。

这样一来,线段树上每个节点之多会被求一次凸包。线段树有\(\log n\)层,每一层所有节点的大小加起来是\(n\),所以求凸包耗费的总复杂度是\(n\log^2 n\)级别的。

其实这就是用线段树模拟二进制分组?

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <climits>
#include <cassert>
using ll = long long;
using T = ll;
struct Point {
  T x, y;
  Point(T qx = 0LL, T qy = 0LL) {
    x = qx; y = qy;
  }
};
using Vector = Point;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(const Vector &a, T lam) {
  return Vector(a.x * lam, a.y * lam);
}
Vector operator *(T lam, const Vector &a) {
  return Vector(a.x * lam, a.y * lam);
}
inline T dot(const Vector &a, const Vector &b) {
  return (a.x * b.x + a.y * b.y);
}
inline T times(const Vector &a, const Vector &b) {
  return (a.x * b.y - a.y * b.x);
}
inline bool cmp(const Point &a, const Point &b) {
  if((a.x - b.x) == 0LL) {
    return a.y < b.y;
  } else {
    return a.x < b.x;
  }
}
inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) {
  std::sort(P + 1, P + 1 + L, cmp);
  for(int i = 1; i <= L; i ++) {
    if(i != 1 && (P[i].x - P[i - 1].x) == 0LL) continue;
    while(bot.size() > 1 && (times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0LL) {
      bot.pop_back();
    }
    bot.push_back(P[i]);
  }
  for(int i = L; i >= 1; i --) {
    if(i != L && (P[i].x - P[i + 1].x) == 0LL) continue;
    while(top.size() > 1 && (times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0LL) {
      top.pop_back();
    }
    top.push_back(P[i]);
  }
  std::reverse(top.begin(), top.end());
}

const int maxn = 400005;
const int maxno = maxn << 2;
const int N = 400000;
bool zen[maxno];
std::vector<Point> bot[maxno], top[maxno];
Point P[maxn];
inline void maintain(int o, int L, int R) {
  static Point tmp[maxn];
  const int lc = o << 1, rc = o << 1 | 1;
  const bool used = zen[o];
  zen[o] = (zen[lc] && zen[rc]);
  if(zen[o] != used) {
    std::copy(P + L, P + R + 1, tmp + 1);
    int len = R - L + 1;
    andrew(tmp, len, bot[o], top[o]);
  }
}
void modify(int o, int L, int R, const int &p, const Point &v) {
  if(L == R) {
    zen[o] = true;
    P[L] = v;
    bot[o].push_back(v); top[o].push_back(v);
  } else {
    const int M = (L + R) / 2;
    if(p <= M) {
      modify(o << 1, L, M, p, v);
    } else {
      modify(o << 1 | 1, M + 1, R, p, v);
    }
    maintain(o, L, R);
  }
}
inline T search(const std::vector<Point> &vec, const Point &p) {
  int l = 0, r = vec.size() - 1;
  while(r - l > 2) {
    int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3;
    if(dot(p, vec[lm]) > dot(p, vec[rm])) {
      r = rm;
    } else {
      l = lm;
    }
  }
  T ans = LLONG_MIN;
  for(int i = l; i <= r; i ++) {
    ans = std::max(ans, dot(p, vec[i]));
  }
  return ans;
}
T query(int o, int L, int R, const int &ql, const int &qr, const Point &p) {
  if(ql <= L && R <= qr) {
    if(p.y > 0LL) {
      return search(top[o], p);
    } else {
      return search(bot[o], p);
    }
  } else {
    int M = (L + R) / 2;
    T ans = LLONG_MIN;
    if(ql <= M) {
      ans = std::max(ans, query(o << 1, L, M, ql, qr, p));
    }
    if(qr > M) {
      ans = std::max(ans, query(o << 1 | 1, M + 1, R, ql, qr, p));
    }
    return ans;
  }
}

inline int decode(int x, long long lastans) {
  return x ^ (lastans & 0x7fffffff);
}
int main() {
  int q; char buf[4]; scanf("%d%s", &q, buf);
  bool typ_E = (buf[0] == 'E' && buf[1] == char(0));
  T las = 0LL;
  int tot = 0;
  while(q --) {
    char op[4]; scanf("%s", op);
    if(op[0] == 'A') {
      T x, y; scanf("%lld%lld", &x, &y);
      if(!typ_E) {
        x = decode(x, las); y = decode(y, las);
      }
      tot ++;
      modify(1, 1, N, tot, Point(x, y));
    } else {
      T x, y, l, r; scanf("%lld%lld%lld%lld", &x, &y, &l, &r);
      if(!typ_E) {
        x = decode(x, las); y = decode(y, las);
        l = decode(l, las); r = decode(r, las);
      }
      las = query(1, 1, N, l, r, Point(x, y));
      printf("%lld\n", las);
    }
  }
  return 0;
}

[LibreOJ 2102][TJOI2015]弦论

xjbYY了一下竟然就艹过去了……

如果说是我们知道了每个点出发能得到的串有多少个,我们就能用类似树上求\(k\)大的求法来搞了。问题来了,怎么知道这个?

我先说一下不重复的情况吧,首先到达这个点就有一种串了,然后我们加上他的出边(注意是转移边!)指向的点的值就行了。重复的情况我们考虑一下\(|Right(x)|\)就能搞出来了

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <utility>
#include <queue>
#include <algorithm>
#include <set>
const int maxn = 500005;
const int maxno = maxn * 4;
const int bufsiz = 50 * 1024 * 1024;
typedef long long ll;

int fa[maxno], ch[maxno][26];
int len[maxno];
ll ans[maxno][2], val[maxno];
int rt, last;
int cur;
inline void init_sam() {
  rt = last = 1;
  cur = 1;
  ans[1][0] = ans[1][1] = -1LL;
}
inline int alloc_node(int l = 0, int f = 0) {
  int ret = ++ cur;
  len[ret] = l; fa[ret] = f;
  ans[ret][0] = ans[ret][1] = -1LL;
  return ret;
}
inline int idx(char c) {
  return c - 'a';
}
inline void extend(char cx) {
  int c = idx(cx);
  int np = alloc_node(len[last] + 1);
  val[np] = 1LL;
  int p = last;
  while(p && !(ch[p][c])) ch[p][c] = np, p = fa[p];
  if(!p) {
    fa[np] = rt;
  } else {
    int q = ch[p][c];
    if(len[q] == len[p] + 1) {
      fa[np] = q;
    } else {
      int nq = alloc_node(len[p] + 1, fa[q]);
      memcpy(ch[nq], ch[q], sizeof(ch[q]));
      fa[q] = fa[np] = nq;
      while(p && ch[p][c] == q) ch[p][c] = nq, p = fa[p];
    }
  }
  last = np;
}
int lc[maxno], rb[maxno];
inline void add_edge(int c, int f) {
  rb[c] = lc[f];
  lc[f] = c;
}
void dfs_1(int x) {
  for(int i = lc[x]; i; i = rb[i]) {
    dfs_1(i);
    val[x] += val[i];
  }
}
void dfs_2(int x) {
  if(ans[x][0] != -1LL) return;
  ans[x][0] = 1LL; ans[x][1] = val[x];
  for(int c = 0; c < 26; c ++) {
    int v = ch[x][c];
    if(v) {
      dfs_2(v);
      ans[x][0] += ans[v][0];
      ans[x][1] += ans[v][1];
    }
  }
}
inline void process() {
  for(int i = 2; i <= cur; i ++) {
    add_edge(i, fa[i]);
  }
  dfs_1(1); dfs_2(1);
#ifdef LOCAL
  for(int i = 1; i <= cur; i ++) {
    printf("ans[%d] : (%lld, %lld)\n", i, ans[i][0], ans[i][1]);
  }
#endif
}
char ret[maxno];
void search(ll k, int typ) {
  static ll val_0[maxno];
  for(int i = 1; i <= cur; i ++) {
    val_0[i] = 1;
  }
  ll *self[2] = {val_0, val};
  k += self[typ][1];
  if(k > ans[1][typ]) {
    ret[0] = '-', ret[1] = '1';
    return;
  }
  int cnt = 0;
  int u = 1;
  while(k > self[typ][u]) {
    int used = 0;
    k -= self[typ][u];
    for(int c = 0; c < 26; c ++) {
      int v = ch[u][c];
      if(!v) continue;
      used ++;
      if(k > ans[v][typ]) {
        k -= ans[v][typ];
      } else {
        ret[cnt ++] = c + 'a';
#ifdef LOCAL
        printf("Towardsing %d with %c\n", v, c + 'a');
        printf("k : %lld\n", k);
#endif
        u = v; break;
      }
    }
    // if(used == 0) break;
  }
}

int main() {
  static char S[maxn];
  scanf("%s", S); int n = strlen(S);
  int typ; ll k; scanf("%d%lld", &typ, &k);
  init_sam();
  for(int i = 0; i < n; i ++) {
    extend(S[i]);
  }
  process();
  search(k, typ);
  puts(ret);
  return 0;
}

[LibreOJ 2033][SDOI2016]生成魔咒

辣鸡爆炸OJ又被卡了说好的修bug呢说好的新OJ呢哼我去loj交了

本题的正解是SA,但很显然可以用SAM搞过去(逃

首先字符集过大?我们可以考虑开map解决转移的问题。

然后思考一个点加入后对答案会有什么影响。一个点的对答案的贡献,就是他自己的最大表示范围减去他父亲的。因为更小的长度在之前都已经被表示过了。即使之后这个点被拆成了两个,那这两个点对答案的总贡献是不会变的。所以每次加完点,把贡献加到答案里即可。

代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <map>
typedef long long ll;
#define REP(i, l, r) for(int i = (l); i <= (r); i ++)
struct Node {
  Node *fa;
  std::map<int, Node*> *ch;
  int len;
  Node() {
    fa = NULL;
    ch = new std::map<int, Node*>();
  }
  Node(int l) {
    fa = NULL;
    ch = new std::map<int, Node*>();
    len = l;
  }
  ~Node() {
    delete ch;
  }
};
Node *rt, *last;
void init() {
  rt = new Node(0);
  // rt -> tl = 1;
  last = rt;
}
ll ans = 0;
void insert(int c) {
  static int clk = 1;
  Node *np = new Node();
  np -> len = last -> len + 1;
  Node *p = last;
  while(p != NULL && p -> ch -> count(c) == 0) {
    p -> ch -> insert(std::make_pair(c, np));
    p = p -> fa;
  }
  if(p == NULL) {
    np -> fa = rt;
  } else {
    Node *q = (*(p -> ch -> find(c))).second;
    if(q -> len == p -> len + 1) {
      np -> fa = q;
    } else {
      Node *nq = new Node(p -> len + 1);
      nq -> fa = q -> fa;
      nq -> ch -> insert(q -> ch -> begin(), q -> ch -> end());
      // nq -> ch -> insert(std::make_pair(c, np));
      np -> fa = q -> fa = nq;
      while(p != NULL && ((*(p -> ch -> find(c))).second) == q) {
        p -> ch -> erase(p -> ch -> find(c));
        p -> ch -> insert(std::make_pair(c, nq));
        p = p -> fa;
      }
    }
  }
  last = np;
  ans += np -> len - np -> fa -> len;
}
 
int main() {
  int n; scanf("%d", &n);
  init();
  REP(i, 1, n) {
    int c; scanf("%d", &c);
    insert(c);
    printf("%lld\n", ans);
  }
  return 0;
}

[LibreOJ 2174][FJOI2016]神秘数 & [CC]FRBSUM

震惊!省选惊现CodeChef原题……竟然是为了……出原题难道不是普遍现象吗

这个题的思想肥肠喵啊(我膜了很长时间题解才看懂)……我争取给各位读者讲懂。

首先对于最后的答案\(x + 1\),一定说明\([1, x]\)都会被凑出来。那么我们可以考虑去维护这个前缀区间。

考虑把数从小到大加入。假设当前我们的可凑出来的前缀区间是\([1, r]\),那么加入一个数\(x\),如果说\(x > r + 1\),那么把之前所有可能的子集和都加上这个\(x\),一定凑不出来\(r + 1\)。并且这之后加入的数会越来越大,那个\(r\)不会再变大了,所以那个\(r\)就是答案了。

如果说\(x\leq r + 1\)呢?那么把前缀区间的每个数加上\(x\)都是可凑成数。所以前缀区间会变成\([1, r + x]\)。

然后观察出来这种性质之后,我们发现我们要考虑区间中不同的数,可以考虑主席树。我们建立一排主席树,对于查询\([L, R]\),不妨假设当前的前缀区间是\([1, r]\),然后考虑将其扩大。首先再加上大于\(r + 1\)的数是对扩大\(r\)没有意义的,所以我们就考虑在\([L, R]\)中找到所有权值处于\([1, r + 1]\)的数字的和(主席树可以派上用场),这样就是一个新的答案了。如果发现转移过去之后答案没有变大,那么以后也不会变大了,跳出来即可。

考虑分析一波复杂度。对于每一个\(r\),转移到更大的\(r\)会让他至少加上\(r + 1\),所以转移的次数是\(\log_2 s\)(这里假设\(s\)是所有数的和),然后每次一次转移的复杂度是\(\log_2 n\),所以单次查询复杂度可以大致认为是\(\log^2 n\)。

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
typedef long long ll;
const int maxn = 100005;
const int maxsiz = maxn * 40;
ll sumv[maxsiz]; int tot = 0;
int lc[maxsiz], rc[maxsiz];
int build_tree(int L, int R) {
  int ret = ++ tot;
  if(L < R) {
    int M = (L + R) / 2;
    lc[ret] = build_tree(L, M);
    rc[ret] = build_tree(M + 1, R);
  }
  return ret;
}
int update(int o, int L, int R, int p, int v) {
  int ret = ++ tot;
  sumv[ret] = sumv[o] + (ll(v));
  lc[ret] = lc[o], rc[ret] = rc[o];
  if(L < R) {
    int M = (L + R) / 2;
    if(p <= M) {
      lc[ret] = update(lc[ret], L, M, p, v);
    } else {
      rc[ret] = update(rc[ret], M + 1, R, p, v);
    }
  }
  return ret;
}
ll query(int o, int L, int R, int ql, int qr) {
  if(ql <= L && R <= qr) {
    return sumv[o];
  } else {
    int M = (L + R) / 2;
    ll ans = 0;
    if(ql <= M) ans += query(lc[o], L, M, ql, qr);
    if(qr > M) ans += query(rc[o], M + 1, R, ql, qr);
    return ans;
  }
}

int n;
ll A[maxn], A2[maxn];
int cnt;
void discretiz() {
  std::sort(A2 + 1, A2 + n + 1);
  cnt = std::unique(A2 + 1, A2 + 1 + n) - A2 - 1;
}
int get_p(ll v) {
  int ret = (std::lower_bound(A2 + 1, A2 + 1 + cnt, v) - A2);
  if(A2[ret] > v) ret --;
  return ret;
}

int T[maxn];
void init_tree() {
  T[0] = build_tree(1, cnt);
  for(int i = 1; i <= n; i ++) {
    T[i] = update(T[i - 1], 1, cnt, get_p(A[i]), A[i]);
  }
}
const ll INF = 1000000000LL;
ll calc_sum(int l, int r, int typ) {
  if(typ == 0) return 0LL;
  return query(T[r], 1, cnt, 1, typ) - query(T[l - 1], 1, cnt, 1, typ);
}
ll calc(int l, int r) {
  ll maxv = 0LL, R = 1LL;
  maxv = calc_sum(l, r, get_p(R));
  while(maxv >= R && R < INF) {
    R = std::min(maxv + 1LL, INF);
    maxv = calc_sum(l, r, get_p(R));
  }
  return maxv + 1LL;
}
int main() {
  scanf("%d", &n);
  for(int i = 1; i <= n; i ++) {
    scanf("%lld", &A[i]); A2[i] = A[i];
  }
  discretiz(); init_tree();
  int q; scanf("%d", &q);
  while(q --) {
    int l, r; scanf("%d%d", &l, &r);
    printf("%lld\n", calc(l, r));
  }
  return 0;
}