Atcoder ABC308E~Ex

E - MEX

简单的统计 - AC

各种M => 各种ME => 各种MEX => ANS.

刚开始把题目看错了,以为无序

int get(char ch) {
	if (ch == 'M') return 0;
	if (ch == 'E') return 1;
	return 2;
}
 
int mex(int i, int j, int k) {
	ll res = 0;
	if (!i || !j || !k) {
		++res;
		if (i == 1 || j == 1 || k == 1) {
			++res;
			if (i == 2 || j == 2 || k == 2) {
				++res;
			}
		}
	}
	return res;
}
 
int main() {
	setIO("");
	
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	cin >> str;
	
	for (int i = 1; i <= n; ++i) {
		int k = get(str[i - 1]);
		if (!k) {
			++c[0][a[i]];
		}
		else if (k == 1) {
			for (int j = 0; j < 3; ++j) {
				c[1][a[i] * 3 + j] += c[0][j];
			}
		}
		else {
			for (int j = 0; j < 9; ++j) {
				c[2][a[i] * 9 + j] += c[1][j];
			}
		}
	}
	
	for (int i = 0; i < 27; ++i) {
		int z = i;
		int x = z / 9; z %= 9;
		int y = z / 3; z %= 3;
		ans += c[2][i] * mex(x, y, z);
	}
	cout << ans << '\n';
	
	return 0;
}

考虑E (补)

对于某个E,在MEX中。所以预处理前i个数中各M个数,后i个数中个X个数,类似于合唱队型

F - Vouchers

贪心 - AC

从大到小排序,能用的大票一定会用。

struct Node {
	ll l, d;
	bool operator < (const Node &x) const {
		return d > x.d;
	}
} a[MAXM];
 
int n, m;
ll ans;
multiset<ll> s;
 
int main() {
	setIO("");
	
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		ll p; cin >> p; ans += p;
		s.insert(p);
	} 
	for (int i = 1; i <= m; ++i) {
		cin >> a[i].l;
	}
	for (int i = 1; i <= m; ++i) {
		cin >> a[i].d;
	}
	sort(a + 1, a + m + 1);
	
	for (int i = 1; i <= m; ++i) {
		auto it = s.lower_bound(a[i].l);
		if (it != s.end()) {
			s.erase(it);
			ans -= a[i].d;
		}
	}
	cout << ans << '\n';
	
	return 0;
}

G - Minimum Xor Pair Query

Trie

尝试01Trie.
插入和删除解决。
查询,在Trie上走,如果从当前节点u到下一个节点v,从v往下还有至少两个数,那么为了让异或值最小,一定不会让数对中的两个数从u点分岔。
当u到v1,v2,v1,v2往下走分别只有一个数,那么只能分岔,并且此时结果一定。
画成树的形式,可以看到,两个数一定是“相邻”的两个叶子节点。

但是,两个数不分岔时,依旧有两种情况:一起走1、一起走2.
也许插入和删除时可以树形dp维护答案:两个数的异或只能在分岔口处实现,所以针对每个节点,记录它往下走的一个数(实际上可能有多个数,但对答案有贡献时,一定只有一个数),应该可做,但我没有写出来。
如果没有删除操作,也很好做:插入一个数之前,带它走一遍Trie,得到一个可能的结果,与答案打擂台。但是要删除一个数时,如果正好删除了最小值,那么跟上来的会是什么,不得而知。

STL multiset - AC (补)

画成树的形式,可以看到,两个数一定是“相邻”的两个叶子节点。

这也是在说,黑板上的数从小到大排序,数对中的两个数一定相邻
这意味着Trie已经失去了它的优越性:给定x,找到与x配对的数y.

但是要删除一个数时,如果正好删除了最小值,那么跟上来的会是什么,不得而知。

将可能的结果放到STL multiset中就好。当时想到了这一点,但觉得太不可能。

int q;
multiset<int> s, res;
 
void del(int x) {
	auto it = res.find(x);
	res.erase(it);
}
 
