Educational Codeforces Round 117 A-E题解
Educational Codeforces Round 117
\(\quad\)
上大分!好耶
A. Distance
题意:给出 \(B\) 点坐标 \(bx,by\),要输出 \(C\) 的坐标使得 \(C\) 到原点,\(C\) 到 \(B\) 点的曼哈顿距离都为 \(B\) 到原点的曼哈顿距离的一半。
分奇偶讨论即可
int n, m;
void solve() {
int x, y;
cin >> x >> y;
int dis =(abs(x) + abs(y));
if(dis % 2 == 0){
if(abs(x) % 2 == 0){
cout << x/2 << ' ' << y/2 << '\n';
}else{
cout << floor(x/2.0) << ' ' << ceil(y/2.0) << '\n';
}
}else{
cout << "-1 -1\n";
}
}
\(\quad\)
B. Special Permutation
题意:要构造一个 \(n\) 的排列(\(n\) 为偶数),使得左半边最小值为 \(a\),右半边最小值为 \(b\)
左边取 \(a\) 和尽可能大的值,右边取 \(b\) 和尽可能小的值,因为 \(n\) 只给了100,可以 \(n^2\) 暴力,省去了分类讨论的麻烦
int n, m, s[maxn];
bool vis[maxn];
void solve() {
int a, b;
cin >> n >> a >> b;
for(int i=1;i<=n;i++) vis[i] = 0;
s[1] = a; vis[a] = 1;
for(int i=2;i<=n/2;i++){
for(int j=n;j>=a;j--){
if(!vis[j] && j != b){
s[i] = j;
vis[j] = 1;
break;
}
if(j == a){
cout << "-1\n";
return;
}
}
}
for(int i=n/2+1;i<=n;i++){
for(int j=b;j>=1;j--){
if(!vis[j]){
s[i] = j;
vis[j] = 1;
break;
}
if(j == 1){
cout << "-1\n";
return;
}
}
}
for(int i=1;i<n;i++) cout << s[i] << ' ';
cout << s[n] << '\n';
}
\(\quad\)
C. Chat Ban
题意:你要依次发送 \(2k-1\) 条消息,每条消息的字符个数分别为 1,2,3...k,k-1...2,1,输入达到 \(x\) 个字符时就会终止,问能发出几条消息(发一半也算)
分类讨论写比较麻烦,写一个计算发送前 \(x\) 条消息需要的字符的函数 \(f(x)\),然后二分答案即可。
ll k, x;
ll cntk(ll s){
if(s <= k) return s*(s+1)/2;
else{
ll r = (2*k-1-s+k) * (s-k) / 2;
return k*(k+1)/2 + r;
}
}
void solve() {
cin >> k >> x;
ll l = 1, r = 2*k-1;
while(l < r){
ll mid = (l+r) / 2;
if(cntk(mid) >= x){
r = mid;
}else{
l = mid+1;
}
}
cout << l << '\n';
}
\(\quad\)
D. X-Magic Pair
题意:有两个数字 \(a,b\),可以一次操作将 \(a=|a-b|\) 或 \(b=|a-b|\),问能否凑出 \(x\)(\(a\) 或 \(b\) 等于 \(x\) 都可,可以进行任意次操作)
用手模拟一下可以发现,当前为 \(a,b\)(假设 \(a\lt b\)),不论怎么操作一定会经过 \(a,b-a\) 这个状态
那么可以用类似辗转相除法的办法处理,如果 \(x\) 在 \(a,b\) 之间且 \(x\equiv b\pmod a\),则可行,否则变化为 \(b\%a,a\) 继续;
记得判余数为 \(0\) 的情况
bool judge(ll a, ll b, ll x){
if(a == 0 || b == 0) return 0;
if(x == a || x == b) return 1;
if(x > b) return false;
if(b%a == x%a) return 1;
return judge(b%a, a, x);
}
void solve() {
ll a, b, x;
string sout[2] = {"NO", "YES"};
cin >> a >> b >> x;
if(a > b) swap(a, b);
cout << sout[judge(a, b, x)] << '\n';
}
\(\quad\)
E. Messages
题意:有 \(n\) 个人,每个人有一个期望读到的信 \(m_i\),和他将读的信数 \(k_i\),每个人都会在信堆里等概率地随机抽取 \(k_i\) 封信阅读(不会取走)。现在你可以选择将哪些信放进信堆,使得读到想读信的人数期望最大。
第一反应想到了二分答案,因为确定了要放信的数量后比较容易judge
考虑怎么计算:选择一共放 \(m\) 封信时的最大期望
对于每个人来说,它读到想要的信的概率是 \(\frac{min(k_i, m)}{m}\),我们可以对于每一封信,统计想读它的人的 \(\min(k_i, m)\) 之和(记为 \(tot\)),那么选这封信的收益就是 \(\frac{tot}{m}\) ;
将信按照 \(tot\) 排序后选取前 \(m\) 封就可以计算出最大期望了
然后很显然地发现,答案不具有单调性,二份答案X
输入数据保证了 \(k\le20\),用手模拟了一下极端情况,大胆猜测选 \(20\) 封左右信时收益达到阈值,再多选收益一定更低
然后暴力跑了 \(m\le50\) 的情况,就过了!
int n;
int a[maxn], b[maxn];
bool apr[maxn];
vector<int> all;
ll tot[maxn];
double ans[maxn];
bool cmpf(ll x, ll y){ return x > y;}
double judge(int m){
for(int i=1;i<=n;i++){
tot[a[i]] += min(b[i], m);
}
sort(tot+1, tot+200000+1, cmpf);
ll fz = 0;
for(int i=1;i<=m;i++){
fz += tot[i];
}
int i = 1;
while(tot[i] > 0) tot[i++] = 0;
return fz * 1.0 / m;
}
void solve() {
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i] >> b[i];
if(!apr[a[i]]){
all.push_back(a[i]);
apr[a[i]] = 1;
}
}
int p = 1;
for(int i=1;i<=min(50, n);i++){
ans[i] = judge(i);
if(ans[i] > ans[p]) p = i;
}
cout << p << '\n';
for(int i=1;i<=n;i++) tot[a[i]] += min(b[i], p);
vector<pair<int, int>> vp;
for(int v:all) vp.push_back({tot[v], v});
sort(vp.begin(), vp.end());
bool fg = false;
while(p--){
if(fg) cout << ' ';
else fg = 1;
cout << vp.back().second;
vp.pop_back();
}
cout << '\n';
}