Blogs2

始终做完全公开的博客

单调栈,前缀和,RMQ。

Description

给定长度为\(n\)的序列:\(a_1,a_2,\ldots ,a_n\),记为\(a[1:n]\)。类似地,\(a[l:r](1\le l \le r \le N)\)是指序列:\(a_l,a_{l+1},\ldots , a_{r-1},a_r\)。若\(1 \le l \le s \le t \le r \le n\),则称\(a[s:t]\)\(a[l:r]\)的子序列。

现在有\(q\)个询问,每个询问给定两个数\(l\)\(r\)\(1 \le l \le r \le n\),求\(a[l:r]\)的不同子序列的最小值之和。

例如,给定序列\(5,2,4,1,3\),询问给定的两个数为\(1和3\),那么\(a[1:3]\)
6个子序列\(a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3]\),这6个子序列的最小值之和为\(5+2+4+2+2+2=17\)

Solution

首先有一个很常见的套路,我们不能直接统计就算贡献。

对于一个区间中的最小值 \(a_p\), 所有跨越她的区间有\((r - p + 1) \times (p - l + 1)\)个, 可以加入贡献, 因此我们接着要计算的就是区间\([l, p)\)\((p, r]\)的答案.

但这样做下去单次最好也要\(O(n)\).

我们定义一个\(F[l][r]\), 为右端点在\(r\), 左端点在\([l,r]\)的所有答案, 对于右侧的情况, 我们需要得到的就是\(F[p + 1][r]\). 首先考虑\(F\)的递推关系, 定义\(pre_r\)\(r\)之前第一个小于她的位置, 那么在这之后\(a_r\)都是区间内最小值, 也就是: \(F[l][r] = F[l][pre_r] + a_r \times (r - pre_r)\).

这是可以递推的! 但由于状态量是\(n^2\)的, 我们还需要做一些优化.

考虑丢掉\(l\), 定义一个状态\(f_p\), 归纳可得:

由于\(p\)是区间内最小值, 所以向前推若干位时, 一定会存在一个\(x\), 使得\(pre_x = p\), 那么对于一个\(r\), 就有\(f_r = a_r \times (r - pre_r) + a_{pre_r} \times(\ldots)+\ldots + a_x \times (x - p) + f_p\).

这时我们有\(f_r - f_p\)为右端点在\(r\), 左端点在\((p, r]\)的答案.

那么我们就可以得出每个右端点\(r_i \in (p,r]\)的答案了, 利用前缀和优化, 有:

$ \begin{align}ans = \sum_{i = p + 1}^{r}{f_i - f_p} = \sum_{i=p+1}^{r}{f_i} - {f_p \times (r - p)} = sum_r - sum_p - f_p\times (r - p)\end{align}$.

左侧的答案同理,反向求一遍即可。

单调栈可以\(O(n)\)求出每个位置的\(pre\)\(suf\), 每次询问用ST表查出最小值位置,前后两端求一下答案就好了。

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

using namespace std;

typedef long long ll;
const int maxn = 1e+5 + 5;

int n, q;
int a[maxn], pre[maxn], suf[maxn];
int stk[maxn], top;
int ST[maxn][18], LO2[maxn], PO2[20];
ll ftr[maxn], ftl[maxn], sfr[maxn], sfl[maxn];

inline int rd() {
  register int x = 0, f = 0, c = getchar();
  while (!isdigit(c)) {
    if (c == '-') f = 1;
    c = getchar();
  }
  while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
  return f ? -x : x;
}
inline int query(int l, int r) {
  int delt = LO2[r - l + 1];
  return (a[ST[l][delt]] < a[ST[r - PO2[delt] + 1][delt]] ?
          ST[l][delt] : 
          ST[r - PO2[delt] + 1][delt]);
} 

