比赛链接
官方题解
Problem A. Anu Has a Function
讨论 的结果,可以发现,答案的各个位是独立的。
进一步发现,答案在某一位上是
当且仅当
是唯一一个在这一位上是
的数。
因此,
的排列顺序是不重要的,枚举
,并计算对应的答案,取最大者即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int a[MAXN], pre[MAXN], suf[MAXN];
int main() {
int n; read(n);
int ans = 0, pos = 1;
for (int i = 1; i <= n; i++) {
read(a[i]);
pre[i] = pre[i - 1] | a[i];
}
for (int i = n; i >= 1; i--)
suf[i] = suf[i + 1] | a[i];
for (int i = 1; i <= n; i++) {
int tmp = pre[i - 1] | suf[i + 1];
int res = a[i] & (~tmp);
if (res > ans) {
ans = res;
pos = i;
}
}
swap(a[pos], a[1]);
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
return 0;
}
Problem B. Aerodynamic
题目中 的定义即为 和 的闵可夫斯基和。
因此,若
相似,则
和
中的每一条边对应的向量都应相等。
从而答案为 Yes 当且仅当
是中心对称图形。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct point {int x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, int b) {return (point) {a.x * b, a.y * b}; }
long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; }
ll x[MAXN], y[MAXN], sx, sy;
int main() {
int n; read(n);
if (n & 1) {
puts("No");
return 0;
}
for (int i = 1; i <= n; i++) {
read(x[i]), read(y[i]);
sx += x[i], sy += y[i];
}
for (int i = 1, j = n / 2 + 1; j <= n; i++, j++)
if (x[i] * n + x[j] * n != sx * 2
|| y[i] * n + y[j] * n != sy * 2) {
puts("No");
return 0;
}
puts("Yes");
return 0;
}
Problem C. Water Balance
这是个 Div.1 C 题
将序列倒过来,用单调栈贪心维护各个断点的位置即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
typedef long long ll;
typedef double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n; ld a[MAXN], s[MAXN];
int top, q[MAXN];
int main() {
read(n);
for (int i = 1; i <= n; i++)
read(a[i]), s[i] = s[i - 1] + a[i];
for (int i = n; i >= 1; i--) {
q[++top] = i;
while (top >= 2 && (s[q[top]] - s[i - 1]) / (q[top] - (i - 1)) > (s[q[top - 1]] - s[q[top]]) / (q[top - 1] - q[top])) top--;
}
q[top + 1] = 0;
for (int i = top; i >= 1; i--) {
ld res = (s[q[i]] - s[q[i + 1]]) / (q[i] - q[i + 1]);
for (int j = q[i + 1] + 1; j <= q[i]; j++)
printf("%.10lf\n", res);
}
return 0;
}
Problem D. Around the World
首先, 以内的数的线性基的数量不多,搜索可知,共有 种。
题目中对图性质的限制意味着若将 号点删去,与 号点相邻的各个点若没有边直接相连,将互不连通。并且与 号点相邻的各个点之间的边一定形成了一个匹配。
考虑对于给定的图,判断是否存在长为 的环,则可任取一棵生成树,对所有非树边形成的环长建立线性基,若它们线性无关,则不存在长为 的环。
由于 以内的数的线性基的数量不多,可以直接将当前线性基的种类作为状态,进行动规套动规,实现细节可以参考代码。
时间复杂度 ,其中 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXM = 405;
const int P = 1e9 + 7;
typedef long long ll;
typedef bitset <32> info;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
unordered_set <info> st;
unordered_map <info, int> home;
vector <pair <int, int>> a[MAXN];
info q[MAXN]; int l, r, trans[MAXM][32];
int x[MAXN], y[MAXN], z[MAXN], f[MAXN];
int sum[MAXN], from[MAXN];
vector <int> v[MAXN][2];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
void dfs(int pos, int fa, int f, int s) {
sum[pos] = s, from[pos] = f;
for (auto x : a[pos])
if (x.first != fa) {
dfs(x.first, pos, f, s ^ x.second);
}
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
q[l = r = 1] = 1;
st.insert(q[1]);
while (l <= r) {
info tmp = q[l++];
for (int i = 0; i <= 31; i++) {
info res = tmp;
for (int j = 0; j <= 31; j++)
res[i ^ j] = (res[i ^ j] | tmp[j]);
if (st.count(res) == 0) {
st.insert(res);
q[++r] = res;
}
}
}
for (int i = 1; i <= r; i++)
home[q[i]] = i;
for (int t = 1; t <= r; t++) {
info tmp = q[t];
for (int i = 0; i <= 31; i++) {
info res = tmp;
for (int j = 0; j <= 31; j++)
res[i ^ j] = (res[i ^ j] | tmp[j]);
if (res.count() == tmp.count() * 2) trans[t][i] = home[res];
else trans[t][i] = 0;
}
}
int n, m; read(n), read(m);
for (int i = 1; i <= n; i++)
f[i] = i;
static int p[MAXN];
for (int i = 1; i <= m; i++) {
read(x[i]), read(y[i]), read(z[i]), p[i] = i;
}
sort(p + 1, p + m + 1, [&] (int a, int b) {return ((x[a] == 1) || (y[a] == 1)) < ((x[b] == 1) || (y[b] == 1)); });
static bool vis[MAXN], key[MAXN];
for (int i = 1; i <= m; i++) {
int pos = p[i];
if (find(x[pos]) != find(y[pos])) {
f[find(x[pos])] = find(y[pos]);
a[x[pos]].emplace_back(y[pos], z[pos]);
a[y[pos]].emplace_back(x[pos], z[pos]);
} else vis[pos] = true;
}
for (auto x : a[1]) {
dfs(x.first, 1, x.first, x.second);
key[x.first] = true;
}
for (int i = 1; i <= m; i++)
if (vis[i]) {
int len = sum[x[i]] ^ sum[y[i]] ^ z[i];
if (from[x[i]] == from[y[i]]) {
v[from[x[i]]][0].push_back(len);
} else {
assert(from[x[i]] == 0 || from[y[i]] == 0);
if (from[x[i]] == 0) v[from[y[i]]][1].push_back(len);
else v[from[x[i]]][1].push_back(len);
}
}
static int dp[MAXN][MAXM]; dp[0][1] = 1;
for (int i = 1; i <= n; i++)
if (!key[i]) memcpy(dp[i], dp[i - 1], sizeof(dp[i - 1]));
else {
if (v[i][1].size()) {
for (int j = 1; j <= r; j++) {
update(dp[i][j], dp[i - 1][j]);
int res = j;
for (auto x : v[i][0])
res = trans[res][x];
if (res != 0) update(dp[i][res], 2ll * dp[i - 1][j] % P);
for (auto x : v[i][1])
res = trans[res][x];
if (res != 0) update(dp[i][res], dp[i - 1][j]);
}
} else {
for (int j = 1; j <= r; j++) {
update(dp[i][j], dp[i - 1][j]);
int res = j;
for (auto x : v[i][0])
res = trans[res][x];
if (res != 0) update(dp[i][res], dp[i - 1][j]);
}
}
}
int ans = 0;
for (int i = 1; i <= r; i++)
update(ans, dp[n][i]);
cout << ans << endl;
return 0;
}
Problem E. So Mean
由于 是偶数,若询问一个大小为 的子集 ,答案为 Yes 当且仅当 是一段连续的数字,此时 中数字的平均数即为中位数。由此,我们可以得知 的位置,由于有两种排列不能区分,任取一个作为 即可。
将剩余每个数和 一起询问一次,我们可以知道所有数的奇偶性。
考虑剩余的
个数,由于
依然是偶数,我们同样可以询问一个大小为
的子集来找到
,这也直接带来了一个操作次数为
的解法。
对于足够小的
,问题已经得到了解决。
按照这个方式找到 ,此时我们总共使用了 次操作。
接下来,我们考虑计算出每一个未被确定的数模
的余数。
由于
,我们可以用中国剩余定理合并,得到确切的数字。
对于模 ,我们总能从已经确定的数字中找到一系列数,将它们与想要确定的数一起询问,从而判断其余数是否为某一值。例如,询问 可以判断 是否是 的倍数。
确定所有数模
的余数大约需要
次操作,
确定所有数模
的余数大约需要
次操作,
确定所有数模
的余数大约需要
次操作。
对于模 ,由于我们已经知道了每个数模 的余数,只需要再进行两次询问,求出该数模 ,模 的余数即可。
时间复杂度 ,操作次数约为 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 805;
const int p[4] = {3, 5, 7, 8};
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, res, ans[MAXN];
bool odd[MAXN], used[MAXN];
pair <int, int> pos[5];
int gett(int x) { // Modulo 3
cout << '?' << ' ' << 3 << ' ' << x << ' ';
cout << pos[1].first << ' ' << pos[2].first << endl;
read(res); if (res) return 0;
cout << '?' << ' ' << 3 << ' ' << x << ' ';
cout << pos[2].first << ' ' << pos[3].first << endl;
read(res); if (res) return 1;
return 2;
}
int getf(int x) { // Modulo 5
bool mark[5];
memset(mark, false, sizeof(mark));
for (int i = 1; i <= 4; i++) {
cout << '?' << ' ' << 5 << ' ' << x << ' ' <<
pos[1].second << ' ' << pos[2].second << ' ' << pos[3].second << ' ';
cout << pos[i].first << endl;
int value = (5 - (3 * n - 3 + i) % 5) % 5;
read(res); if (res) return value;
mark[value] = true;
}
for (int i = 0; i <= 4; i++)
if (!mark[i]) return i;
return -1;
}
int gets(int x) { // Modulo 7
bool mark[7]; int cnt = 0;
memset(mark, false, sizeof(mark));
for (int i = 1; i <= 4 && cnt <= 5; i++)
for (int j = 1; j <= 4 && cnt <= 5; j++) {
int value = (7 - (n * 4 + 4 - i - (n - j + 1)) % 7) % 7;
if (mark[value]) continue;
cout << '?' << ' ' << 7 << ' ' << x << ' ';
for (int k = 1; k <= 4; k++)
if (k != i) cout << pos[k].first << ' ';
for (int k = 1; k <= 4; k++)
if (k != j) cout << pos[k].second << ' ';
cout << endl; read(res); if (res) return value;
mark[value] = true, cnt++;
}
for (int i = 0; i <= 6; i++)
if (!mark[i]) return i;
return -1;
}
int gete(int x) { // Modulo 8
int ans = odd[x];
for (int i = 1; i <= 4; i++) {
int value = (4 - (10 - i) % 4) % 4;
if (value == ans) {
cout << '?' << ' ' << 4 << ' ' << x << ' ';
for (int k = 1; k <= 4; k++)
if (k != i) cout << pos[k].first << ' ';
cout << endl; read(res);
if (res) ans = ans;
else ans = ans + 2;
break;
}
}
for (int i = 1; i <= 4; i++) {
int value = (8 - (n * 4 + 4 - i) % 8) % 8;
if (value == ans || value == ans + 4) {
cout << '?' << ' ' << 8 << ' ' << x << ' ';
for (int k = 1; k <= 4; k++) {
if (k != i) cout << pos[k].first << ' ';
cout << pos[k].second << ' ';
}
cout << endl; read(res);
if (res ^ (value == ans)) ans = ans + 4;
else ans = ans;
break;
}
}
return ans;
}
void Output() {
if (ans[1] > n / 2) {
for (int i = 1; i <= n; i++)
ans[i] = n + 1 - ans[i];
}
cout << '!' << ' ';
for (int i = 1; i <= n; i++)
cout << ans[i] << ' ';
cout << endl;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
cout << '?' << ' ' << n - 1 << ' ';
for (int j = 1; j <= n; j++)
if (i != j) cout << j << ' ';
cout << endl; read(res);
if (res) {
if (pos[1].first) pos[1].second = i;
else pos[1].first = i;
}
}
used[pos[1].first] = used[pos[1].second] = true;
odd[pos[1].first] = true;
for (int i = 1; i <= n; i++)
if (i != pos[1].first) {
cout << '?' << ' ' << 2 << ' ' << i << ' ' << pos[1].first << endl;
read(odd[i]);
}
for (int k = 2; k <= 4 && k * 2 <= n; k++) {
for (int i = 1; i <= n; i++) {
if (used[i]) continue;
cout << '?' << ' ' << n - k * 2 + 1 << ' ';
for (int j = 1; j <= n; j++)
if (i != j && !used[j]) cout << j << ' ';
cout << endl; read(res);
if (res) {
if (odd[i] ^ (k % 2 == 1)) pos[k].second = i;
else pos[k].first = i;
}
}
used[pos[k].first] = used[pos[k].second] = true;
}
for (int i = 1; i <= 4 && i <= n / 2; i++) {
ans[pos[i].first] = i;
ans[pos[i].second] = n + 1 - i;
}
if (n <= 6) {
Output();
return 0;
}
for (int i = 1; i <= n; i++) {
if (ans[i]) continue;
int t = gett(i);
int f = getf(i);
int s = gets(i);
int e = gete(i);
for (int j = 1; j <= n; j++)
if (j % 3 == t && j % 5 == f && j % 7 == s && j % 8 == e) {
ans[i] = j;
break;
}
}
Output();
return 0;
}