Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated, T-shirts + prizes!)
前言
比赛AC
A. Kuroni and the Gifts
简明题意
- 有两个长度为n的数组,a[]和b[],现在有n个人,每个人可以从a数组和b数组中分别选一个值并累加起来作为得分。要使得每个人的得分都不相同,需要你输出一种合法的选法。
- a[]数组两两不相同,b也是
正文
- 直接ab分别排序,对应相加就是一种答案。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int maxn = 2e5 + 10;
void solve()
{
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
int a[110], b[110];
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
for (int i = 1; i <= n; i++)
{
cout << a[i];
if (i != n)
cout << " ";
}
cout << endl;
for (int i = 1; i <= n; i++)
{
cout << b[i];
if (i != n)
cout << " ";
}
cout << endl;
}
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
B. Kuroni and Simple Strings
简明题意
- 给一个长度<=1000的又(和)组成的字符串。
- () 、 (())、((()))… 每次可以从字符串中删除一个这样的子序列,问最少进行多少次删除操作可以使得原字符串无法进行更多的删除操作。
正文
- 倒叙枚举((()))这种序列,在原字符串中能删就删。最后得到的答案就是最优解。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<string>
using namespace std;
const int maxn = 2e5 + 10;
string s;
vector<int> ans[1000];
int k;
void solve()
{
cin >> s;
string t = "a";
s = t + s;
int n = s.size() - 1;
for (int i = n / 2; i >= 1; i--)
{
int m = 1000;
while (m--)
{
int cnt1 = 0, l = -1;
for (int j = 1; j <= n; j++)
{
if (s[j] == '(') cnt1++;
if (cnt1 == i) {
l = j;
break;
}
}
int cnt2 = 0, r = -1;
for (int j = n; j >= 1; j--)
{
if (s[j] == ')') cnt2++;
if (cnt2 == i) {
r = j;
break;
}
}
if (r != -1 && l != -1 && r > l)
{
k++;
int cnt1 = 0, cnt2 = 0;
for (int j = 1; j <= n; j++)
{
if (s[j] == '(') s[j] = '.', cnt1++, ans[k].push_back(j);
if (cnt1 == i) break;
}
for (int j = n; j >= 1; j--)
{
if (s[j] == ')') s[j] = '.', cnt2++, ans[k].push_back(j);
if (cnt2 == i) break;
}
}
}
}
cout << k << endl;
for (int i = 1; i <= k; i++)
{
sort(ans[i].begin(), ans[i].end());
cout << ans[i].size() << endl;
for (int j = 0; j < ans[i].size(); j++)
{
cout << ans[i][j];
if (j != ans[i].size() - 1) cout << " ";
}
cout << endl;
}
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
赛后补题
C. Kuroni and Impossible Calculation
简明题意
- 给n、m,再给n个数。求这n个数两两之差的积%m。m<=1000
正文
- ∏ i = 2 n ( a i − a 1 ) ∗ ∏ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ \prod\limits_{i=2}^{n}(a_i-a_1)*\prod······ i=2∏n(ai−a1)∗∏⋅⋅⋅⋅⋅⋅
- 由于只涉及到减法,因此可以把mod m提到括号内: ∏ i = 2 n ( a i m o d m − a 1 m o d m ) ∗ ∏ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ \prod\limits_{i=2}^{n}(a_i\mod m-a_1\mod m)*\prod······ i=2∏n(aimodm−a1modm)∗∏⋅⋅⋅⋅⋅⋅
- 当 a i a_i ai%m= a 1 a_1 a1%m时,这一项是0,最终答案就也是0.那么i共有n种,当n>m时,一定有两项%m的值相等,答案就是0。而m<=1000,因此只要n>m,答案直接是0,其他暴力算就可以了。
代码
#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
int a[1100];
void solve()
{
int n, m;
cin >> n >> m;
if (n > m) cout << 0;
else
{
for (int i = 1; i <= n; i++)
cin >> a[i];
long long ans = 1;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
ans = ans * abs(a[j] - a[i]) % m;
cout << ans;
}
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
D. Kuroni and the Celebration
简明题意
- 交互题。给出一棵含n个节点的树。你每次可以向系统询问任意两个点的lca,需要你输出这棵树的根。(询问最多n/2次(向下取整))
正文
- 首先要从叶子节点入手。从叶子节点入手,如果任意两个叶子节点的LCA就是这两个叶子节点中的一个,那么这个LCA就是答案。否则就不是。
- 一棵树最少1个叶子节点,最多n-1个叶子节点。按照上面的方法,最坏情况要询问n-2次。不满足要求。
- 那么我们考虑询问一次后就把这个节点去掉,然后给和这个节点相连的节点的度-1.直到找到两个叶子的lca等于他们本身,或者最终只剩1个节点。
- 这样最坏情况会询问多少次呢?想要出现最坏情况,叶子节点尽可能多。最多会有n-1个叶子节点,每次询问后会少两个叶子节点,所以最坏就是[ n − 1 2 ] \frac{n-1}{2}] 2n−1],满足要求。
代码
#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
vector<int> g[1100];
int degree[1100];
void solve()
{
int n;
cin >> n;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v), g[v].push_back(u);
degree[u]++, degree[v]++;
}
vector<int> a;
for (int i = 1; i <= n; i++)
if (degree[i] == 1) a.push_back(i);
while (a.size() >= 2)
{
int u = a[0], v = a[1];
a.erase(a.begin()), a.erase(a.begin());
cout << "? " << u << " " << v << endl, cout.flush();
int lca; cin >> lca;
if (lca == u || lca == v)
{
cout << "! " << lca, cout.flush();
return;
}
for (auto& it : g[u])
if (--degree[it] == 1) a.push_back(it);
for (auto& it : g[v])
if (--degree[it] == 1) a.push_back(it);
}
cout << "! " << *a.begin(), cout.flush();
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
E. Kuroni and the Score Distribution
简明题意
- 给出n、m,需要你构造一个长度为n的序列,这个序列满足3个条件
- 1.每个数<1e9
- 2.序列单调递增
- 3.恰有m项满足:i<j<k&&a[i]+a[j]=a[k]
正文
- 可以发现,1234…这样一次选择一个序列得到的满足要求3的个数是最多的。(在相同的长度下)。那么我们就可以按着1234…选,直到选够m个,剩下的在补齐一些不可能组成条件3的数就可以了。
- 按照12345组,假设新增了一个i,第i个组成的符合条件三的数量是 c n t [ i ] = c n t [ i − 1 ] + [ i − 1 2 ] cnt[i]=cnt[i-1]+[\frac{i-1}{2}] cnt[i]=cnt[i−1]+[2i−1].这样一直组,直到某个i+1使得加上这个cnt[i+1],条件3的数量就超过了m,这时候令p=m-pre_cnt[i],(pre_cnt表示cnt的前缀和),那么就相当于还差p个条件3,我们要在后面补数,使得条件三的数量增多p个。
- 这里讨论如何使得条件3增多p个。考虑到第i个位置时,填入i, c n t [ i ] = c n t [ i − 1 ] + [ i − 1 2 ] cnt[i]=cnt[i-1]+[\frac{i-1}{2}] cnt[i]=cnt[i−1]+[2i−1],而如果在第i个位置填入k(k>=i),那么 c n t [ i ] = c n t [ i − 1 ] + [ i − 1 − [ k − ( i − 1 ) ] + 1 2 ] cnt[i]=cnt[i-1]+[\frac{i-1-[k-(i-1)]+1}{2}] cnt[i]=cnt[i−1]+[2i−1−[k−(i−1)]+1]
- 上式怎么推的自己好好想想。上式化简一下:
c n t [ i ] = c n t [ i − 1 ] + [ 2 i − k − 1 2 ] cnt[i]=cnt[i-1]+[\frac{2i-k-1}{2}] cnt[i]=cnt[i−1]+[22i−k−1] - 如果令k=i,得到 c n t [ i ] = c n t [ i − 1 ] + [ i − 1 2 ] cnt[i]=cnt[i-1]+[\frac{i-1}{2}] cnt[i]=cnt[i−1]+[2i−1],跟最开始推的按照12345…组的公式达成一致。还是按照上面的式子,我们能发现在位置i填的数越大,组成条件3的就越少。因此这里也验证了为什么按照123456…组是最优的。
- 这时填好了,还差p个没有填。这个 p < [ i − 1 ] 2 p<\frac{[i-1]}{2} p<2[i−1](这怎么来的?回忆一下p的定义)。因为在位置i填i会新增(i-1)/2个,而现在需要的p比这个小,因此我们填一个比i大的数就可以刚好新增p个。所以我们一定可以在位置i填入一个数k使得 c n t [ i ] = p cnt[i]=p cnt[i]=p。
- 应该填谁呢?令 [ 2 i − k − 1 2 ] = p [\frac{2i-k-1}{2}]=p [22i−k−1]=p,假设k是奇数,k=2i-2p-1,而如果k是偶数,k=2i-2p-2。但要注意填2i-2p-2时有点要注意点地方。
- 填好之后,就刚刚好有m个条件3了。这时还差一些数,我们要再填一些数进去,使得不组成新的条件3。 因为我们的k最大也就5000,我们可以直接从一个很大的数开始,每次加6000,就一定不会组成新的条件3了。比如从1e8开始,每次增加6000,那么肯定不可能和k之前的组成条件3,因为这里的等差数列差是6000,k是不够的。而等差数列自身也不可能组成新的,因为第一次能组的是1e8+6000和1e8+6000+6000两项与1e8+18000,要想直接增多到2e6,起码得(1e8-6000) / 6000=1万多项才可能出现一次条件3.
代码
#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
int ans[6000];
void solve()
{
int n, m, cur = 0, i;
cin >> n >> m;
bool ok = 0;
for (i = 1; i <= n; i++)
{
if (cur + (i - 1) / 2 < m) cur += (i - 1) / 2, ans[i] = i;
else
{
ok = 1;
int p = m - cur;
ans[i] = 2 * i - 2 * p - 1;
if (ans[i] == 0) ans[i]++;
break;
}
}
if (!ok)
{
cout << -1;
return;
}
ans[i + 1] = 100000000;
for (int j = i + 2; j <= n; j++) ans[j] = ans[j - 1] + 6000;
for (int i = 1; i <= n; i++) cout << ans[i] << " ";
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}