int main() {
  n = rd(); q = rd();
  LO2[1] = 0;
  for (int i = 2; i <= n; ++i) LO2[i] = (LO2[i >> 1] + 1);
  PO2[0] = 1;
  for (int i = 1; i < 18; ++i) PO2[i] = (PO2[i - 1] << 1);
  for (int i = 1; i <= n; ++i) {
    a[i] = rd(); ST[i][0] = i;
  }
  for (int j = 1; j <= LO2[n]; ++j)
    for (int i = 1; i <= n - PO2[j - 1] + 1; ++i)
      ST[i][j] = (a[ST[i][j - 1]] < a[ST[i + PO2[j - 1]][j - 1]] ? ST[i][j - 1] : ST[i + PO2[j - 1]][j - 1]);

  a[0] = a[n + 1] = 0x3f3f3f3f;
  for (int i = 1; i <= n; ++i) {
    while (top && a[stk[top]] > a[i]) suf[stk[top]] = i, top--;
    pre[i] = stk[top]; stk[++top] = i; 
  }
  while (top) pre[stk[top]] = stk[top - 1], suf[stk[top]] = n + 1, top--;
  for (int i = 1; i <= n; ++i)
    ftr[i] = ftr[pre[i]] + (ll)a[i] * (i - pre[i]), sfr[i] = sfr[i - 1] + ftr[i];
  for (int i = n; i; --i)
    ftl[i] = ftl[suf[i]] + (ll)a[i] * (suf[i] - i), sfl[i] = sfl[i + 1] + ftl[i];
  int l, r, pos;
  while (q--) {
    l = rd(); r = rd(); pos = query(l, r);
    ll ans = 1ll * a[pos] * (pos - l + 1) * (r - pos + 1) + 
             (ll)sfr[r] - (ll)sfr[pos] - 1ll * ftr[pos] * (r - pos) +
             (ll)sfl[l] - (ll)sfl[pos] - 1ll * ftl[pos] * (pos - l);
    printf("%lld\n", ans);
  }
  return 0;
}

然后听说有一道类似的题BZOJ4262

线段树 + 树状数组(什么鬼, 我选择单调栈)

Description

给定一个序列,初始为空。现在我们将$1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置。每插入一个数字,我们都想知道此时最长上升子序列长度是多少?

Solution

我们可以发现一个性质, 由于数字是递增插入的, 所以不会影响插入位置后面的答案. 那么我们直接构造出最终的序列, 求出每个位置对应数值的答案\(f_{a[i]}\), 那么到每个数值为止的答案就是\(f\)的前缀最大值了.

怎么构造最终序列呢? 平衡树?算了早就不会写了可以用线段树倒着占坑, 因为后插入的不会影响先插入的相对位置, 也就是我们倒着插入时找到第\(x_i + 1\)个空位就可以了, 在线段树上二分即可.

然后就是求LIS了, 随便哪种\(O(nlogn)\)的算法都可以.

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

using namespace std;

const int maxn = 1e+5 + 5;

int n, a[maxn];

int stk[maxn], mx, top, ans[maxn];

int s[maxn << 2];
inline void pushup(int cur) {
  s[cur] = s[cur << 1] + s[cur << 1|1];
}
void build(int cur, int l, int r) {
  if (l == r) {
    s[cur] = 1; return;
  }
  int mid = (l + r) >> 1;
  build(cur << 1, l, mid);
  build(cur << 1|1, mid + 1, r);
  pushup(cur);
}
void update(int cur, int l, int r, int p, int v) {
  if (l == r) {
    s[cur] = v;
    return;
  }
  int mid = (l + r) >> 1;
  if (p <= mid) update(cur << 1, l, mid, p, v);
  else update(cur << 1|1, mid + 1, r, p, v);
  pushup(cur);
}
int query(int cur, int l, int r, int k) {
  if (l == r) {
    return l;
  }
  int mid = (l + r) >> 1, tmp = s[cur << 1];
  if (k <= tmp) return query(cur << 1, l, mid, k);
  else return query(cur << 1|1, mid + 1, r, k - tmp); 
}