int main() {
	setIO("");
	
	cin >> q;
	s.insert(-1); s.insert(MAXX);
	while (q--) {
		int op, x; cin >> op;
		if (op == 1) {
			cin >> x;
			auto it = s.insert(x);
			auto pre = prev(it), nxt = next(it);
			if (*pre > -1 && *pre < MAXX) res.insert(*pre ^ x);
			if (*nxt > -1 && *nxt < MAXX) res.insert(*nxt ^ x);
			if (*pre > -1 && *pre < MAXX && *nxt > -1 && *nxt < MAXX) res.erase(res.find(*pre ^ *nxt));
		}
		else if (op == 2) {
			cin >> x;
			auto it = s.find(x);
			auto pre = prev(it), nxt = next(it);
			if (*pre > -1 && *pre < MAXX) res.erase(res.find(*pre ^ x));//erase(value)会删除所有value.
			if (*nxt > -1 && *nxt < MAXX) res.erase(res.find(*nxt ^ x));
			if (*pre > -1 && *pre < MAXX && *nxt > -1 && *nxt < MAXX) res.insert(*pre ^ *nxt);
			s.erase(it);
		}
		else {
			cout << *res.begin() << '\n';
		}
	}
	
	return 0;
}

Ex - Make Q

最小生成树

在树上加一条边可以构成环。又要cost最小,想到可能要在最小生成树的基础上。
但是此处Q不在意节点个数,只在意边权大小;最小生成树则需要顾及所有节点。
举例发现最小生成树确实不太适合本题。

枚举

N ≤ 300 N\leq 300 N300,支持 O ( N 3 ) O(N^3) O(N3),可能要先枚举某个东西(有点假期计划的感觉。枚举边吗?枚举点吗?思路往后就乱了,迷糊了,没有想出来。

要找出所有环吗?不可能,环的个数可达到 2 n 2^n 2n的数量级.

枚举、最短路径树 - (补)

边太多,枚举点。枚举最特殊的、最有性质的那个点:环上的、与Q的尾巴相连的点,记为a。
对于环的寻找,枚举环上相邻的两点b-c,即环为a----------b-c---------a,还需要知道a到b、a到c的最短距离。在剩下的不在环上的与a相邻的点中找一个与a距离最短的点d,构成尾巴。
dijkstra求最短路时,会构成一棵树,保证从根到节点的路径恰是一条最短路径。利用这棵树,可以找出点d,它是根的直接子节点之一(易证,注意与a相邻的点在树中不一定是a的子节点)。这棵树被称作“最短路径树”。
时间复杂度 O ( n 3 ) O(n^3) O(n3).

写不对,不知道为什么。先放着。

int n, m, g[MAXN][MAXN], dis[MAXN], top[MAXN], ans;
bool vis[MAXN];

void dijkstra(int beg) {
	memset(vis, 0, sizeof(vis)); vis[beg] = true;
	memset(dis, 0x3f, sizeof(dis)); dis[beg] = 0;
	top[beg] = 0;
	for (int i = 1; i <= n; ++i) {
		if (g[beg][i]) dis[i] = g[beg][i], top[i] = i;
	}
	for (int i = 1; i < n; ++i) {
		int pos = -1;
		for (int j = 1; j <= n; ++j) {
			if (!vis[j] && (pos == -1 || dis[j] < dis[pos])) pos = j;
		}
		vis[pos] = true;
		for (int j = 1; j <= n; ++j) {
			if (g[pos][j] && ckmin(dis[j], dis[pos] + g[pos][j])) top[j] = top[pos];
		}
	}
}

int main() {
	setIO("");
	
	cin >> n >> m;
	for (int i = 1; i <= m; ++i) {
		int a, b, c; cin >> a >> b >> c;
		g[a][b] = g[b][a] = c;
	}
	
	ans = INF;
	for (int i = 1; i <= n; ++i) {
		dijkstra(i);
		int m[3] = { INF, INF, INF };
		for (int j = 1; j <= n; ++j) {
			if (j != i && top[j] == j) {
				if (m[0] >= g[i][j]) m[2] = m[1], m[1] = m[0], m[0] = g[i][j];
				else if (m[1] >= g[i][j]) m[2] = m[1], m[1] = g[i][j];
				else if (m[2] > g[i][j]) m[2] = g[i][j];
			}
		}
		for (int j = 1; j <= n; ++j) {
			if (j == i) continue;
			for (int k = j + 1; k <= n; ++k) {
				if (k != i && top[j] != top[k] && g[j][k]) {
					int x = g[i][top[j]], y = g[i][top[k]], tmp = m[0];
					if (m[0] == x && m[1] == y || m[0] == y && m[1] == x) tmp = m[2];
					else if (m[0] == x || m[0] == y) tmp = m[1];
					ckmin(ans, dis[j] + dis[k] + g[j][k] + tmp);
				}
			}
		}
	}
	if (ans < INF) cout << ans << '\n';
	else cout << -1 << '\n';
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值