标签:最优 三角形 lin 直接 top 最大数 strlen cout scanf
构造题好评,虽然这把崩了
二人游戏,一个人有 \(k_1\) 张牌,另一个人 \(k_2\) 张,满足 \(2\le k_1+k_2=n\le 100\),每张牌上有一个数,保证所有的牌上的数互不相同且在 \([1,n]\) 内
每回合双方都会出一张牌,牌上数小的一方的牌会给牌上数大的一方
拿到所有 \(n\) 张牌的一方赢得比赛
求两人都采取最优策略的情况下谁会赢
多组数据,数据组数 \(t\le100\)
设 \(a\) 和 \(b\) 为双方的最大数
显然如果 \(a>b\) 那么 A 必然每次都出最大的牌,这样可以必胜,反过来也是一样
于是判断 \(a\) 与 \(b\) 的大小关系即可,\(O(tn)\)
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 105;
int n, m1, m2, a[N], b[N];
void work()
{
read(n); read(m1); read(m2);
for (int i = 1; i <= m1; i++) read(a[i]);
for (int i = 1; i <= m2; i++) read(b[i]);
std::sort(a + 1, a + m1 + 1); std::sort(b + 1, b + m2 + 1);
puts(a[m1] > b[m2] ? "YES" : "NO");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
给定一个 \(n\) 个数的非负整数序列 \(a\),求 \(a\) 的一个子区间使得区间内数的 \(\max\) 和 \(\min\) 之差不小于区间长度,或者告知无解
多组数据,\(\sum n\le2\times10^5\),\(0\le a_i\le10^9\)
先判断所有的长度为 \(2\) 的子区间
可以发现如果所有长度为 \(2\) 的子区间的极差都 \(\le 1\) 则一定无解,因为显然 \(n\) 个数的,相邻数之差绝对值不超过 \(1\) 的序列极差的最大值只能是 \(n-1\)(序列单调时取得最大值)
\(O(\sum n)\)
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Abs(const T &a) {return a < 0 ? -a : a;}
const int N = 2e5 + 5;
int n, a[N];
void work()
{
read(n);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i < n; i++) if (Abs(a[i] - a[i + 1]) >= 2)
return (void) printf("YES\n%d %d\n", i, i + 1);
puts("NO");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
给定一个 \(n\) 个数的非负整数序列 \(a\)
给出一种方案,加入不超过 \(3\) 个 \([0,10^{18}]\) 内的数,使得所有数的和是所有数异或和的 \(2\) 倍
可以证明一定存在解
多组数据,\(\sum n\le10^5\),\(0\le a_i\le10^9\)
做法有很多种,这里给出一个加入 \(2\) 个数的方案
设所有数的异或和为 \(x\),所有数的和为 \(s\),则加入的 \(2\) 个数分别为 \(x\) 和 \(x+s\)
这样所有数的异或和为 \(x\bigoplus x\bigoplus (x+s)=x+s\)
所有数的和为 \(s+x+(x+s)=2(x+s)\)
条件得到满足。\(O(\sum n)\)
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 1e5 + 5;
int n, a[N];
void work()
{
ll sum = 0; int xo = 0;
read(n);
for (int i = 1; i <= n; i++) read(a[i]), sum += a[i], xo ^= a[i];
puts("2");
printf("%d %lld\n", xo, sum + xo);
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
交互题
有一个 \(k\) 以及一个隐藏的数 \(m\),还有一个 \(n\) 以及一个隐藏的 \(n\) 元序列,保证所有的数互不相同
每次可以询问 \(k\) 个下标,得出序列这些下标上的数中,第 \(m\) 小的数对应的下标
你需要用不超过 \(n\) 次操作询问出 \(m\)
\(1\le m\le k<n\le500\),序列的元素在 \([0,10^9]\) 范围内
认真分析之后发现 \(n\) 是没有用的,只有 \(k<n\) 是有用的,于是我们可以把 \(n\) 当成 \(k+1\)
这样不难发现合法的询问只有 \(k+1\) 种
接下去应该也有很多种做法,这里只说一种
先询问 \(1\dots k\) ,设第 \(m\) 小的数对应下标为 \(x\) ,然后把 \(1\dots k\) 中的 \(x\) 去掉换成 \(k+1\),可以得到 \(x\) 和 \(k+1\) 的大小关系(利用两次询问出的第 \(m\) 小的数值进行判断)
然后依次对于每个 \(i\in[1,k]-\{x\}\),询问 \([1,k]-\{i\}+\{k+1\}\),同样可以得出 \(i\) 和 \(x\) 的大小关系
这样就能得到 \(m\) 的值了,操作次数为 \(k+1\),复杂度 \(O(k^2)\)
#include <bits/stdc++.h>
const int N = 505;
int n, k, pos, val, ans = 1;
bool com;
int main()
{
int x, y;
scanf("%d%d", &n, &k);
printf("? ");
for (int i = 1; i <= k; i++) printf("%d ", i);
puts(""); fflush(stdout);
scanf("%d%d", &pos, &val);
printf("? ");
for (int i = 1; i <= k; i++) if (i != pos) printf("%d ", i);
printf("%d\n", k + 1); fflush(stdout);
scanf("%d%d", &x, &y); com = y < val;
for (int i = 1; i <= k; i++) if (i != pos)
{
printf("? ");
for (int j = 1; j <= k; j++) if (j != i) printf("%d ", j);
printf("%d\n", k + 1); fflush(stdout);
scanf("%d%d", &x, &y);
if ((x != pos) ^ com) ans++;
}
return printf("! %d\n", ans), 0;
}
平面上给定 \(n\) 个互不相同的点,坐标是绝对值不超过 \(10^6\) 的整数
求一种把 \(n\) 个点划分成两个非空集合的方案,使得不存在四个点(可以相同)\(i,j,k,l\) 满足 \(i,j\) 属于同一个集合,\(k,l\) 不属于同一个集合,且 \(i\) 到 \(j\) 的欧几里得距离与 \(k\) 到 \(l\) 的欧几里得距离相等
可以证明一定存在解
\(2\le n\le10^3\)
首先发现正三角形无解
从而得出坐标为整数是有用的
看到两个集合又容易想到奇偶性
考虑能否有一种方案,对于两个点 \(i,j\) ,如果距离的平方为偶数则在同一个集合内,否则不在同一个集合内
这启发我们考虑模 \(2\) 意义下的加法。不难发现把两维坐标之和为奇数的放一个集合,偶数的放另一个集合可以满足条件
如果两维坐标之和的奇偶性全相同怎么办?
发现在这种情况下,把坐标系旋转 \(45\) 度并把坐标压缩到 \(\frac 1{\sqrt 2}\) 之后,所有的点坐标仍然是整数
于是这样处理之后继续做
每旋转一次之后任意两点之间的距离的平方都会除以 \(2\),所以复杂度 \(O(n\log d)\),\(d\) 为任意两点之间最小距离
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 1005;
int n, m, X[N], Y[N];
int main()
{
read(n);
for (int i = 1; i <= n; i++) read(X[i]), read(Y[i]);
while (1)
{
int c0 = 0, c1 = 0;
for (int i = 1; i <= n; i++)
(X[i] + Y[i] & 1 ? c1 : c0)++;
if (c0 && c1)
{
std::cout << c1 << std::endl;
for (int i = 1; i <= n; i++) if (X[i] + Y[i] & 1)
printf("%d ", i);
return puts(""), 0;
}
for (int i = 1; i <= n; i++)
{
int x = X[i] + Y[i], y = X[i] - Y[i];
X[i] = x - (c1 > 0) >> 1; Y[i] = y - (c1 > 0) >> 1;
}
}
return 0;
}
给定一个 \(01\) 序列 \(s\)
求有多少个子区间满足其包含至少一个 \(1\),并且 \(1\) 的个数是区间长度的约数
\(1\le |s|\le 2\times10^5\)
时限 8s
这题没开感觉十分亏
第一部分:如何求 \(1\) 的个数不超过 \(S\) 的合法子区间数
先枚举右端点,再枚举 \(1\le i\le S\) 表示 \(1\) 的个数
显然右端点固定下来之后,满足区间内 \(1\) 的个数恰好为 \(i\) 的子区间的左端点是连续的一段
于是这时产生的区间内 \(1\) 的个数恰好为 \(i\) 的子区间的长度也是连续的一段,设其为 \([u,v]\)
那么要计入答案的就是 \([u,v]\) 内 \(i\) 的倍数的个数,即 \(\lfloor\frac vi\rfloor-\lfloor\frac{u-1}i\rfloor\)
\(O(nS)\)
第二部分:如何求 \(\frac{len}{cnt}\) 不超过 \(S\) 的合法子区间数,\(len\) 为区间长度,\(cnt\) 为 \(1\) 的个数
还是枚举 \(1\le\frac{len}{cnt}=i\le S\)
考虑先给每个数一个权值 \(a_x=1-[s_x=1]\times i\)
易得一个子区间的 \(\frac{len}{cnt}=i\) 当且仅当该区间的 \(a\) 之和为 \(0\)
于是求出 \(a\) 有多少对相同的前缀和即可
\(O(nS\log S)\)(sort
)或 \(O(nS)\)(利用上哈希或 std::unordered_map
)
考虑把这两部分暴力结合起来
易得对于一个合法区间,其 \(cnt\) 和 \(\frac{len}{cnt}\) 不可能同时超过 \(\sqrt n\)
于是取 \(S=\lfloor\sqrt n\rfloor\),跑一遍第二种暴力
然后再跑第一种暴力,注意直接算则会把 \(cnt\) 和 \(\frac{len}{cnt}\) 都不超过 \(S\) 的子区间算重,需要限制长度不小于 \((i+1)S\)
\(O(n\sqrt n)\)
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e5 + 5;
int n, S, a[N], top, stk[N];
ll ans, sum[N];
char s[N];
int main()
{
scanf("%s", s + 1); n = strlen(s + 1);
S = sqrt(n);
for (int d = 1; d <= S; d++)
{
for (int i = 1; i <= n; i++) a[i] = 1 - (s[i] == '1' ? d : 0);
sum[0] = 0;
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
std::sort(sum, sum + n + 1);
for (int i = 0; i <= n;)
{
int j = i;
while (j <= n && sum[i] == sum[j]) j++;
ans += 1ll * (j - i) * (j - i - 1) >> 1;
i = j;
}
}
for (int i = 1; i <= n; i++)
{
if (s[i] == '1') stk[++top] = i;
for (int j = 1; j <= S && j <= top; j++)
{
int l = i - stk[top - j + 1] + 1, r = i - stk[top - j];
if (l < j * (S + 1)) l = j * (S + 1);
if (l > r) continue;
ans += r / j - (l - 1) / j;
}
}
return std::cout << ans << std::endl, 0;
}
给定 \(n\) 个整数 \(a_{1\dots n}\),对于每个 \(i\) 都满足 \(i-n\le a_i\le i-1\)
求这 \(n\) 个整数中选出一个非空子集使得和为 \(0\)
\(1\le n\le 10^6\)
妙啊!!!\(\times 1\)(不过听说随机乱搞能过???)
首先 \(i-n\le a_i\le i-1\) 相当于 \(1\le i-a_i\le n\)
考虑建立图论模型:\(i\) 向 \(i-a_i\) 连一条边,显然这是一个基环树森林
考虑一个环,易得如果选出的子集为这个环上的点,环上每条边的贡献都是入点与出点的编号差,于是和为 \(0\)
于是随便找一个环即可,\(O(n)\)
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 1e6 + 5;
int n, a[N], cnt;
bool vis[N];
void work()
{
int u;
read(n); cnt = 0;
for (int i = 1; i <= n; i++) read(a[i]), a[i] = i - a[i];
for (int i = 1; i <= n; i++) vis[i] = 0;
for (u = 1; !vis[u]; u = a[u]) vis[u] = 1;
for (int i = 1; i <= n; i++) vis[i] = 0;
for (; !vis[u]; u = a[u]) vis[u] = 1, cnt++;
for (int i = 1; i <= n; i++) vis[i] = 0;
printf("%d\n", cnt);
for (; !vis[u]; u = a[u]) vis[u] = 1, printf("%d ", u);
puts("");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
给定 \(n\) 个数 \(a_{1\dots n}\),每个数都是不超过 \(10^6\) 的正整数
对于任意 \(1\le i<j\le n\),如果 \(a_i<a_j\) 则连边 \((i,j)\),求连通块数
有 \(q\) 次单点修改,你需要在每次修改之后输出上面问题的答案,保证任何时候这 \(n\) 个数互不相同
\(1\le n,q\le 5\times10^5\)
时限 8s
性质:每个连通块都是一段区间,且任意连通块内数的 \(\min\) 都大于下一个连通块内数的 \(\max\)
证明略
问题转化成有多少个 \(1\le i\le n\) 满足对于任意 \(j\le i,k>i\) 都有 \(a_j>a_k\)
即整个序列前 \(i\) 大的数恰好占据了序列前 \(i\) 个位置
考虑暴力,把所有的数从大到小插入
易得组成恰好一个前缀连通块的条件为不存在 \(1\le i<n\) 使得 \(i\) 位置还没被插入,\(i+1\) 位置已经被插入
于是考虑开一棵值域为 \([1,10^6]\) 的线段树,位置 \(h\) 储存插入了所有 \(\ge h\) 的数之后满足如上条件的 \(i\) 个数,并维护区间最小值及最小值个数
对于所有 \(a_i<a_{i+1}\),我们让线段树的区间 \([a_i+1,a_{i+1}]\) 加一
这样答案就是全体最小值的个数(如果最小值不为 \(0\) 则答案为 \(0\))
修改时讨论 \(pos-1,pos,pos+1\) 三个位置的影响并重新进行区间加即可
但上面的过程中忽略了一个条件,如果 \(h\) 不与任意的 \(a_i\) 相等,那么这样的 \(h\) 是不能计入答案的,所以实现时我们还要记录区间内有多少个 \(h\) 是能够计入答案的,每次修改时要对这个东西进行单点修改
\(O((n+q)\log a)\)
#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 5e5 + 5, M = 4e6 + 17, L = 1e6;
int n, q, a[N], add[M], pts[M];
bool is[M];
struct elem
{
int val, cnt;
friend inline elem operator + (elem a, elem b)
{
if (a.val < b.val) return a;
else if (a.val > b.val) return b;
return (elem) {a.val, a.cnt + b.cnt};
}
} T[M];
void build(int l, int r, int p)
{
T[p].val = add[p] = 0;
if (l == r) return (void) (T[p].cnt = pts[p] = is[l]);
int mid = l + r >> 1;
build(l, mid, p2); build(mid + 1, r, p3);
T[p].cnt = pts[p] = pts[p2] + pts[p3];
}
void down(int p)
{
add[p2] += add[p]; add[p3] += add[p];
T[p2].val += add[p]; T[p3].val += add[p];
add[p] = 0;
}
void modify(int l, int r, int pos, int v, int p)
{
if (l == r) return (void) (pts[p] += v, T[p].cnt += v);
int mid = l + r >> 1; down(p);
if (pos <= mid) modify(l, mid, pos, v, p2);
else modify(mid + 1, r, pos, v, p3);
T[p] = T[p2] + T[p3];
}
void change(int l, int r, int s, int e, int v, int p)
{
if (e < l || s > r) return;
if (s <= l && r <= e) return (void) (add[p] += v, T[p].val += v);
int mid = l + r >> 1; down(p);
change(l, mid, s, e, v, p2); change(mid + 1, r, s, e, v, p3);
T[p] = T[p2] + T[p3];
}
elem ask(int l, int r, int s, int e, int p)
{
if (e < l || s > r) return (elem) {-1, 0};
if (s <= l && r <= e) return T[p];
int mid = l + r >> 1; down(p);
return ask(l, mid, s, e, p2) + ask(mid + 1, r, s, e, p3);
}
int main()
{
int pos, x; read(n); read(q);
for (int i = 1; i <= n; i++) read(a[i]), is[a[i]] = 1;
build(1, L, 1);
for (int i = 1; i < n; i++) if (a[i] < a[i + 1])
change(1, L, a[i] + 1, a[i + 1], 1, 1);
while (q--)
{
read(pos); read(x);
modify(1, L, a[pos], -1, 1); modify(1, L, x, 1, 1);
if (pos > 1)
{
if (a[pos - 1] < a[pos]) change(1, L, a[pos - 1] + 1, a[pos], -1, 1);
if (a[pos - 1] < x) change(1, L, a[pos - 1] + 1, x, 1, 1);
}
if (pos < n)
{
if (a[pos] < a[pos + 1]) change(1, L, a[pos] + 1, a[pos + 1], -1, 1);
if (x < a[pos + 1]) change(1, L, x + 1, a[pos + 1], 1, 1);
}
a[pos] = x; printf("%d\n", T[1].val ? 0 : T[1].cnt);
}
return 0;
}
给定一个 \(2^k\times 2^k\) 的正整数矩阵 \(a\),每个数在 \([0,2^{60})\) 内,行列从 \(0\) 开始标号
还有一个大小为 \(t\) 的二元组集合 \(S=\{(x,y)\}\),里面的元素互不相同且 \(1\le x,y\le 2^k\)
每次操作可以选择一个位置 \((u,v)\) 和一个数 \(p\),对于每个 \((x,y)\in S\),让矩阵 \(((u+x)\bmod 2^k,(v+y)\bmod 2^k)\) 位置上的数异或上 \(p\)
求把所有的数都变成 \(0\) 的最少操作次数
\(1\le k\le 9,1\le t\le\min(99,4^k)\) 且 \(t\) 是奇数
时限 5s
妙啊!!!\(\times 2\)
为了方便讨论,先把二元组集合内的横纵坐标都减一
设数组 \(f_{i,j}=\sum_{(x,y)\in S}a_{(i-x)\bmod2^k,(j-y)\bmod2^k}\)
性质 \(1\):当 \(t\) 为奇数时,\(a\) 全 \(0\) 当且仅当 \(f\) 全 \(0\)
证明咕咕咕(我还没看),可以看原题解
性质 \(2\):对 \((i,j)\) 进行异或 \(p\) 操作的影响是对于每个 \((x,y)\in S\) 使 \(f_{(i+2x)\bmod2^k,(j+2y)\bmod2^k}\) 异或上 \(p\)
首先直观地描述,对 \((i,j)\) 进行异或 \(p\) 操作相当于在 \(S\) 中分两次,每次选一个二元组,设这两个二元组分别为 \((x,y)\) 和 \((u,v)\),让 \(f_{(i+x+u)\bmod2^k,(j+y+v)\bmod2^k}\) 异或上 \(p\)
若 \((x,y)\ne(u,v)\),则两次分别选出 \((x,y)(u,v)\) 和分别选出 \((u,v)(x,y)\) 的影响是一样的,根据异或运算的性质 \(p\bigotimes p=0\),可以得到如果 \((x,y)\ne(u,v)\) 的情况不造成影响
于是只剩下了 \((x,y)=(u,v)\) 的情况,\(\text{Q.E.D.}\)
观察性质 \(2\),容易发现如果把矩阵 \(f\) 按照行列号的奇偶性拆成 \(4\) 个部分,那么每个部分是独立的,可以分别递归做
递归到 \(k=0\) 的时候判断矩阵的唯一元素是否为 \(0\) 即可计入答案
\(O(4^kkt)\)
异或真的是最美妙的运算啊。
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
typedef std::vector<std::pair<int, int> > vp;
typedef std::vector<ll> vl;
typedef std::vector<vl> vvl;
const int N = 520;
int k, t, ans;
vl emp;
vvl a;
vp op;
vvl trans(vvl a, vp op)
{
int n = a.size();
vvl res;
for (int i = 0; i < n; i++)
{
res.push_back(emp);
for (int j = 0; j < n; j++)
{
res[i].push_back(0);
for (int k = 0; k < t; k++)
res[i][j] ^= a[i - op[k].first + n & n - 1]
[j - op[k].second + n & n - 1];
}
}
return res;
}
void jjd(vvl a, vp op)
{
if (a.size() == 1) return (void) (ans += (a[0][0] > 0));
a = trans(a, op); int n = a.size();
vvl b[2][2];
for (int i = 0; i < n; i++)
{
b[i & 1][0].push_back(emp); b[i & 1][1].push_back(emp);
for (int j = 0; j < n; j++)
b[i & 1][j & 1][i >> 1].push_back(a[i][j]);
}
for (int i = 0; i < t; i++) op[i].first &= (n >> 1) - 1,
op[i].second &= (n >> 1) - 1;
for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++)
jjd(b[i][j], op);
}
int main()
{
read(k); ll x; int u, v;
for (int i = 0; i < (1 << k); i++)
{
a.push_back(emp);
for (int j = 0; j < (1 << k); j++)
read(x), a[i].push_back(x);
}
read(t);
for (int i = 1; i <= t; i++)
read(u), read(v), op.push_back(std::make_pair(u - 1, v - 1));
return std::cout << (jjd(a, op), ans) << std::endl, 0;
}
[题解][Codeforces]Good Bye 2019 简要题解
标签:最优 三角形 lin 直接 top 最大数 strlen cout scanf
原文地址:https://www.cnblogs.com/xyz32768/p/12147147.html