int main()
{
  scanf("%d", &n); int *x = new int[n + 1];
  build(1, 1, n);
  for (int i = 1; i <= n; ++i)
    scanf("%d", x + i);
  for (int i = n; i; --i) {
    //scanf("%d", x);
    int pos = query(1, 1, n, *(x+i) + 1);
    a[pos] = i;
    update(1, 1, n, pos, 0);
  }
  for (int i = 1; i <= n; ++i) {
    if (stk[top] < a[i]) stk[++top] = a[i], ans[a[i]] = top;
    else {
      int pos = lower_bound(stk + 1, stk + 1 + top, a[i]) - stk;
      stk[pos] = a[i];
      ans[a[i]] = pos;
    }
  }
  for (int i = 1; i <= n; ++i) {
    ans[i] = max(ans[i], ans[i - 1]);
    printf("%d\n", ans[i]);
  }
  return 0;
}

拆点, 矩阵乘法,路径计数。

Description

给定一张n个点m条边的带权有向图,每条边的边权只可能是1,2,3中的一种。

将所有可能的路径按路径长度排序,请输出第k小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点。

Solution

看到巨大的数据范围就会想到log级别的算法了,此题点数不多,矩阵乘法是一个选择。

实际操作可以类比一道边权在10以内的题,我们也可以按单位距离拆点,然后进行路径计数,恰好等于K时走了多少步,就是答案长度了。

写起来有很多细节,而且计数很容易爆long long,膜了GXZLegend的题解,可以绑一个计数器到各个点,然后用倍增的方式,逐次累加答案并构造出答案的每个二进制位。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
 
using namespace std;
 
const int maxp = 121;
const int maxn = 41;
 
typedef long long ll;
 
int n, m, lim;
ll k, ans;
 
struct matrix
{
  ll val[maxp][maxp];
  matrix() {
    memset(val, 0, sizeof val);
  }
  ll& operator () (int x, int y) {
    return val[x][y];
  }
  matrix operator * (matrix b) {
    matrix ret;
    for (int k = 0; k <= lim; ++k)
      for (int i = 0; i <= lim; ++i)
        for (int j = 0; j <= lim; ++j)
          ret(i, j) += val[i][k] * b(k, j);
    return ret;
  }
  bool check() {
    ll count = 0;
    for (int i = 1; i <= n; ++i) {
      count += (val[i][0] - 1);
      if (count >= k) return 1; 
    }
    return 0;
  }
}dou[70], res;
 
int main() {
  int u, v, w;
  scanf("%d%d%lld", &n, &m, &k);
  dou[0](0,0) = 1; lim = 3 * n;
  for (int i = 1; i <= n; ++i)
    res(i, i) = dou[0](i, 0) = dou[0](i, i + n) = dou[0](i + n, i + 2 * n) = 1;
  for (int i = 1; i <= m; ++i) {
    scanf("%d%d%d", &u, &v, &w);
    dou[0](u + (w - 1) * n, v)++;
  }
  int tim = 1;
  for(; ; ++tim) {
    if (tim > 65) {
      puts("-1");
      return 0;
    }
    dou[tim] = dou[tim - 1] * dou[tim - 1];
    if (dou[tim].check()) break;
  }
  tim -= 1;
  for (; tim >= 0; --tim) {
    matrix tmp = res * dou[tim];
    if (!tmp.check()) res = tmp, ans |= (1ll << tim);
  }
  printf("%lld\n", ans);
  return 0;
}

A Set Of Fun Problems II

两道洛谷上的题。

Luogu P2425

Description

小红帽喜欢回文数,但生活中的数常常不是回文数。

现在她手上有\(t\)个数,现在她知道这\(t\)个数分别在\(x\)进制下是回文数\((x \ge 2)\),请你对于每个数求出最小的\(x\).

Solution

首先, 对于一个数\(n\), \(n+1\) 一定是一个回文数, 因为她在该进制下只有一位.

