Loading

:D 获取中...

Solution Set - 训练计划 链表

咕掉了两道不可做题(指黑色)。

梦幻布丁

Resource: HNOI2009

放在链表的题单里,和链表有什么关系呢???

因为都是在对颜色整体进行操作,我们可以根据颜色拉出来对应的链表。

那么每次合并就相当于把一个链表接到另一个链表上去,暴力修改,那么是 \(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

生日礼物

Resource: POJ Challenge

首先的话,我们先贪心地考虑全选连续的为正的数字。假如说这个段数 \(\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

办公楼

Resource: POI2007

不同楼之间的职员必须可以两两联系,那么不能两两联系的一定会放在同一栋楼里。所以就是所谓的“补图连通块个数”。

但是显然直接连反图的边会 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

内存分配

Resource: NOI1999

首先你需要理清题意。(这里我就不理了。)

明确一点,如果有一个原来需要 \(M\) 个内存的进程不满足,要使它满足只可能是在有新的进程结束之后,所以我们的算法流程就很清晰了。

  1. 把应该结束的进程结束,维护内存相关信息。
  2. 尝试依次插入之前的空余进程。
  3. 尝试插入当前进程,失败则加入空闲进程。

循环上述操作。那么这里空闲进程随便用个队列维护就好了,至于维护内存片可以用链表,也可以 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 @ 2024-01-31 22:15  liuzimingc  阅读(7)  评论(0编辑  收藏  举报