Codeforces Round #773 (Div. 1) A~D 题解
比赛链接
A
开个 map,能选掉就选掉。
#include
#define DC int T = gi (); 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 PII;
typedef pair PLI;
typedef pair PLL;
template
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 p;
inline void solve()
{
n = gi (), x = gi ();
p.clear();
for (int i = 1; i <= n; i+=1) a[i] = gi (), ++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
#define DC int T = gi (); 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 PII;
typedef pair PLI;
typedef pair PLL;
template
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 b;
int pp[N], pre[N];
vector ans;
vector 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 ();
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 ();
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
#define DC int T = gi (); 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 PII;
typedef pair PLI;
typedef pair PLL;
template
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 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 ().swap(s[fx]);
}
}
int main()
{
// freopen(".in", "r", stdin); freopen(".out", "w", stdout);
n = gi (), q = gi ();
iota(fa + 1, fa + 2 + n, 1);
iota(L + 1, L + 2 + n, 1);
while (q--)
{
int op = gi ();
if (!op)
{
int l = gi (), r = gi (), ty = gi ();
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 ();
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
#define DC int T = gi (); 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 PII;
typedef pair PLI;
typedef pair PLL;
template
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 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 (), m = gi ();
for (int i = 1; i <= n; i+=1)
{
for (int j = 0; j < m; j+=1)
a[i].a[j] = gi ();
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 ();
}
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;
}