对于小于\(\sqrt{n}\)的进制, 我们可以暴力枚举.

对于大于\(\sqrt{n}\)的进制, 我们知道她一定只有两位, 分别是\(n \div x\)\(n\,mod\,x\) , 如果她是回文的, \(n\)就可以表示成\(p \times x + p\)的形式, 也就是\(p(x + 1) = n\), 所以我们可以找一个最大的\(p\), 使得\(p \le \sqrt{n}\)\(p |n\). 这样的枚举也是\(O(\sqrt{n})\)的, 找到一个以后, 答案就是\(n \div p - 1\)了.

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>

using namespace std;

typedef long long ll;

const ll inf = 10000000000;

ll a, arr[1005], len;

bool chk(ll d) {
  bool sol = true; 
  ll tmp = a;
  len = 0;
  while (tmp) {
    arr[len++] = tmp % d;
    tmp /= d;
  }
  int mid = len / 2;
  for (int i = 0; i <= mid; ++i) {
    int j = len - i - 1;
    if (arr[i] != arr[j]) {
      sol = false; break;
    }
  }
  return sol;
}

int main() {
  int T; scanf("%d", &T);
  while (T--) {
    scanf("%lld", &a);
    if (a == 1 || a == 3) {
      puts("2");
      continue;
    } else if (a == 2) {
      puts("3");
      continue;
    }
    ll pos = inf;
    ll lim = sqrt(a) + 1;
    for (int i = 2; i <= lim; ++i) {
      if (chk(i)) {
        pos = i; break;
      }
    }
    if (pos < inf) {
      printf("%lld\n", pos);
      continue;
    }
    for (ll i = a / lim - 1; i; --i) {
      if (a == a / i * i) {
        pos = a / i - 1;
        break;
      }
    }
    if (pos < inf) printf("%lld\n", pos);
    else printf("%lld\n", a + 1);
  }
  return 0;
}

Luogu P1875

Description

给定一些合成关系, 和直接购买的费用, 求得到一个东西的最小代价和方案数.

Solution

听说树形DP能过, 不过没什么意义. 因为题目并没有保证没有环出现.

我们用最短路的过程来消去DP的后效性.

考虑Dijkstra的过程, 当一个点从堆里被拿出来的时候, 就可以确定她是最小值了. 那么我们的更新就建立再已经求出的最小值上, 并且只用最小值向下更新, 至于计数就是套路了, 都知道怎么做.

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

const int maxn = 1005;

struct node
{
  int d, id;
  node(int d_ = 0, int i_ = 0):d(d_), id(i_) {}
  bool operator < (const node &rhs) const {
    return d > rhs.d;
  }
};
int n, tow[maxn][maxn], cost[maxn];
int f[maxn], tag[maxn], ans[maxn];
priority_queue<node> q;

int main() {
  scanf("%d", &n);
  for (int i = 0; i < n; ++i)
    scanf("%d", &cost[i]), q.push(node(cost[i], i)), ans[i] = 1;
  int x, y, z;
  memset(tow, -1, sizeof tow);
  while (scanf("%d%d%d", &x, &y, &z) != EOF) {
    tow[x][y] = z;
    tow[y][x] = z;
  }
  while (!q.empty()) {
    node u = q.top(); q.pop();
    if (tag[u.id]) continue;
    //printf("%d\n", u.id);
    tag[u.id] = 1;
    for (int i = 0; i < n; ++i) {
      if (tag[i] && ~tow[u.id][i]) {
        //printf("%d %d %d\n", u.id, i, tow[u.id][i]);
        if (cost[u.id] + cost[i] < cost[tow[u.id][i]])
          cost[tow[u.id][i]] = cost[u.id] + cost[i], 
          ans[tow[u.id][i]] = ans[u.id] * ans[i],
          q.push(node(cost[tow[u.id][i]], tow[u.id][i]));//,printf("+%d\n", tow[u.id][i]);
        else if (cost[u.id] + cost[i] == cost[tow[u.id][i]])
          ans[tow[u.id][i]] += ans[u.id] * ans[i],
          q.push(node(cost[tow[u.id][i]], tow[u.id][i]));//,printf("s%d\n", ans[tow[u.id][i]]);
      }
    }
  }
  printf("%d %d\n", cost[0], ans[0]);
  return 0;
}

