AtCoder Grand Contest 001 题解
A-BBQ Easy (排序)
题意
略
题解
排序后取奇数位置的数即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5;
int arr[N];
typedef long long ll;
int main() {
int n;
cin >> n;
for(int i = 1; i <= 2 * n; i++) {
cin >> arr[i];
}
sort(arr + 1 , arr + 2 * n + 1);
ll tot = 0;
for(int i = 1; i <= 2 * n; i += 2) {
tot += arr[i];
}
cout << tot << endl;
}
B-Mysterious Light (gcd)
题意
略
题解
手动模拟一下发现就是一个求n-x和x的gcd的过程。
ll solve(ll a, ll b) {
if(!b) {
return a;
}
ll res = 0;
res += ((a - 1) / b) * 2 * b;
res += solve(b, a % b);
return res;
}
int main() {
IOS;
ll n, x;
cin >> n >> x;
cout << solve(n - x, x) + n << endl;
}
C-Shorten Diameter (树分治,暴力dfs)
题意
给你一颗无根树,要求删除最少结点使得剩余的图还是一颗树且直径不大于K。
题解
由于树直径的中点是唯一的,且每个点到这个点的距离不超过直径的一半(显然)。
直径的中点可能在点上(直径为偶数)也可能在边上(直径为奇数)。一个暴力的做法是根据K的奇偶来枚举最终剩余的树的直径中点,删除距离该点超过K/2的所有点,取其中最小。复杂的$$O(n^2)$$
由于本题数据范围小,可以暴力。若N非常大,要用到树分治。
const int N = 3e5 + 10;
const double eps = 1e-5;
vector<int> np[N];
typedef pair<int, int> PII;
int n, k;
int dfs(int p, int fa, int dep) {
int res = 0;
if(dep > k / 2) res++;
for(int nt : np[p]) {
if(nt == fa) continue;
res += dfs(nt, p, dep + 1);
}
return res;
}
int main() {
IOS;
cin >> n >> k;
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
}
int ans = n - 1;
if(k % 2 == 0) {
for(int i = 1; i <= n; i++) { //枚举点
int tot = 0;
ans = min(ans, dfs(i, 0, 0));
}
} else {
for(int i = 1; i <= n; i++) { //枚举边,直径中心为边的中心
int tmp = 0;
for(int j : np[i]) {
ans = min(ans, dfs(i, j, 0) + dfs(j, i, 0));
}
}
}
cout << ans << endl;
}
D-Arrays and Palindrome (思维)
题意
题目比较绕,详见洛谷的翻译
题解
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e5 + 10;
const double eps = 1e-5;
int arr[N];
int main() {
IOS;
int n, m;
cin >> n >> m;
int cnt = 0;
int odd1 = 0, odd2 = 0;
for(int i = 1; i <= m; i++) {
cin >> arr[i];
if(arr[i] % 2) {
cnt++;
if(cnt == 1) odd1 = i;
if(cnt == 2) odd2 = i;
}
}
if(m == 1) {
cout << arr[1] << endl;
if(arr[1] == 1) {
cout << 1 << endl;
cout << 1 << endl;
} else {
cout << 2 << endl;
cout << 1 << " " << arr[1] - 1 << endl;
}
}
else if(cnt > 2) cout << "Impossible" << endl;
else {
if(cnt == 2) {
swap(arr[odd1], arr[1]);
swap(arr[odd2], arr[m]);
} else if(cnt == 1) {
swap(arr[odd1], arr[1]);
}
for(int i = 1; i <= m; i++) cout << arr[i] << " \n"[i == m];
if(arr[m] - 1) cout << m << endl;
else cout << m - 1 << endl;
cout << arr[1] + 1 << " ";
for(int i = 2; i < m; i++) {
cout << arr[i] << " ";
}
if(arr[m] - 1) cout << arr[m] - 1 << endl;
else cout << endl;
}
}
E-BBQ Hard (dp)
题意
题解
组合数\(\left( \begin{matrix} x+y \\ x \end{matrix} \right)\)几何意义是从\((0, 0)\)到\((x, y)\)的路径数。
略证:
\(\left( \begin{matrix} x+y \\ x \end{matrix} \right) = \left( \begin{matrix} x+(y-1) \\ x \end{matrix} \right)+\left( \begin{matrix} (x-1)+y \\ x-1 \end{matrix} \right)\)
故题目可转换为从\((0, 0)\)到\((a_i+a_j, b_i+b_j)\)的路径数,进一步可转化为\((-a_i, -b_i)\)到\((a_j, b_j)\)。令每个\((-a_i, -b_i)\)为1,然后dp。最后答案为每个\((a_i, b_i)\)的和。还要减去每个\((-a_i, -b_i)\)到\((a_i, b_i)\)的值,即\(\left( \begin{matrix} 2(a_i+b_i) \\ a_i+b_i \end{matrix} \right)\),然后除以2,因为题目要求的是\(i<j\)的数对。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 2e3 + 10;
const int M = 1e9 + 7;
const double eps = 1e-5;
inline ll qpow(ll a, ll b, ll m) {
ll res = 1;
while(b) {
if(b & 1) res = (res * a) % m;
a = (a * a) % m;
b = b >> 1;
}
return res;
}
ll inv(ll x) {
return qpow(x, M - 2, M);
}
ll f[N << 2], rf[N << 2];
ll dp[N << 1][N << 1];
ll C(int n, int m) {
return f[n] * rf[n - m] % M * rf[m] % M;
}
typedef pair<int, int> PII;
PII arr[300000];
int main() {
IOS;
f[0] = rf[0] = 1;
for(int i = 1; i < (N << 2); i++) {
f[i] = (f[i - 1] * i) % M;
rf[i] = (rf[i - 1] * inv(i)) % M;
}
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
arr[i] = mp(a, b);
dp[N - a][N - b]++;
}
for(int i = 1; i < (N << 1); i++) {
for(int j = 1; j < (N << 1); j++) {
dp[i][j] = (dp[i][j] + (dp[i - 1][j] + dp[i][j - 1]) % M) % M;
}
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
int a = arr[i].first, b = arr[i].second;
ans += dp[N + a][N + b];
ans %= M;
ans -= C(2 * a + 2 * b, 2 * a);
ans = (ans % M + M) % M;
}
ans = ans * 500000004 % M;
cout << ans << endl;
}
F-Wide Swap (线段树,拓扑排序)
题意
题解
以\(P_i\)为下标,i为值构造新序列(即逆序列),题目变为若相邻的数相差大于K则可交换,求最小字典序。这个转换一下子把题目变得顺眼起来。
为了字典序最小,小的值要越前越好。因此从1开始,如果合法(前面的数比自己大K)就不停向前移动,直到移不动为止。那么当前1和它前面的数(设为x)相对位置就固定不动了,而且这个位置关系是最优的,然后x向1连边,代表x在1之前,然后更新1的值\(P_1\)为INF。以此类推,建出的图优先队列拓扑排序一下即可。
注意最后的序列还要在逆一次变回原序列对应的答案。
复杂的\(O(n^2)\),但网上正解复杂为\(O(n)\)。以后有时间再补。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N = 5e5 + 10;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int mi[N << 2];
int pe[N], rpe[N];
int n;
int deg[N];
vector<int> np[N];
void build(int l, int r, int rt) {
if(l == r) {
mi[rt] = rpe[l];
return ;
}
mi[rt] = INF;
int mid = (l + r) / 2;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}
int query(int l, int r, int L, int R, int rt) {
if(l >= L && r <= R) {
return mi[rt];
}
int mid =(l + r) / 2;
int res = INF;
if(L <= mid) res = min(res, query(l, mid, L, R, rt << 1));
if(R > mid) res = min(res, query(mid + 1, r, L, R, rt << 1 | 1));
mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
return res;
}
void update(int l, int r, int p, int val, int rt) {
if(l == r) {
mi[rt] = val;
return ;
}
int mid = (l + r) / 2;
if(p <= mid) update(l, mid, p, val, rt << 1);
else update(mid + 1, r, p, val , rt << 1 | 1);
mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}
int find(int p, int v, int k) {
int l = 1, r = p - 1;
while(l <= r) {
int mid = (l + r) / 2;
int tar = query(1, n, mid, p - 1, 1);
if(tar >= v + k) {
r = mid - 1;
} else {
l = mid + 1;
}
}
l--;
return l;
}
vector<int> ans;
priority_queue<int, vector<int>, greater<int> > q;
int main() {
int k;
cin >> n >> k;
for(int i = 1; i <= n; i++) {
cin >> pe[i];
rpe[pe[i]] = i;
}
build(1, n, 1);
for(int i = 1; i <= n; i++) {
int p = find(pe[i], i, k);
if(p) {
np[rpe[p]].push_back(i);
deg[i]++;
}
update(1, n, pe[i], INF, 1);
}
for(int i = 1; i <= n; i++) {
if(!deg[i]) {
q.push(i);
}
}
while(!q.empty()) {
int cur = q.top();
q.pop();
ans.push_back(cur);
for(int nt : np[cur]) {
deg[nt]--;
if(!deg[nt]) q.push(nt);
}
}
for(int i = 0; i < ans.size(); i++) {
pe[ans[i]] = i + 1;
}
for(int i = 1; i <= n; i++) cout << pe[i] << endl;
}