Codeforces Round #773 (Div. 1) A~D 题解

比赛链接

A

开个 map,能选掉就选掉。

#include <bits/stdc++.h>
#define DC int T = gi <int> (); while (T--)
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define Add(a, b) a = (a + b) % mod
#define Mul(a, b) a = 1ll * a * b % mod
#define chkmin(a, b) a = min(a, b)
#define chkmax(a, b) a = max(a, b)
#define int long long

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int, int> PII;
typedef pair <LL, int> PLI;
typedef pair <LL, LL> PLL;

template <typename T>
inline T gi()
{
	T x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int N = 200003, M = N << 1;

int n, x, a[N];
map <int, int> p;

inline void solve()
{
	n = gi <int> (), x = gi <int> ();
	p.clear();
	for (int i = 1; i <= n; i+=1) a[i] = gi <int> (), ++p[a[i]];
	for (auto &y : p)
		if (y.se)
		{
			int now = y.fi * x;
			int mn = min(p[now], y.se);
			y.se -= mn;
			p[now] -= mn;
		}
	int sum = 0;
	for (auto y : p) sum += y.se;
	cout << sum << endl;
	return;
}

signed main()
{
	//freopen(".in", "r", stdin); freopen(".out", "w", stdout);
	DC solve();
	return !!0;
}

B

阴间构造

对于这种构造,我们肯定是要先找到一种基本操作,然后根据基本操作去构造方案。

不难发现无解的必要条件是存在数字出现了奇数次。下面我们通过构造方案证明其他情况下一定有解。

基本操作:翻转一段区间,即 abc \(\to\) cba

操作如下:

\[\text{abc}\\ \text{abc}\underline{\textbf{aa}}\\ \text{abca}\underline{\textbf{bb}}\text{a}\\ \text{abcab}\underline{\textbf{cc}}\text{ba}\\ \text{[abcabc]cba} \]

从前往后扫描序列,将相邻两个相同字符的位置配对,假设其中一对是 \((i,j)\)

那么我们可以通过依次翻转 \((i,j-1)\)\((i,j)\)\(j\) 移动到 \(i\) 之后且其他字符相对顺序不变。

举个例子:假设序列为 \(\text{abca}\)

操作如下:

\[\text{abca}\\ \text{abc}\underline{\textbf{aa}}\text{a}\\ \text{abca}\underline{\textbf{bb}}\text{aa}\\ \text{abcab}\underline{\textbf{cc}}\text{baa}\\ \text{abcabccbaa}\underline{\textbf{cc}}\\ \text{abcabccbaac}\underline{\textbf{bb}}\text{c}\\ \text{abcabccbaacb}\underline{\textbf{aa}}\text{bc}\\ \text{abcabccbaacba}\underline{\textbf{aa}}\text{abc}\\ \text{[abcabc][cbaacbaa][aa]bc} \]

代码实现(可能?)需要一定的技巧性。

具体的,每做完一次操作就将已经操作过的数移到最前面,对剩余数重新计算匹配位置,顺便记录一下当前移动到的位置。

参考代码:

#include <bits/stdc++.h>
#define DC int T = gi <int> (); while (T--)
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define Add(a, b) a = (a + b) % mod
#define Mul(a, b) a = 1ll * a * b % mod
#define chkmin(a, b) a = min(a, b)
#define chkmax(a, b) a = max(a, b)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int, int> PII;
typedef pair <LL, int> PLI;
typedef pair <LL, LL> PLL;

template <typename T>
inline T gi()
{
	T x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int INF = 0x3f3f3f3f, N = 503, M = N << 1;

int n, now, a[N], c[N], cnt, p[N], t[N];
vector <int> b;
int pp[N], pre[N];
vector <PII> ans;
vector <int> lens;
bool vis[N];

inline void filp(int l, int st, int len, int op)
{
	if (len < 2) return;
	now += len;
	if (!op)
		for (int i = 1; i <= len; i+=1)
			ans.pb({now++, c[a[st + i - 1]]});
	else
	{
		for (int i = len - 1; i >= 1; i-=1)
			ans.pb({now++, c[a[st + i - 1]]});
		ans.pb({now++, c[a[st + len - 1]]});
	}
	lens.pb(len << 1);
}

inline void rebuild()
{
	int qq = 0;
	for (int i = 1; i <= n; i+=1) if (vis[i]) t[++qq] = a[i];
	int tmp = qq;
	for (int i = 1; i <= n; i+=1) if (!vis[i]) t[++qq] = a[i];
	for (int i = 1; i <= n; i+=1) a[i] = t[i], pre[i] = pp[i] = vis[i] = 0;
	for (int i = 1; i <= tmp; i+=1) vis[i] = 1;
	for (int i = tmp + 1; i <= n; i+=1)
		if (!pre[a[i]]) pre[a[i]] = i;
		else pp[pre[a[i]]] = i, pre[a[i]] = 0;
}

inline void solve()
{
	n = gi <int> ();
	b.clear(), ans.clear(), lens.clear();
	for (int i = 1; i <= n; i+=1) pre[i] = p[i] = vis[i] = 0;
	for (int i = 1; i <= n; i+=1) a[i] = c[i] = gi <int> ();
	sort(c + 1, c + 1 + n); cnt = unique(c + 1, c + 1 + n) - c - 1;
	for (int i = 1; i <= n; i+=1) a[i] = lower_bound(c + 1, c + 1 + cnt, a[i]) - c, ++p[a[i]];
	for (int i = 1; i <= n; i+=1) if (p[i] & 1) return (void)puts("-1");
	for (int i = 1; i <= n; i+=1)
		if (!pre[a[i]]) pre[a[i]] = i;
		else pp[pre[a[i]]] = i, pre[a[i]] = 0;
	now = 0;
	for (int i = 1; i <= n; i+=2)
		filp(now, i, pp[i] - i, 0), filp(now, i, pp[i] - i + 1, 1), lens.pb(2), now += 2, vis[i] = vis[pp[i]] = 1, rebuild();
	cout << ans.size() << endl;
	for (auto x : ans) cout << x.fi << ' ' << x.se << endl;
	cout << lens.size() << endl;
	for (auto x : lens) cout << x << ' ';
	puts("");
	return;
}

int main()
{
//	freopen(".in", "r", stdin); freopen(".out", "w", stdout);
	DC solve();
	return 0;
}

C

容易发现如果一个询问的答案是 YES,就相当于有一个区间只有这一个位置没有被覆盖成 \(0\)

经过转化后的题意:

  • 若操作为 0 l r 0,相当于将 \([l,r]\) 全都覆盖成 \(0\)
  • 若操作为 0 l r 1,相当于加入一条线段 \([l,r]\)
  • 若操作为 1 x,算出以 \(x-1\) 为右端点的最长的一个为 \(0\) 的段的左端点 \(l_1\),和以 \(x+1\) 为右端点的最长的一个段为 \(0\) 的右端点 \(r_1\),相当于询问有没有一条线段 \([l,r]\) 使得 \(l_1\le l\le x\land x\le r\le r_1\)

线段树二分之后离线差分变成三维偏序,CDQ 分治即可。

上面那句话也就图一乐。

考虑并查集 + set 启发式合并,每次覆盖就把 \([l-1,r]\) 的集合并起来,加入线段就直接在 \(r\) 所在的集合加入点 \(l\),查询直接看在 \(x\) 所在集合有没有点在 \([l_1,x]\) 之间,\(l_1\) 也可以并查集算。

注意 set 启发式合并的时候直接交换两个 set 即可。

#include <bits/stdc++.h>
#define DC int T = gi <int> (); while (T--)
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define Add(a, b) a = (a + b) % mod
#define Mul(a, b) a = 1ll * a * b % mod
#define chkmin(a, b) a = min(a, b)
#define chkmax(a, b) a = max(a, b)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int, int> PII;
typedef pair <LL, int> PLI;
typedef pair <LL, LL> PLL;

template <typename T>
inline T gi()
{
	T x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int INF = 0x3f3f3f3f, N = 200003, M = N << 1;

int n, q;
int fa[N], L[N];
set <int> s[N];

int getf(int u) {return fa[u] == u ? u : fa[u] = getf(fa[u]);}

inline void Union(int x, int y)
{
	int fx = getf(x), fy = getf(y);
	if (fx != fy)
	{
		fa[fx] = fy;
		chkmin(L[fy], L[fx]);
		if (s[fx].size() > s[fy].size()) swap(s[fx], s[fy]);
		for (auto p : s[fx]) s[fy].insert(p);
		set <int> ().swap(s[fx]);
	}
}

int main()
{
//	freopen(".in", "r", stdin); freopen(".out", "w", stdout);
	n = gi <int> (), q = gi <int> ();
	iota(fa + 1, fa + 2 + n, 1);
	iota(L + 1, L + 2 + n, 1);
	while (q--)
	{
		int op = gi <int> ();
		if (!op)
		{
			int l = gi <int> (), r = gi <int> (), ty = gi <int> ();
			if (!ty)
				for (int i = getf(r); i >= l; )
					Union(i, i - 1), i = getf(i);
			else s[getf(r)].insert(l);
		}
		else
		{
			int u = gi <int> ();
			if (L[getf(u)] != u) puts("NO");
			else
			{
				int fu = L[getf(u - 1)], fv = getf(u);
				auto x = s[fv].lower_bound(fu + 1);
				if (x != s[fv].end() && (*x) <= u) puts("YES");
				else puts("N/A");
			}
		}
	}
	return 0;
}

D

核心:两个序列 \(\{a\}\)\(\{b\}\) 没有相同元素的判断方法:记一个计数器 \(cnt\),对于每一个 \(b\) 的子集,如果它的元素个数是奇数,就将 \(cnt\) 加上它在 \(a\) 中的出现次数,否则就减去它在 \(a\) 中的出现次数。最后如果 \(cnt=1\) 说明 \(a\)\(b\) 至少有一个元素相同,否则它们就没有相同元素。

有这个结论之后对每一个子集哈希,双指针求答案即可。

#include <bits/stdc++.h>
#define DC int T = gi <int> (); while (T--)
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define Add(a, b) a = (a + b) % mod
#define Mul(a, b) a = 1ll * a * b % mod
#define chkmin(a, b) a = min(a, b)
#define chkmax(a, b) a = max(a, b)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int, int> PII;
typedef pair <LL, int> PLI;
typedef pair <LL, LL> PLL;

template <typename T>
inline T gi()
{
	T x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return f * x;
}

const int INF = 0x3f3f3f3f, N = 100003, M = N << 1;
const ULL base = 11451419198101ull;

unordered_map <ULL, int> p;
int n, m;
struct Node
{
	int a[6], w;
	ULL b[33];
} a[N];

inline void modify(int id, int op) {for (int i = 1; i < (1 << m); i+=1) p[a[id].b[i]] += op;}

inline int query(int id)
{
	int now = 0;
	for (int i = 1; i < (1 << m); i+=1)
		if (__builtin_parity(i)) now += p[a[id].b[i]];
		else now -= p[a[id].b[i]];
	return now;
}

int main()
{
//	freopen(".in", "r", stdin); freopen(".out", "w", stdout);
	n = gi <int> (), m = gi <int> ();
	for (int i = 1; i <= n; i+=1)
	{
		for (int j = 0; j < m; j+=1)
			a[i].a[j] = gi <int> ();
		sort(a[i].a, a[i].a + m);
		for (int j = 1; j < (1 << m); j+=1)
			for (int k = 0; k < m; k+=1)
				if (j >> k & 1)
					a[i].b[j] = a[i].b[j] * base + a[i].a[k];
		a[i].w = gi <int> ();
	}
	sort(a + 1, a + 1 + n, [](Node x, Node y) {return x.w < y.w;});
	for (int i = 1; i <= n; i+=1) modify(i, 1);
	int r = n, ans = 2000000007;
	for (int l = 1; l <= n; l+=1)
	{
		bool fl = 0;
		modify(l, -1);
		while (query(l) < r - l) modify(r--, -1), fl = 1;
		if (fl) modify(++r, 1), chkmin(ans, a[l].w + a[r].w);
	}
	if (ans != 2000000007) printf("%d\n", ans);
	else puts("-1");
	return 0;
}
posted @ 2022-02-24 11:48  csxsi  阅读(72)  评论(1编辑  收藏  举报