树链剖分。

Description

有一张无向图, 定义关键路径为删去后图不连通的路径, 现在要依次删去一些边, 随时有一些询问, 求两点间的关键路径数.

Solution

这种题首先要想到倒序处理, 因为删边不好做.

然后来考虑题目的性质, 加入这张图最后被删得只剩一颗树了, 那么每一次加边, 都会让边两端路径上的关键路径变为0.

而在一颗树上两点之间关键路径数都是1.

区间求和, 修改? 树链剖分啊.

首先删边, 然后把剩下的图跑出来一颗生成树, 再用非树边更新, 接着依次执行操作和询问即可.

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cctype>

using namespace std;

const int maxn = 30005;
const int maxm = 1e+5 + 5;

struct option
{
  int typ, a, b;
}q[maxn + 10005];
struct edge
{
  int to, nxt; bool ban;
}e[maxm << 1];
int n, m, ptr, lnk[maxn], opn, cnt;
int dfn[maxn], top[maxn], f[maxn], mxson[maxn], siz[maxn];
int ans[maxn], dep[maxn];

inline void add(int bgn, int end) {
  e[ptr] = (edge) {end, lnk[bgn], 0};
  lnk[bgn] = ptr; ptr++;
  e[ptr] = (edge) {bgn, lnk[end], 0};
  lnk[end] = ptr; ptr++;
}
inline int rd() {
  register int x = 0, f = 0, c = getchar();
  while (!isdigit(c)) {
    if (c == '-') f = 1;
    c = getchar();
  }
  while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
  return f ? -x : x;
}
void dfs(int x, int fa) {
  //printf("%d\n", x);
  siz[x] = 1;
  f[x] = fa;
  dep[x] = dep[fa] + 1;
  for (int p = lnk[x]; ~p; p = e[p].nxt) {
    int y = e[p].to;
    if (y == fa || e[p].ban || dep[y]) continue;
    dfs(y, x);
    siz[x] += siz[y];
    if (siz[y] > siz[mxson[x]]) mxson[x] = y;
  }
}
void dfs2(int x, int init) {
  dfn[x] = ++cnt;
  top[x] = init;
  if (!mxson[x]) return;
  dfs2(mxson[x], init);
  for (int p = lnk[x]; ~p; p = e[p].nxt) {
    int y = e[p].to;
    if (f[y] != x || y == mxson[x] || e[p].ban) continue;
    dfs2(y, y);
  }
}

