2022-08-12 11:44阅读: 54评论: 0推荐: 0

[UOJ NOI Round#6 Day1] 题解

trip

Problem

给定一张 n 个点 m 条边的图,边有边权

H 要和 k 个朋友依次在这张图上会面,第 i 个朋友初始位置在 pi,小 H 一开始在 1 号点。

当他和一个或多个朋友处于同一位置时,则视为完成了会面。

H 和他的朋友们每秒钟移动的速度均为 1,他想知道,倘若他和朋友按照一个商量好的策略移动,那么他和所有网友顺次会面最少需要花费多少秒?

注意,小 H 可以在边上折返,也能在边上完成会面。例如,一条长度为 1 的边连接了 xy 两点,小 H 和一个朋友于 t 时刻分别处于点 x 和点 y,小 H 可以花费 0.5s 走到边中间,和朋友完成会面,再花费 0.5s 返回点 x

Scope Limitation

2n105,n1m2×105,1k20,图无重边、自环。

Solution

注意到一个关键点转化:设 f(x)=max{dis(pi,x)}(0ik),特殊的,我们令 p0=1。其中 x 可以是图上任意一点,这个点可以在边上dis(u,v) 表示图上任意两点间的距离。而题目实际上是让我们求 f(x)最小值

证明实际上也相当简单,即考虑和某个朋友相遇后,让该朋友继续跟着小 H 移动,最后所有人一定会移动到同一个点,而我们的答案只和最后一个到达该点的人有关。

我们可以在 O(knlogn) 的时间复杂度内处理出所有人到图上节点的距离,但这样我们仍然不能得出答案,因为最后相聚的点可能在边上。

略作思考,我们发现实际上对于一条两端点分别为 xy 的边,令一些人到达节点 x,再令一些人到达节点 y,分别处理出到达 xy 的人中花费时间的最值,就能简单的算出这两人怎么行动会是最优解。

如果直接枚举所有情况,时间复杂度是 O(2km+knlogn),能够拿到 85 分。

事实上,我们只需对所有人到达点 x 花费的时间排序,让一个前缀走 x,一个后缀走 y 即可。这不难理解,因为我们只需要知道最后到达 xy 的人,前缀走 x,可以确定出最后到达 x 的人,后缀走 y,亦能通过预处理计算出最后到达 y 的人,且我们一定不需要前缀中的某些数,其不会让答案更优。

时间复杂度 O(km+knlogn)

code

#include <bits/stdc++.h>
#define st first
#define nd second
#define mk make_pair
#define pii pair<int, int>
#define int long long
using namespace std;
const int N = 2e5 + 10, INF = 1e15;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct edge{ int v, w; };
struct record{ int u, v, w; }rec[N];
struct node{
	int v, p;
	bool operator < (const node &x) const{ return v > x.v; } 
};
int n, m, k, ans;
int dis[22][N];
bool vis[N];
vector<edge> G[N];
inline void DJ(int st, int id)
{
	priority_queue<node> q;
	memset(vis, false, sizeof(vis));
	memset(dis[id], 127 / 3, sizeof(dis[id]));
	dis[id][st] = 0, q.push((node){0, st});
	while(!q.empty()){
		node u = q.top(); q.pop();
		if(vis[u.p]) continue;
		vis[u.p] = true;
		for(register edge to : G[u.p]){
			if(dis[id][u.p] + to.w < dis[id][to.v]){
				dis[id][to.v] = dis[id][u.p] + to.w;
				q.push((node){dis[id][to.v], to.v});
			}
		}
	}
}
inline void update(int l, int r, int id)
{
	if(l == -1 || r == -1) { ans = min(ans, max(l, r) * 2); return; } //全部聚集在其中一边 
	int c = abs(l - r), z = rec[id].w; //c 表示时间差,z 表示边长 
	ans = min(ans, 2 * max(l, r) + max(0ll, z - c)); //先到的一方先走 c 的距离,之后 z - c 的距离一起走
}
signed main()
{
	n = read(), m = read(), ans = INF;
	for(register int i = 1; i <= m; i++){
		int x = read(), y = read(), z = read();
		G[x].push_back((edge){y, z}), G[y].push_back((edge){x, z});
		rec[i] = (record){x, y, z};
	}
	k = read();
	for(register int i = 1, x; i <= k; i++) x = read(), DJ(x, i);
	DJ(1, k + 1);
	for(register int i = 1; i <= m; i++){
		vector<pii> vec;
		int x = rec[i].u, y = rec[i].v;
		for(register int j = 1; j <= k + 1; j++) vec.push_back(mk(dis[j][x], j));
		sort(vec.begin(), vec.end());
		int mx[22];
		mx[vec.size()] = -1;
		for(register int j = vec.size() - 1, id; j >= 0; j--)
			id = vec[j].nd, mx[j] = max(mx[j + 1], dis[id][y]);
		update(-1, mx[0], i);
		for(register int j = 0; j < (int)vec.size(); j++) update(vec[j].st, mx[j + 1], i);
	}
	printf("%lld\n", ans);
	return 0;
}

show

Problem

有一个长度为 n01S,你需要计算 t 次操作后能得到多少不同的 01 串。

一次操作定义为:在串中选择两个位置插入一对 01 使得 01 前。

Scope Limitation

1n,t300

Solution

考虑状态压缩。

首先,进行一个转化,注意到插入依次匹配且 0 必须在 1 之前十分像括号序列,将 0 视为左括号,将 1 视为右括号。

设最后的零一串为 Tfi,j 表示对于 T 长度为 i 的前缀,是否能通过 S 长度为 j 的前缀加上若干个通过操作插入的 01 得到。

fi,j 的转移如下:

  • Ti+1=Sj+1:fi,jfi+1,j+1
  • ai+1bi:fi,jfi+1,j

第一个转移是简单的,即 TS 直接相匹配。

第二个转移相当于在这个位置插入了一个与 S 不匹配的括号,我们令 aibi 分别表示 TS 作为括号序列的权值,即若 Ti 是左括号,有 ai=ai1+1,否则,ai=ai11bi 则类似。限制条件实际上即是满足了去除 S 原有的括号,我们插入的括号必须是一个能够完全匹配的括号序列。

明白了 fi,j 的转移后,考虑设计状态。

dpi,j,V 表示 T 填了 i 位后,ai=j,且当前状态与 S 的匹配度压缩为 V 的方案数。其中,V 是一个二进制数,第 k 位表示 fi,k 的值。

最后按照上述转移计算,最后状态中 Vn 位为 1j=bn 计入答案。

倘若直接转移,我们的状态数大概是 O(2n(n+2×t)2),忽略一些无用状态,再进行滚动,同时用 map 记录每个状态的值,能够通过 45 分的部分。

考虑优化我们的状态。

我们发现了这样一个事实,即对于 V,他对应的下两个状态的 V 的最高位事实上只和当前 V 的最高位有关,这是显然的,于是我们只需要记录 V 的最高位。

但这样存在一个问题,我们现在记录的状态中,省略了低位,但是事实上,我们的最高位有可能无法向后更新,而低位却能。简单来说,还是 ai+1bi 的问题,我们能够插入一些左括号,不断叠高 a 的值。

对此,设计一个反悔操作。

S 上预处理一个 prei,记录最大的 j 满足 j<ibj<bi

这样做有什么好处?细想我们的转移,很显然能够转移到当前状态,一定是满足 aibi 的,而若不能转移,只存在一种情况,就是 ai=bi,且我们想在 i+1 这个位置填入一个右括号。这时,我们只需要让 S[prei+1,i] 这段区间由操作插入负责,就可以降低 bi,从而实现目标。

同时 [prei+1,i] 一定是可以由插入负责的,因为按照上述定义,若 prei 存在,这一段区间就是一段能够完全匹配的括号序列,或者单个左括号。

这样,状态的数量的极值就变成了 O(n(n+2×t)2),这一定是跑不满的,足以通过此题。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3, M = 3e2 + 10, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
//括号序列的值、f 的压缩、该状态对应的数量
struct Status{ int a, v; };
char s[N];
int n, t, opt, cnt;
int b[N], pre[N], dp[2][N << 1][M];
bool vis[2][N << 1][M];
vector<Status> stu[2];
signed main()
{
	n = read(), t = read(), cin >> (s + 1), b[0] = 910;
	for(register int i = 1; i <= n; i++){
		if(s[i] == '0') b[i] = b[i - 1] + 1;
		else b[i] = b[i - 1] - 1;
	}
	for(register int i = 0; i <= n; i++){
		pre[i] = -1;
		for(register int j = i - 1; j >= 0; j--)
			if(b[j] < b[i]) { pre[i] = j; break; }
	}
	dp[opt ^ 1][910][0] = 1, stu[opt ^ 1].push_back((Status){910, 0});
	t = n + 2 * t;
	while(t--){
		//清空当前状态
		for(register Status u : stu[opt]) vis[opt][u.a][u.v] = false, dp[opt][u.a][u.v] = 0;
		stu[opt].clear();
		for(register Status u : stu[opt ^ 1]){ //枚举上一轮状态 
			Status to;
			//这一轮是左括号
			to.a = u.a + 1, to.v = -1;
			if(s[u.v + 1] == '0'){
				to.v = u.v + 1;
				(dp[opt][to.a][to.v] += dp[opt ^ 1][u.a][u.v]) %= mod;
				if(!vis[opt][to.a][to.v]) stu[opt].push_back(to), vis[opt][to.a][to.v] = true;
			}
			else{
				if(to.a >= b[u.v]){
					to.v = u.v;
					(dp[opt][to.a][to.v] += dp[opt ^ 1][u.a][u.v]) %= mod;
					if(!vis[opt][to.a][to.v]) stu[opt].push_back(to), vis[opt][to.a][to.v] = true;
				}
			}
			//若这一轮是右括号 
			to.a = u.a - 1, to.v = -1;
			if(s[u.v + 1] == '1'){
				to.v = u.v + 1;
				(dp[opt][to.a][to.v] += dp[opt ^ 1][u.a][u.v]) %= mod;
				if(!vis[opt][to.a][to.v]) stu[opt].push_back(to), vis[opt][to.a][to.v] = true;
			}
			else{
				if(to.a >= b[u.v]){
					to.v = u.v;
					(dp[opt][to.a][to.v] += dp[opt ^ 1][u.a][u.v]) %= mod;
					if(!vis[opt][to.a][to.v]) stu[opt].push_back(to), vis[opt][to.a][to.v] = true;
				}
				else{
					if(pre[u.v] != -1){
						(dp[opt][to.a][pre[u.v]] += dp[opt ^ 1][u.a][u.v]) %= mod;
						if(!vis[opt][to.a][pre[u.v]]) stu[opt].push_back((Status){to.a, pre[u.v]}), vis[opt][to.a][pre[u.v]] = true; 
					}
				}
			}
		}
		opt ^= 1, cnt++;
	}
	printf("%lld\n", dp[opt ^ 1][b[n]][n]);
	return 0;	
}
/*
2 1
00
*/

game

Problem

给定一个依次排列长度为 n 的序列,两个人将在这个序列上做一个游戏:

  • 两人依次选择序列里的数,不能选择已经被自己或对方选走的数,小 B 先手。
  • A 每次只会选择剩下的数中编号最小的那个,小 B 会自己制定策略。

给定 q 次询问,每次询问给出两个数 lr,问在区间 [l,r] 上进行这个游戏,小 B 选取的数和最大是多少?

Scope Limitation

1n,q2×105

Solution

依次考虑每个数,我们发现,小 B 的选取有这样一个限制,对于前 i 个数,他最多选取 i2 个。

设计状态 fi,j 表示前 i 个数选取了 j 个的答案,直接转移即可,时间复杂度 O(n2q),期望得分 40 分。

仔细想了想这个限制,发现我们向后面加数,并不会影响到前面没有被选择的数,也就是说前面没有被选择的数依然不会被选择,也不会增加选取前面的数的机会。但他有可能可以替换掉被选取的数,因为它在更靠后的位置,这是十分显然的。

于是我们得到了一个反悔贪心,用小根堆维护,每次小根堆内的数小于可以选取的数,就选;若不能直接选,比较堆顶和当前数即可。

时间复杂度 O(qnlogn),期望得分依旧是 40 分。

对于这种区间的多次询问,我们很自然的想到了莫队。

但是莫队有一个问题,向后加数当然是容易的,但是如何向前加数以及如何删除成了我们难以解决的问题。

向前加数能够相当于将我们上面的过程倒了过来,用一个大根堆维护没有选取的数,每向前加入一个数,如果可以选择,就选大根堆堆顶。但这样显然是错误的,有可能会出现连续选择首位两个数的情况,对于这种情况,我们很难有效地进行处理。

仔细思考发现,倘若只向前加入一个数,是十分难维护的。我们换一种思维,每次向前加入两个数,同时将它们插入到大根堆,然后直接取堆顶,就避免了这种情况。

至于删除,我们选择逃避,利用回滚莫队即可。

当由于向前操作只能偶数倍的进行,可能会和某些回答要求的范围错开,于是我们需要做两次回滚莫队,每次调节一下左端点的奇偶性。同时由于回滚需要删除,优先队列不支持删除操作,所以需要用到 set,常数略大。

时间复杂度 O(nnlogn),期望得分 70 分。

如果将 set 换成压位 trie,时间复杂度将会优化为 O(nnlogwn)O(nnloglogn),期望得分 70100

我们继续优化上述解法, 考虑分治。

对于区间 [l,mid],用主席树维护每个后缀选取了哪些数,对区间 [mid+1,r],用主席树维护每个前缀没有选哪些数,接下来考虑将两个区间合并。

合并区间 [x,mid][mid+1,y] 时,设区间 [x,mid] 中选了的数变为没有选择的有 a 个,区间 [mid+1,y] 中选了的数变为选了的有 b 个,显然 a=b,即将两个区间内的数状态做了次替换。考虑二分,二分出一个最大的 k 满足 [x,mid] 中选取的数里面第 k 小的数小于 [mid+1,y] 中没有选取的数里面第 k 大的数。

复杂度 O((n+q)log2n)

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, M = 42, INF = 1e15;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct Query{ int l, r, id; }que[N];
int n, Q, lim;
int a[N], t[N], ans[N];
int tot, pre[N], suf[N], rt1[N], rt2[N];
int ls[N * M], rs[N * M], siz[N * M], sum[N * M];
vector<Query> vec[N];
inline void update(int &rt, int l, int r, int x, int c)
{
	int u = ++tot;
	ls[u] = ls[rt], rs[u] = rs[rt];
	siz[u] = siz[rt] + c, sum[u] = sum[rt] + t[x] * c, rt = u;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(x <= mid) update(ls[rt], l, mid, x, c);
	else update(rs[rt], mid + 1, r, x, c);
}
inline int Search(int k, int l, int r, int x)
{
	if(l == r) return l;
	int mid = (l + r) >> 1;
	if(x <= siz[ls[k]]) return Search(ls[k], l, mid, x);
	else return Search(rs[k], mid + 1, r, x - siz[ls[k]]);
}
inline int ask(int k, int l, int r, int x)
{
	if(l == r) return x * t[l];
	int mid = (l + r) >> 1;
	if(x <= siz[ls[k]]) return ask(ls[k], l, mid, x);
	else return ask(rs[k], mid + 1, r, x - siz[ls[k]]) + sum[ls[k]];
}
inline int Sol(int l, int r) //反悔贪心 
{
	int res = 0;
	priority_queue<int, vector<int>, greater<int> > q;
	for(register int i = l; i <= r; i++){
		if((int)q.size() < (i - l + 2) / 2) res += t[a[i]], q.push(t[a[i]]);
		else if(q.top() < t[a[i]]) res -= q.top(), res += t[a[i]], q.pop(), q.push(t[a[i]]); 
	}
	return res;
}
inline void Binary(int l, int r, vector<Query> &Qy)
{
	if(Qy.empty()) return;
	int mid = (l + r) >> 1;
	vector<Query> Ls, Rs; //左右儿子
	for(register int i = l; i <= r; i++) vec[i].clear();
	for(register Query it : Qy){
		if(it.r <= mid) Ls.push_back(it);
		else if(it.l > mid) Rs.push_back(it);
		else vec[it.l].push_back(it);
	}
	for(register int opt = 0; opt < 2; opt++){ //调节奇偶性
		int p = mid + opt;
		rt1[p] = rt2[p + 1] = 0, pre[p] = suf[p + 1] = 0, tot = 0;
		int L = INF, R = 0;
		for(register int i = p - 1; i >= l; i -= 2)
			for(register Query it : vec[i]) L = it.l, R = max(R, it.r);
		priority_queue<int> ded;
		priority_queue<int, vector<int>, greater<int>> hav;
		for(register int i = p + 1; i <= R; i++){
			rt1[i] = rt1[i - 1], pre[i] = pre[i - 1];
			if(!((i - p) % 2)){ //偶数形扩展,一次扩展两个 
				int x = i, y = i - 1;
				if(a[x] < a[y]) swap(x, y);
				pre[i] += t[a[x]], hav.push(a[x]);
				if(!hav.empty() && a[y] > hav.top())
					pre[i] -= t[hav.top()], pre[i] += t[a[y]], update(rt1[i], 1, lim, hav.top(), 1), hav.pop(), hav.push(a[y]);
				else update(rt1[i], 1, lim, a[y], 1); //维护一个没选的前缀 
			}
		}
		for(register int i = p; i >= L; i--){ //维护一个选了的后缀 
			rt2[i] = rt2[i + 1], suf[i] = suf[i + 1], ded.push(a[i]);
			if(!((p - i + 1) % 2)) update(rt2[i], 1, lim, ded.top(), 1), suf[i] += t[ded.top()], ded.pop();
		}
		for(register int i = p - 1; i >= L; i -= 2){
			for(register Query it : vec[i]){
				int x = 0, y = min((p - i + 1) / 2, (it.r - p) / 2), tem = suf[i] + pre[it.r];
				while(x < y){
					int amo = (x + y + 1) >> 1;
					if(Search(rt1[it.r], 1, lim, (it.r - p) / 2 - amo + 1) >= Search(rt2[i], 1, lim, amo)) x = amo;
					else y = amo - 1;
				}
				if(x){
					tem += sum[rt1[it.r]] - ask(rt2[i], 1, lim, x);
					if(x != (it.r - p) / 2) tem -= ask(rt1[it.r], 1, lim, (it.r - p) / 2 - x);
				}
				ans[it.id] += tem;
			}
		}
	}
	Binary(l, mid, Ls), Binary(mid + 1, r, Rs);
}
signed main()
{
	n = read(), Q = read();
	for(register int i = 1; i <= n; i++) a[i] = read(), t[i] = a[i];
	sort(t + 1, t + n + 1), lim = unique(t + 1, t + n + 1) - t - 1;
	for(register int i = 1; i <= n; i++) a[i] = lower_bound(t + 1, t + lim + 1, a[i]) - t;
	vector<Query> Qy;
	for(register int i = 1, l, r; i <= Q; i++){
		l = read(), r = read();
		if((r - l + 1) & 1) ans[i] += t[a[r]], r--;
		if(l > r) continue;
		if(r - l + 1 <= 20) ans[i] += Sol(l, r);
		else Qy.push_back((Query){l, r, i});
	}
	Binary(1, n, Qy);
	for(register int i = 1; i <= Q; i++) printf("%lld\n", ans[i]);
	return 0;
}

本文作者:╰⋛⋋⊱๑落叶๑⊰⋌⋚╯

本文链接:https://www.cnblogs.com/Defoliation-ldlh/p/16579347.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(54)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起