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\),有顯然的狀態轉移:
發現乘積很大,額外維護一個數組儲存 \(\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;
}