int s[maxn << 2], tag[maxn << 2];
inline void pushup(int cur) {
  s[cur] = s[cur << 1] + s[cur << 1|1];
}
inline void pushdown(int cur) {
  if (tag[cur]) {
    s[cur << 1] = s[cur << 1|1] = 0;
    tag[cur << 1] = tag[cur << 1|1] = 1;
    tag[cur] = 0;
  }
}
void build(int cur, int l, int r) {
  if (l == r) {
    s[cur] = 1;
    return;
  }
  int mid = (l + r) >> 1;
  build(cur << 1, l, mid);
  build(cur << 1|1, mid + 1, r);
  pushup(cur);
}
void update(int cur, int l, int r, int L, int R) {
  if (L <= l && r <= R) {
    s[cur] = 0;
    tag[cur] = 1;
    return;
  }
  int mid = (l + r) >> 1; pushdown(cur);
  if (L <= mid) update(cur << 1, l, mid, L, R);
  if (R > mid) update(cur << 1|1, mid + 1, r, L, R);
  pushup(cur);
}
int query(int cur, int l, int r, int L, int R) {
  if (L <= l && r <= R) return s[cur];
  int mid = (l + r) >> 1, ret = 0; pushdown(cur);
  if (L <= mid) ret += query(cur << 1, l, mid, L, R);
  if (R > mid) ret += query(cur << 1|1, mid + 1, r, L, R);
  return ret;
}
void updt(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) swap(x, y);
    update(1, 1, n, dfn[top[x]], dfn[x]);
    x = f[top[x]];
  }
  if (dep[x] > dep[y]) swap(x, y);
  if (dep[x] != dep[y]) update(1, 1, n, dfn[x] + 1, dfn[y]);
}
void efs(int x) {
  //printf("e%d\n", x);
  for (int p = lnk[x]; ~p; p = e[p].nxt) {
    int y = e[p].to;
    if (e[p].ban) continue;
    if (f[y] == x) efs(y);
    if (y != f[x] && dep[y] < dep[x])
      updt(x, y);
  }
}
int queryt(int x, int y) {
  int ret = 0;
  //printf("%d %d\n", x, y);
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) swap(x, y);
    //printf("q %d %d\n", dfn[top[x]], dfn[x]);
    ret += query(1, 1, n, dfn[top[x]], dfn[x]);

    x = f[top[x]];
  }
  if (dep[x] > dep[y]) swap(x, y);
  //printf("q %d %d\n", dfn[x], dfn[y]);
  ret += query(1, 1, n, dfn[x], dfn[y]);
  ret -= query(1, 1, n, dfn[x], dfn[x]);
  return ret;
}

int main() {
  n = rd(); m = rd();
  memset(lnk, -1, sizeof lnk);
  int a, b;
  for (int i = 1; i <= m; ++i) {
    a = rd(); b = rd();
    add(a, b);
  }
  int c;
  while (true) {
    c = rd(); if (c == -1) break;
    q[++opn].typ = c;
    q[opn].a = rd(); q[opn].b = rd();
    if (!c) {
      for (int p = lnk[q[opn].a]; ~p; p = e[p].nxt) {
        if (e[p].to == q[opn].b) {
          //printf("%d %d\n", e[p].to, q[opn].b);
          //puts("try");
          e[p].ban = e[p ^ 1].ban = 1;
          break;
        }
      }
    }
  }
  dfs(1, 0);
  dfs2(1, 1);
  build(1, 1, n);
  efs(1);
  for (int i = opn; i; --i) {
    //puts("try");
    if (q[i].typ) {
      ans[i] = queryt(q[i].a, q[i].b);
    } else {
      updt(q[i].a, q[i].b);
    }
  }
  for (int i = 1; i <= opn; ++i) {
    if (q[i].typ) printf("%d\n", ans[i]);
  }
  return 0;
}

拓扑排序 + 最短路。

Description

有一些正权双向边和边权可能为负的单向边, 保证没有负环. 求最短路, 且不能使用SPFA.

Solution

不能使用SPFA就只能用Dijkstra了, 但是她不能处理负权边, 注意到没有负环, 也就是把图缩点后一定是负权边在DAG上转移, 假如我们每次只做联通块内的点, Dijkstra是没有影响的.

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <stack>
#include <queue>

using namespace std;

const int maxn = 25005;
const int maxm = 100005;

struct edge
{
  int to, nxt, v;
}e[maxm << 1], ce[maxm];
struct node
{
  int d, id;
  node(int d_ = 0, int i_ = 0):d(d_),id(i_){}
  bool operator < (const node &rhs) const {
    return d > rhs.d;
  }
};
int cptr, clnk[maxn];
int ptr, t, lnk[maxn], dis[maxn], n, m1, m2, s, dgr[maxn];
int dfn[maxn], cnt, num, bel[maxn], low[maxn], vis[maxn];
stack<int> st;
vector<int> vec[maxn];

