Solution Set - 训练计划 链表
咕掉了两道不可做题(指黑色)。
梦幻布丁
放在链表的题单里,和链表有什么关系呢???
因为都是在对颜色整体进行操作,我们可以根据颜色拉出来对应的链表。
那么每次合并就相当于把一个链表接到另一个链表上去,暴力修改,那么是 \(O(n)\) 的,但是要怎么维护答案呢?
首先可以处理出不做任何操作时的答案 \(ans = 1 + \sum_{i = 2} ^ n [a_i \not= a_{i - 1}]\)(植树问题要加 \(1\))。那么假设每次将一个下标为 \(i\) 的颜色 \(x\) 更改为 \(y\),只会引起 \(c_{i - 1}\) 或 \(c_{i + 1}\) 统计的变化(就是上面那个式子),变化量就是 \(\sum\limits_{c_i = x} [c_{i - 1} = y] + [c_{i + 1} = y]\),减去就好了。
但是你看,这一次 \(O(n)\) 不直接飞起?引入一个非常 NB 的东西:启发式合并。就是每次小的接到大的上面去,时间复杂度不会证,但类似并查集的启发式合并,均摊下来是 \(O(\log n)\) 的。或者想成连边,就是树的平均深度 \(O(\log n)\)?这样就完了。
以及维护链表类似于链式前向星那样记录的,具体可以看代码。
namespace liuzimingc {
const int N = 1e6 + 5;
int n, m, a[N], ed[N], nxt[N], head[N], siz[N], ans, fa[N];
void merge(int x, int y) {
for (int i = head[x]; i; i = nxt[i]) ans -= (a[i - 1] == y) + (a[i + 1] == y);
for (int i = head[x]; i; i = nxt[i]) a[i] = y;
nxt[ed[x]] = head[y];
head[y] = head[x];
siz[y] += siz[x];
head[x] = ed[x] = siz[x] = 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
fa[a[i]] = a[i];
if (!siz[a[i]]) ed[a[i]] = i; // 链表的最末端
siz[a[i]]++;
nxt[i] = head[a[i]];
head[a[i]] = i;
ans += a[i] != a[i - 1];
}
while (m--) {
int op, x, y;
cin >> op;
if (op == 1) {
cin >> x >> y;
if (x == y) continue;
if (siz[fa[x]] > siz[fa[y]]) swap(fa[x], fa[y]);
if (!siz[fa[x]]) continue;
merge(fa[x], fa[y]);
}
else cout << ans << endl;
}
return 0;
}
} // namespace liuzimingc
生日礼物
首先的话,我们先贪心地考虑全选连续的为正的数字。假如说这个段数 \(\leq m\) 就可以直接统计输出,否则我们就有两种选择:舍弃掉一正数段,答案减少这一段之和,或者选一段负数段,答案减少负数段之和的绝对值。
所以总之就是对于相同符号的每一段,选出一些使得绝对值之和最小。而且的话,如果我们选择负数段,本质上要求的是把两边的正数段“连起来”,所以肯定不能即选择一个负数段又舍弃掉相邻的正数段,也就是选出不相邻的绝对值最小和。此外最两边的负数显然也是不行的。总的用一个优先队列维护就好了。然后对于这种快速删除元素以及维护元素两边的元素,很 trivial 的用链表就好了。这种模型好像很常见,如种树。
namespace liuzimingc {
const int N = 1e5 + 5;
#define endl '\n'
#define int long long
int n, m, p, pre[N], nxt[N], a[N], b[N], tot = 1, ans;
bool vis[N];
struct node {
int v, pos;
friend bool operator <(node a, node b) {
return abs(a.v) > abs(b.v);
}
};
priority_queue<node> q;
void del(int p) {
vis[p] = true;
pre[nxt[p]] = pre[p];
nxt[pre[p]] = nxt[p];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++)
if (b[i] * a[tot] >= 0) a[tot] += b[i];
else a[++tot] = b[i];
n = tot;
for (int i = 2; i <= n; i++) pre[i] = i - 1;
for (int i = 1; i < n; i++) nxt[i] = i + 1;
tot = 0;
for (int i = 1; i <= n; i++) {
if (a[i] > 0) tot++, ans += a[i];
q.push((node){ a[i], i });
}
while (tot > m) {
while (vis[q.top().pos]) q.pop();
node now = q.top(); q.pop();
if ((pre[now.pos] && nxt[now.pos]) || now.v > 0) ans -= abs(now.v);
else continue;
a[now.pos] += a[pre[now.pos]] + a[nxt[now.pos]];
del(pre[now.pos]);
del(nxt[now.pos]);
q.push((node){ a[now.pos], now.pos });
tot--;
}
cout << ans << endl;
return 0;
}
#undef int
} // namespace liuzimingc
办公楼
不同楼之间的职员必须可以两两联系,那么不能两两联系的一定会放在同一栋楼里。所以就是所谓的“补图连通块个数”。
但是显然直接连反图的边会 T 得妈都不认识。我们考虑这样,先每次随便拿出来一个还没有确定的职员,然后看一下剩下还没有确定的职员中哪些没有和他直接连边,就一定会在同一个连通块里,然后一直这么操作就好了。用链表优化这个操作,可以直接 \(O(1)\) 删除,很快啊!总的时间复杂度 \(O(n + m)\)。证明等会儿写。
namespace liuzimingc {
const int N = 1e5 + 5;
#define endl '\n'
int n, m, nxt[N], pre[N], tot, ans[N];
vector<int> e[N];
bool vis[N], flag[N];
queue<int> q;
void del(int p) {
pre[nxt[p]] = pre[p];
nxt[pre[p]] = nxt[p];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
while (m--) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
for (int i = 0; i < n; i++) nxt[i] = i + 1; // nxt[0] / 1?
for (int i = 2; i <= n; i++) pre[i] = i - 1;
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
vis[i] = true;
tot++;
ans[tot]++;
del(i);
q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
for (const int &v : e[u]) flag[v] = true;
for (int j = nxt[0]; j; j = nxt[j])
if (!flag[j]) q.push(j), del(j), vis[j] = true, ans[tot]++;
else flag[j] = false;
}
}
sort(ans + 1, ans + 1 + tot);
cout << tot << endl;
for (int i = 1; i <= tot; i++) cout << ans[i] << " \n"[i == tot];
return 0;
}
} // namespace liuzimingc
内存分配
首先你需要理清题意。(这里我就不理了。)
明确一点,如果有一个原来需要 \(M\) 个内存的进程不满足,要使它满足只可能是在有新的进程结束之后,所以我们的算法流程就很清晰了。
- 把应该结束的进程结束,维护内存相关信息。
- 尝试依次插入之前的空余进程。
- 尝试插入当前进程,失败则加入空闲进程。
循环上述操作。那么这里空闲进程随便用个队列维护就好了,至于维护内存片可以用链表,也可以 set。当然我不喜欢链表,所以 set 我爱你。
注意,
只要在任一时刻,存在长度为 \(M\) 的空闲地址片,系统马上将该进程取出队列,
所以步骤 1 和 2 应该是动态执行的,还有就是每一秒应该结束的进程会在一瞬间全部结束,所以不能结束一个进程就做步骤 2,应该把同一时间的都弄完才行。
代码有点小长但思路应该很清晰。
namespace liuzimingc {
const int N = 1e4 + 5;
#define endl '\n'
#define pii pair<int, int>
int n, t[N], m[N], p[N], cur, tot; // current_time
set<pii> s;
queue<int> rest; // fuck I get stuck
struct node {
int t, l, r;
friend bool operator <(node a, node b) {
return a.t > b.t;
}
};
priority_queue<node> run;
bool add(int i) {
for (const auto &j : s) {
int l = j.first, r = j.second;
if (r - l + 1 >= m[i]) {
s.erase(j);
s.insert(make_pair(l + m[i], r));
run.push((node){ cur + p[i], l, l + m[i] - 1 });
return true;
}
}
return false;
}
void check_rest() {
while (rest.size()) {
int now = rest.front();
if (!add(now)) return;
rest.pop();
}
} // 处理等待的是否 OK,运行完了就 check
void print() {
for (const auto &i : s) cout << "[" << i.first << ", " << i.second << "]";
cout << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
s.insert(make_pair(0, n - 1));
for (int i = 1; ; i++) {
check_rest();
cin >> t[i] >> m[i] >> p[i];
if (!t[i] && !m[i] && !p[i]) break;
while (run.size() && run.top().t <= t[i]) {
node now = run.top(); run.pop();
pii l = make_pair(-114514, -114514), r = make_pair(-114514, -114514), x = make_pair(now.l, now.r);
if (*s.begin() <= x) l = *--s.upper_bound(x);
if (x <= *s.rbegin()) r = *s.upper_bound(x);
bool f1 = (l.second + 1 == x.first), f2 = (r.first == x.second + 1);
if (!f1 && f2) {
s.insert(make_pair(x.first, r.second));
s.erase(r);
}
else if (f1 && !f2) {
s.insert(make_pair(l.first, x.second));
s.erase(l);
}
else if (f1 && f2) {
s.insert(make_pair(l.first, r.second));
s.erase(l);
s.erase(r);
}
else s.insert(x);
cur = now.t;
if (run.empty() || now.t != run.top().t) check_rest();
} // 处理运行完的
cur = t[i];
if (!add(i)) rest.push(i), tot++;
}
check_rest();
while (run.size()) {
node now = run.top(); run.pop();
pii l = make_pair(-114514, -114514), r = make_pair(-114514, -114514), x = make_pair(now.l, now.r);
if (*s.begin() <= x) l = *--s.upper_bound(x);
if (x <= *s.rbegin()) r = *s.upper_bound(x);
bool f1 = (l.second + 1 == x.first), f2 = (r.first == x.second + 1);
if (!f1 && f2) {
s.insert(make_pair(x.first, r.second));
s.erase(r);
}
else if (f1 && !f2) {
s.insert(make_pair(l.first, x.second));
s.erase(l);
}
else if (f1 && f2) {
s.insert(make_pair(l.first, r.second));
s.erase(l);
s.erase(r);
}
else s.insert(x);
cur = now.t;
if (run.empty() || now.t != run.top().t) check_rest();
} // 处理运行完的
cout << cur << endl << tot << endl;
return 0;
}
} // namespace liuzimingc
除去两道黑题,完结撒花。
Posted by liuzimingc