2020.11.13 胡策


写在前面

一套好像是学长胡策的题。

扯一句,这个 b 的数据有问题= =


A

给定一长度为 \(n\) 的数列 \(a\),要求从 \(a\) 中选出一些数,使这些数不能相邻,最大化选出的数的乘积。
答案对 \(10^9 + 9\) 取模。
\(1\le n,a_i\le 10^6\)
1S,128MB,O2,C++ 11。

显然 DP,设 \(f_{i,0/1}\) 表示考虑到前 \(i\) 个位置,第 \(i\) 个位置选/不选时,选出的数的最大乘积。
初始化 \(f_{0}=1\),有显然的状态转移:

\[\begin{aligned} f_{i,0} &= \max(f_{i-1, 0}, f_{i-1,1})\\ f_{i,1} &= f_{i-1,0} \times a_{i} \end{aligned}\]

发现乘积很大,额外维护一个数组储存 \(\log f\),用于比较大小,以完成 \(f_{i,0}\) 的转移。
时间复杂度 \(O(n)\)

这里是 @Kersen 的代码 /qq

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define N 1000010
#define M 1010

using namespace std;
const int mod = 1e9+9;
const int inf = 0x3f3f3f3f;
int n, a[N]; ll dp[N][2];
double lna[N], f[N][2];

int read() {
  int s = 0, f = 0; char ch = getchar();
  while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
  while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
  return f ? -s : s;
}

int main() {
  freopen("jsnk.in", "r", stdin);
  freopen("jsnk.out", "w", stdout);
  n = read();
  for (int i = 1; i <= n; i++) a[i] = read(), lna[i] = log10(a[i]);
  dp[0][0] = dp[0][1] = 1;
  for (int i = 1; i <= n; i++) {
    if (f[i - 1][0] >= f[i - 1][1]) 
      f[i][0] = f[i - 1][0], dp[i][0] = dp[i - 1][0];
    else f[i][0] = f[i - 1][1], dp[i][0] = dp[i - 1][1];
    f[i][1] = f[i - 1][0] + lna[i];
    dp[i][1] = (dp[i - 1][0] * 1ll * a[i]) % mod;
  }
  if (f[n][0] >= f[n][1]) cout << dp[n][0];
  else cout << dp[n][1];
}

B

\(T\) 组数据,每次给定一张 \(n\) 个点 \(m\) 条边的无向图,边有边权。
给定 \(k\) 个关键点,求 \(k\) 个关键点两两距离的最小值。
\(1\le T\le 10\)\(1\le k\le n\le 10^5\)\(1\le m\le 10^5\)\(0\le\) 边权 \(\le 10^3\)
3.5S,1G,O2,C++ 11。

扯一句,这个 b 卡 spfa,他还是人?

二进制分组多源最短路裸题。
枚举二进制位,根据关键点的编号这一位是否为 1 进行分组,将这一位为 1 的作为起点,跑多源最短路。统计到达这一位不为 1 的关键点的最短路长度。
由于所有关键点的编号都不同,能保证两个关键点在某一次分组中不在同一集合里,因此正确。

使用堆优化 Dijkstra 实现,时间复杂度 \(O((n+m)\log (n+m)\log k)\)
注意常数。