inline int rd() {
  register int x = 0, f = 0, c = getchar();
  while (!isdigit(c)) {
    if (c == '-') f = 1;
    c = getchar();
  }
  while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
  return f ? -x : x;
}
inline void add(int bgn, int end, int val) {
  e[++ptr] = (edge){end, lnk[bgn], val};
  lnk[bgn] = ptr;
}
inline void cadd(int bgn, int end, int val) {
  ce[++cptr] = (edge){end, clnk[bgn], val};
  clnk[bgn] = cptr;
}
void tarjan(int x) {
  dfn[x] = low[x] = ++cnt;
  vis[x] = 1; 
  st.push(x);
  for (int p = lnk[x]; p; p = e[p].nxt) {
    int y = e[p].to;
    if (!dfn[y]) {
      tarjan(y);
      low[x] = min(low[x], low[y]);
    } else if (vis[y]) {
      low[x] = min(low[x], dfn[y]);
    }
  }
  if (dfn[x] == low[x]) {
    int now; num++;
    do {
      now = st.top(); st.pop();
      bel[now] = num; vec[num].push_back(now);
      vis[now] = 0;
    } while (now != x);
  }
}
void dijkstra(int x) {
  priority_queue<node> q;
  for (vector<int>::iterator iter = vec[x].begin(); iter != vec[x].end(); ++iter)
    if (dis[*iter] < 0x3f3f3f3f) q.push(node(dis[*iter], *iter));
  while (!q.empty()) {
    node u = q.top(); q.pop();
    if (vis[u.id]) continue;
    vis[u.id] = 1;
    for (int p = lnk[u.id]; p; p = e[p].nxt) {
      int y = e[p].to;
      if (dis[y] > dis[u.id] + e[p].v) {
        dis[y] = dis[u.id] + e[p].v;
        if (bel[u.id] != bel[y]) continue;
        q.push(node(dis[y], y));
      }
    }
  } 
}
void toposort() {
  queue<int> q;
  for (int i = 1; i <= num; ++i)
    if (!dgr[i]) q.push(i);
  while (!q.empty()) {
    int u = q.front(); q.pop();
    dijkstra(u);
    for (int p = clnk[u]; p; p = ce[p].nxt) {
      int y = ce[p].to;
      if (!--dgr[y]) q.push(y);
    }
  }
}


int main() {
  n = rd(); m1 = rd(); m2 = rd(); s = rd();
  int u, v, w;
  memset(dis, 0x3f, sizeof dis);
  dis[s] = 0;
  for (int i = 1; i <= m1; ++i) {
    u = rd(); v = rd(); w = rd();
    add(u, v, w);
    add(v, u, w);
  }
  for (int i = 1; i <= m2; ++i) {
    u = rd(); v = rd(); w = rd();
    add(u, v, w);
  }
  for (int i = 1; i <= n; ++i)
    if (!dfn[i]) tarjan(i);
  for (int i = 1; i <= n; ++i) {
    for (int p = lnk[i]; p; p = e[p].nxt) {
      int y = e[p].to;
      if (bel[i] != bel[y])
        cadd(bel[i], bel[y], e[p].v), ++dgr[bel[y]];
    }
  }
  toposort();
  for (int i = 1; i <= n; ++i) {
    if (dis[i] < 0x3f3f3f3f) printf("%d\n", dis[i]);
    else puts("NO PATH");
  }
  return 0;
}

线段树。

Description

维护一个01序列, 支持查询区间1的个数, 区间置0, 取出区间里所有的1并填入某个区间, 优先填靠前的0, 多了就浪费.

Solution

首先看见区间操作肯定要线段树的. 区间查询和置0都好做, 主要是操作3.

操作3的第一步是取出1, 这个和区间查询同理.

如果发现要浪费, 就直接区间置1.

关键就在于每次找一段最长0了, 这个可以二分啦. 有时区间头是1, 那么我们还需要跳过一段最长1. 这两个二分都可以做.

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cctype>

