1. 程式人生 > 實用技巧 >2020.11.13 胡策

2020.11.13 胡策

目錄


寫在前面

一套好像是學長鬍策的題。


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 的程式碼

#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\)構造出新的數列 \(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\) 的同時,用權值樹狀陣列簡單維護即可,複雜度 \(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;
}