//知识点:多源最短路 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 2e5 + 10;
const int kM = 5e5 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
int n, m, k, ans, pos[kN];
int e_num, head[kN], v[kM], w[kM], ne[kM];
int dis[kN];
bool vis[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Init() {
  ans = kInf;
  e_num = 0;
  memset(head, 0, sizeof (head));
}
void AddEdge(int u_, int v_, int w_) {
  v[++ e_num] = v_;
  w[e_num] = w_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Dijkstra(int bit_) {
  std::priority_queue <pr <int, int> > q;
  memset(dis, 63, sizeof (dis));
  memset(vis, 0, sizeof (vis));
  for (int i = 1; i <= k; ++ i) {
    if (i & (1 << bit_)) {
      dis[pos[i]] = 0;
      q.push(mp(0, pos[i]));
    }
  }
  
  while (! q.empty()) {
    int u_ = q.top().second;
    q.pop();
    if (vis[u_]) continue ;
    vis[u_] = true;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (dis[u_] + w_ < dis[v_]) {
        dis[v_] = dis[u_] + w_;
        q.push(mp(-dis[v_], v_));
      }
    }
  }
}
//=============================================================
int main() {
  freopen("muzan.in", "r", stdin);
  freopen("muzan.out", "w", stdout);
  int T = read();
  while (T --) {
    Init();
    n = read(), m = read(), k = read();
    for (int i = 1; i <= m; ++ i) {
      int u_ = read(), v_ = read(), w_ = read();
      AddEdge(u_, v_, w_);
      AddEdge(v_, u_, w_);
    }
    for (int i = 1; i <= k; ++ i) pos[i] = read();
    for (int bit = 0; (1 << bit) <= k; ++ bit) {
      Dijkstra(bit);
      for (int i = 1; i <= k; ++ i) {
        if (i & (1 << bit)) continue ;
        Chkmin(ans, dis[pos[i]]);  
      }
    }
    printf("%d\n", ans >= kInf ? -1 : ans);
  }
  return 0;
}

C

给定一只由 \(0,1,2\) 构成的数列,求有多少个区间,满足某权值的数量不多于区间长度的一半。
\(1\le n\le 5\times 10^6\)
1.5S,128MB,O2,C++ 11。

考场 60pts

首先有一些性质:

  • 一个区间可以表示成两个前缀的差。
  • 对于一个不合法区间,只可能有一种权值的出现次数 大于区间长度的一半。

考虑先计算出所有区间的个数 \(\frac{n(n+1)}{2}\),再减去不合法区间个数。
由性质 2,三种不合法区间是互斥的,可分别考虑每一种权值。

先单独考虑 \(0\),若区间 \([l,r]\) 不合法,则显然有:

\[\operatorname{cnt}_0(l,r)\ge \frac{(r-l+1)}{2} \]

其中 \(\operatorname{cnt}_x(l,r)\) 表示区间 \([l,r]\)\(x\) 的个数。再稍微化下式子,原式等价于:

\[\operatorname{cnt}_0(l,r)> \operatorname{cnt}_{1}(l,r) + \operatorname{cnt}_{2}(l,r) \]

即:

\[(\operatorname{cnt}_{1}(l,r) + \operatorname{cnt}_{2}(l,r))-\operatorname{cnt}_0(l,r)< 0 \]

考虑这式子的意义,表示 \(0\) 的贡献为 \(-1\)\(1,2\) 的贡献为 \(1\)
考虑将 \(0\) 的权值设为 \(-1\)\(1,2\) 的权值设为 \(1\)构造出新的数列 \(v\),则上式等价于:

\[v(l,r)<0 \]

再根据性质 1,维护 \(v\) 的前缀和 \(\operatorname{sum}\),把区间权值和拆成两个前缀和考虑,则上式等价于:

\[\operatorname{sum}(r) - \operatorname{sum}(l-1)<0 \]

即对于每个 \(r\),询问有多少个 \(l-1\),满足 \(l<r\),且 \(\operatorname{sum}(r) < \operatorname{sum}(l-1)\)
枚举 \(r\) 的同时,用权值树状数组简单维护不同的 \(\operatorname{sum}\) 的数量即可,时间复杂度 \(O(n\log n)\)

对三种权值分别重复上述过程即可。

//知识点:瞎搞 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, cnt[3][kN];
char s[kN];
LL ans;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Bit {
  #define lowbit(x) (x&-x)
  int Lim, t[(kN << 1) + 10];
  void Init() {
    Lim = 2 * kN;
    memset(t, 0, sizeof (t));
  }
  void Insert(int pos_) {
    for (int i = pos_; i <= Lim; i += lowbit(i)) {
      t[i] ++;
    }
  }
  int Query(int pos_) {
    int ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      ret += t[i];
    }
    return ret;
  }
}
void Solve(int id_) {
  Bit::Init();
  for (int i = 0; i <= n; ++ i) {
//    printf("%lld\n\n", ans);
    ans -= 1ll * Bit::Query(2 * kN) - Bit::Query(cnt[id_][i] + kN);
    Bit::Insert(cnt[id_][i] + kN);
  }
}
//=============================================================
int main() {
  freopen("dokuso.in", "r", stdin);
  freopen("dokuso.out", "w", stdout);
  n = read();
  ans = 1ll * n * (n + 1ll) / 2;
  scanf("%s", s + 1);
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j <= 2; ++ j) {
      cnt[j][i] = cnt[j][i - 1];
      if (s[i] - '0' == j) {
        cnt[j][i] --;
      } else {
        cnt[j][i] ++;
      }
    }
  }
  for (int i = 0; i <= 2; ++ i) Solve(i);
  printf("%I64d\n", ans);
  return 0;
}

100 pts

由于 \(v\) 中只有 1/-1,则 \(\operatorname{sum}\) 的相邻两项之差绝对值只可能为 1。
则树状数组查询的区间边界的差值也只可能为 \(1\),用树状数组维护显得很傻逼。
维护一下上次答案的增量,维护每种权值的数量,大力改即可。
复杂度 \(O(n)\)

//知识点:瞎搞 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e6 + 10;
//=============================================================
int n, cnt[3][kN], sum[2 * kN];
char s[kN];
LL ans;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Solve(int id_) {
  memset(sum, 0, sizeof (sum));
  sum[kN] ++;
  LL nowsum = 0;
  for (int i = 1; i <= n; ++ i) {
    if (cnt[id_][i] - cnt[id_][i - 1] < 0) {
      nowsum += sum[cnt[id_][i - 1] + kN];
    } else {
      nowsum -= sum[cnt[id_][i] + kN];
    }
    ans -= nowsum;
    sum[cnt[id_][i] + kN] ++;
  }
}
//=============================================================
int main() {
//  freopen("dokuso.in", "r", stdin);
//  freopen("dokuso.out", "w", stdout);
  n = read();
  ans = 1ll * n * (n + 1ll) / 2;
  scanf("%s", s + 1);
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j <= 2; ++ j) {
      cnt[j][i] = cnt[j][i - 1];
      if (s[i] - '0' == j) {
        cnt[j][i] --;
      } else {
        cnt[j][i] ++;
      }
    }
  }
  for (int i = 0; i <= 2; ++ i) Solve(i);
  printf("%lld\n", ans);
  return 0;
}

std

std 的化式子也挺经典的,可以看下这道题:

总之就是非常舒服

基本思想是通过调整不等式,使得不等式两侧只与一个下标有关。
比如这样的不等式:\(a_i+b_j>a_j+b_i\),可以调整成 \(a_i-a_j>b_i-b_j\)

std 的推导似乎比我麻烦许多= =
跑路了!

总结

  • \(\log\) 以比较连乘的大小。
  • 学到了二进制分组。
  • 对于一类这样的不等式:\(a_i+b_j>a_j+b_i\),可以调整成 \(a_i-a_j>b_i-b_j\)
  • 维护信息时注意特殊性质,不可把问题过于抽象,可能会丢失某些重要的信息。
posted @ 2020-11-13 15:25  Luckyblock  阅读(126)  评论(4编辑  收藏  举报