using namespace std;

const int maxn = 2e+5 + 5;

int n, m;
int s[maxn << 2], tag[maxn << 2];

inline void pushup(int cur) {
  s[cur] = s[cur << 1] + s[cur << 1|1];
}
inline void pushdown(int cur, int len) {
  if (~tag[cur]) {
    s[cur << 1] = tag[cur] * (len - (len >> 1));
    s[cur << 1|1] = tag[cur] * (len >> 1);
    tag[cur << 1] = tag[cur << 1|1] = tag[cur];
    tag[cur] = -1;
  }
}
void build(int cur, int l, int r) {
  tag[cur] = -1;
  if (l == r) {
    s[cur] = 1;
    return;
  }
  int mid = (l + r) >> 1;
  build(cur << 1, l, mid);
  build(cur << 1|1, mid + 1, r);
  pushup(cur);
}
void update(int cur, int l, int r, int L, int R, int v) {
  if (L <= l && r <= R) {
    s[cur] = v * (r - l + 1);
    tag[cur] = v;
    return;
  }
  int mid = (l + r) >> 1; pushdown(cur, r - l + 1);
  if (L <= mid) update(cur << 1, l, mid, L, R, v);
  if (R > mid) update(cur << 1|1, mid + 1, r, L, R, v);
  pushup(cur);
}
int querys(int cur, int l, int r, int L, int R) {
  if (L <= l && r <= R) return s[cur];
  int mid = (l + r) >> 1, ret = 0; pushdown(cur, r - l + 1);
  if (L <= mid) ret += querys(cur << 1, l, mid, L, R);
  if (R > mid) ret += querys(cur << 1|1, mid + 1, r, L, R);
  return ret;
}
int query(int lb, int rb, int k) {
  if (lb > rb) return -1;
  int l = 0, r = rb - lb;
  while (l <= r) {
    int mid = (l + r) >> 1;
    if (querys(1, 1, n, lb, lb + mid) == k * (mid + 1)) l = mid + 1;
    else r = mid - 1;
  }
  return l;
}
inline int rd() {
  register int x = 0, c = getchar();
  while (!isdigit(c)) c = getchar();
  while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
  return x;
}

int main() {
  n = rd(); m = rd();
  build(1, 1, n);
  int opt, l1, r1, l2, r2;
  while (m--) {
    opt = rd();
    if (opt == 0) {
      l1 = rd(); r1 = rd();
      update(1, 1, n, l1, r1, 0);
    } else if (opt == 1) {
      l1 = rd(); r1 = rd(); l2 = rd(); r2 = rd();
      int s = querys(1, 1, n, l1, r1);
      update(1, 1, n, l1, r1, 0);
      int s_already = querys(1, 1, n, l2, r2);
      if (s > (r2 - l2 + 1) - s_already) {
        update(1, 1, n, l2, r2, 1); continue;
      } 
      if (!s) continue;
      while (true) {
        int hed = 0;
        if (querys(1, 1, n, l2, l2) == 1) {
          hed = query(l2, r2, 1); l2 += hed;
        } 
        hed = query(l2, r2, 0);
        if (hed == -1) break;
        if (hed > s) {
          update(1, 1, n, l2, l2 + s - 1, 1);
          break;
        } else {
          update(1, 1, n, l2, l2 + hed - 1, 1);
          l2 += hed; s -= hed;
        }
      }
    } else {
      l1 = rd(); r1 = rd();
      int ans = 0;
      while (true) {
        int hed = 0;
        if (querys(1, 1, n, l1, l1) == 1) {
          hed = query(l1, r1, 1); l1 += hed;
        }
        hed = query(l1, r1, 0);
        if (hed == -1) break;
        ans = max(ans, hed);
        l1 += hed;
        if (l1 > r1) break;
      }
      printf("%d\n", ans);
    } 
  }
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/nishikino-curtis/p/9921947.html
今日推荐