整除分块 + 哈夫曼树优化:CCPC2026网络赛L

Logging Compression - Problem - QOJ.ac

这题其实就是让你实现哈夫曼树。

我们首先考虑压缩信息。如果一段区间每个数出现是相同的,我们就可以合并来存储他们。那样子在优先队列里我们记两个值:当前值 ss + 有多少个数是这样的 nn。

我们考虑我们在小根堆里取出来的第一个东西,其 n≥2n\ge 2,那样子其实本质上有 $\lfloor\dfrac n 2\rfloor $ 对自己和自己合并。那我们直接把 (2s,⌊n2⌋)(2s,\lfloor\dfrac n 2\rfloor ) 放回优先队列里即可。

如果还剩一个,那样子就再堆顶取一个合并就行了。

image-20261005164947818


然后现在我们考虑怎么分段。

题目给的区间覆盖信息我们拿差分转一转就行。

然后初始有一个除 kk 之类的限制,我们拿整除分块就行。


但这样子时间复杂度是根号+双log,过不去。

我们发现因为我们上述的方法,哈夫曼树的节点会很多(远多于叶子节点)。但由于每次合并出来的结果必然单增。因此我们可以初始先对叶子节点排个序,然后每次新增的非叶子节点我们直接拿一个队列来存就行。


还有两个小细节

一个是 n=1n=1,那样子1也是要占位的

另一个是如果有位置没有被覆盖,它们也是要占位的。这个时候我们就需要给某个代价最小的叶子节点往下降一层,旁边挂一个虚空点。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include<bits/stdc++.h>
#include<cassert>
using namespace std;bool Mbe;
#ifdef LOCAL
#define debug(...) fprintf(stdout, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
#define fi first
#define se second
//#define M
//#define mo
//#define N
struct node {
int n, s;
bool operator < (const node &A) const {
return s < A.s;
}
};
//priority_queue<node>q;
vector<node>q1, q2;
int n, m, i, j, k, T;
vector<int>G;
map<int, int>mp;
int l, r, ans, sum;

bool Med; signed main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
atexit([]{fprintf(stderr,"%.lfms\n%lfMB\n",clock()*1000./CLOCKS_PER_SEC,(&Mbe-&Med)/1024./1024);});
#endif
// srand(time(NULL));
// T = read();
// while(T--) {
//
// }
n = read(); m = read();
if(n == 1) { printf("%lld", m); return 0; }
for(l = 1; l <= n; l = r + 1) {
r = min(n, n / (n / l));
// debug("[%lld %lld] %lld\n", l, r, n / l);
G.pb(l); G.pb(r);
}
for(i = 1; i <= m; ++i) {
l = read(); r = read();
mp[l]++; mp[r + 1]--;
G.pb(l); G.pb(r + 1);
// if(n == 100 && m == 50) printf("%lld %lld\n", l, r);
}
sort(G.begin(), G.end());
G.erase(unique(G.begin(), G.end()), G.end());
// for(auto t : G) debug("%lld ", t);
// debug("\n");
m = G.size(); int mn = 1e18, len = 0;
for(i = 0; i < m - 1; ++i) {
sum += mp[G[i]];
if(sum) {
q1.pb({G[i + 1] - G[i], sum * (n / G[i])});
len += G[i + 1] - G[i];
mn = min(mn, sum * (n / G[i]));
debug("[%lld %lld] %lld(%lld * %lld)\n", G[i], G[i + 1] - 1, sum * (n / G[i]), sum, n / G[i]);
}
}
if(len != n) ans = mn;
sort(q1.begin(), q1.end());
i = j = 0; int flg = 0;
auto Take = [&] () -> node {
node t;
if(i == q1.size()) t = q2[j], ++j, flg = 2;
else if(j == q2.size()) t = q1[i], ++i, flg = 1;
else {
if(q1[i].s < q2[j].s) t = q1[i], ++i, flg = 1;
else t = q2[j], ++j, flg = 2;
}
return t;
};
while(i < q1.size() || j < q2.size()) {
auto t = Take();
debug("(%lld %lld)\n", t.n, t.s);
if(t.n >= 2) {
q2.pb({t.n / 2, t.s * 2});
ans += t.n / 2 * 2 * t.s;
if(t.n % 2 == 0) continue;
}
if(i < q1.size() || j < q2.size()) {
auto t2 = Take();
if(t2.n > 1) {
if(flg == 1) --i, q1[i].n--;
if(flg == 2) --j, q2[j].n--;
}
ans += t.s + t2.s;
q2.pb({1, t.s + t2.s});
}
}
printf("%lld", ans);
return 0;
}