题解(HAUT2019级新生周赛(六)-张伟涛专场)
问题 A: 棋盘上的米粒
模拟签到题
从第一个格子开始放米粒,如果米粒数量为正数,就代表当前位置的格子是可以放上米粒的,放不放满无所谓,维护一个 k k k 代表放到第几个格子。
C++版代码
#include <iostream>
using namespace std;
typedef long long LL;
LL n;
int sol() {
int k = 0;
LL tem = 1;
while (n > 0) {
k++;
n -= tem;
tem *= 2;
}
return k;
}
int main() {
while (cin >> n) {
cout << sol() << "\n";
}
return 0;
}
C语言版代码
#include <stdio.h>
typedef long long LL;
int main() {
LL n, tem, k;
while (scanf("%lld", &n) != EOF) {
tem = 1; k = 0;
while (n > 0) {
n -= tem;
tem *= 2;
k++;
}
printf("%lld\n", k);
}
return 0;
}
问题 B: 不一样的蛋糕
二分基础题
二分切出的蛋糕的大小,然后检验大小是否合格,不断夹逼最优值即可
C++版代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long LL;
int n, m;
vector<LL> a;
LL sum;
void init() {
sum = 0;
a.resize(n);
}
bool che(double mid) {
int num = 0;
for (int i = 0; i < n; ++i) {
num += (int)(a[i] / mid);
}
return num >= m;
}
double sol() {
init();
for (int i = 0; i < n; ++i) cin >> a[i], sum += a[i];
double l = 0, r = sum, ans = r;
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if (che(mid)) ans = mid, l = mid;
else r = mid;
}
return ans;
}
int main() {
while(cin >> n >> m) {
printf("%.2f\n", sol());
}
return 0;
}
C语言版代码
#include <stdio.h>
#define maxn 10005
#define LL long long
int n, m;
LL a[maxn], al;
LL sum;
void init() {
sum = 0;
al = 0;
}
int che(double mid) {
int num = 0;
for (int i = 0; i < n; ++i) {
num += (int)(a[i] / mid);
}
return num >= m;
}
double sol() {
init();
for (int i = 0; i < n; ++i) scanf("%lld", &a[i]), sum += a[i];
double l = 0, r = sum, ans = r;
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if (che(mid)) ans = mid, l = mid;
else r = mid;
}
return ans;
}
int main() {
while (~scanf("%d%d", &n, &m)) {
printf("%.2f\n", sol());
}
return 0;
}
问题 C: HJ病毒
快速幂基础题
由1+1=2我们可以知道: 答案是
n
×
2
t
n×2^t
n×2t
然后用快速幂算出 2 t 2^t 2t 即可,不要忘记取模
C++版代码
#include <iostream>
#define mod 1000000007
using namespace std;
typedef long long LL;
LL n, t;
LL q_w(LL x, LL t) {
LL ans = 1;
while (t) {
if (t & 1) ans *= x, ans %= mod;
x *= x;
x %= mod;
t >>= 1;
}
return ans;
}
int main() {
while (cin >> n >> t) {
cout << n * q_w(2, t) % mod << "\n";
}
return 0;
}
C语言版代码
#include <stdio.h>
#define mod 1000000007
typedef long long LL;
LL n, t;
LL q_w(LL x, LL t) {
LL ans = 1;
while (t) {
if (t & 1) ans *= x, ans %= mod;
x *= x;
x %= mod;
t >>= 1;
}
return ans;
}
int main() {
while (~scanf("%lld%lld", &n, &t)) {
printf("%lld\n", n * q_w(2, t) % mod);
}
return 0;
}
问题 D: 最小的整数
一个奇数如果旁边有偶数的话,它就可以和旁边的偶数换位置,偶数亦然。那么如果一个序列里有多个数字,但是只有一个是奇数,剩下的全是偶数的话,奇数就可以出现在任何它想去的位置。也就是说奇数和偶数之间的相对位置是可以随意设置的,但是奇数不能和奇数换位置,那么奇数之间的相对位置就是固定的,偶数亦然。
那么把序列里的奇数和偶数分离开,然后把两个序列按字典序最小合并到一个序列里就是答案了。
C++版代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#define maxn 300005
using namespace std;
const int inf = 0x3f3f3f3f;
vector<int>b[2];
string ans, a;
void init() {
b[0].clear();
b[1].clear();
ans.clear();
}
int main() {
while (cin >> a) {
init();
for (int i = 0; i < a.size(); i++) {
int x = a[i] - '0';
b[x % 2].push_back(x);
}
vector<int>::iterator i = b[0].begin(), j = b[1].begin();
while (i != b[0].end() || j != b[1].end()) {
int tem = inf, f = -1;
if (i != b[0].end() && *i < tem) tem = *i, f = 0;
if (j != b[1].end() && *j < tem) tem = *j, f = 1;
ans += tem + '0';
(f ? j : i)++;
}
cout << ans << "\n";
}
cout << 0;
return 0;
}
C语言版代码
#include <stdio.h>
#include <string.h>
#define maxn 300005
const int inf = 0x3f3f3f3f;
int b[2][maxn], bl[2];
char ans[maxn], a[maxn];
int ansl = 0, al = 0;
void init() {
bl[0] = bl[1] = 0;
ansl = 0;
al = strlen(a);
}
int main() {
while (~scanf("%s", a)) {
init();
for (int i = 0; i < al; i++) {
int x = a[i] - '0';
b[x % 2][bl[x % 2]++] = x;
}
int i = 0, j = 0;
while (i < bl[0] || j < bl[1]) {
int tem = inf, f = -1;
if (i < bl[0] && b[0][i] < tem) tem = b[0][i], f = 0;
if (j < bl[1] && b[1][j] < tem) tem = b[1][j], f = 1;
ans[ansl++] = tem + '0';
if (f) j++;
else i++;
}
ans[ansl] = '\0';
printf("%s\n", ans);
}
return 0;
}
问题 E: 第k大分数(简单版)
暴力基础题
先把所有的形成的分数保存起来,然后按从大到小快排一遍,最后输出第 k k k 个即可
C++版代码
#include <iostream>
#include <algorithm>
#include <vector>
#define _for(i, a) for(int i = 0; i < (a); ++i)
#define _rep(i, a, b) for(int i = (a); i <= (b); ++i)
using namespace std;
typedef long long LL;
struct poi {
LL n, d;
poi() {}
poi(int n, int d) :n(n), d(d) {}
bool operator < (const poi &b) const {
return 1.0 * n / d > 1.0 * b.n / b.d;
}
};
LL n, k;
vector<poi> a;
void sol() {
_for(i, n) {
LL tem;
cin >> tem;
_rep(i, 1, tem - 1) a.push_back(poi(i, tem));
}
sort(a.begin(), a.end());
cout << a[k - 1].n << "/" << a[k - 1].d << "\n";
}
int main() {
while (cin >> n >> k) {
sol();
}
return 0;
}
C语言版代码
#include <stdio.h>
#include <stdlib.h>
#define _for(i, a) for(int i = 0; i < (a); ++i)
#define _rep(i, a, b) for(int i = (a); i <= (b); ++i)
#define maxn 1000005
typedef long long LL;
typedef struct {
LL n, d;
}poi;
LL n, k;
poi a[maxn];
int al = 0;
int cmp(const void*a, const void*b) {
poi aa = *(poi*)a, bb = *(poi*)b;
if (1.0 * aa.n / aa.d - 1.0 * bb.n / bb.d < 0) return 1;
else if (1.0 * aa.n / aa.d - 1.0 * bb.n / bb.d > 0) return -1;
else return 0;
}
void sol() {
_for(i, n) {
LL tem;
scanf("%lld", &tem);
_rep(i, 1, tem - 1) {
a[al].n = i;
a[al].d = tem;
al++;
}
}
qsort(a, al, sizeof(poi), cmp);
printf("%lld/%lld\n", a[k - 1].n, a[k - 1].d);
}
int main() {
while (~scanf("%lld%lld", &n, &k)) {
sol();
}
return 0;
}
问题 F: 第k大分数(加强版)
二分提高题
因为给出的 p i p_i pi 是质数,所以每一个分数对应的小数都是独一无二的,就像只能有 1 2 \frac{1}{2} 21,而不会有 2 4 \frac{2}{4} 42 一样。
而且当分母一定时,分子的大小顺序就是对应的分数的大小顺序,就像 4 5 > 3 5 > 2 5 > 1 5 \frac{4}{5}>\frac{3}{5}>\frac{2}{5}>\frac{1}{5} 54>53>52>51
那么如果我们可以知道大于小数 m i d mid mid ,且分母为 P P P 的分数的个数 c n t cnt cnt 的话,我们就可以枚举 n n n 个分母求得所有大于 m i d mid mid 的分数的个数 c n t cnt cnt ,通过比较这个 c n t cnt cnt 和 k k k 的大小就可以知道 m i d mid mid 是太大了还是太小了,二分夹逼,直到最后 c n t = k cnt = k cnt=k 。
那么怎么获取这个 c n t cnt cnt 呢?我们知道当分母为 5 5 5 时,构成的分数是: 4 5 , 3 5 , 2 5 和 1 5 \frac{4}{5},\frac{3}{5},\frac{2}{5}和\frac{1}{5} 54,53,52和51 ,假设 m i d mid mid 此时为 0.5 0.5 0.5 ,那么把 0.5 0.5 0.5 转化为一个分数且分母为 5 5 5 的最简单的方式就是分子分母同时乘以 5 5 5 : 0.5 = 5 × 0.5 5 0.5=\frac{5\ ×\ 0.5}{5} 0.5=55 × 0.5。所以得到的分数就是 2.5 5 \frac{2.5}{5} 52.5,显然,分子大于 2.5 2.5 2.5 的有 2 2 2 个,即 ( 5 − ⌊ 2.5 ⌋ − 1 5-\lfloor2.5\rfloor -\ 1 5−⌊2.5⌋− 1)(符号意为向下取整);同理当分母为 P P P 时,大于 m i d mid mid 的分数的个数就是 P − ⌊ P ⋅ m i d ⌋ − 1 P-\lfloor P\ ·\ mid \rfloor-1 P−⌊P ⋅ mid⌋−1,。
而每一次求 c n t cnt cnt 时我们都保存最接近 m i d mid mid 的分数的分子和分母,那么二分结束时的分子和分母就是要输出的答案了。
C++版代码
#include <iostream>
#include <algorithm>
#define maxn 1005
#define _for(i, a) for(LL i = 0; i < (a); ++i)
using namespace std;
typedef long long LL;
LL n, k;
LL P[maxn];
LL Numerator, Denominator; //最接近mid的分子和分母
LL find(double mid) {
LL cnt = 0;
_for(i, n) {
LL num = mid * P[i]; //向下取整
cnt += P[i] - num - 1; //比mid小的分数
if (Numerator == -1 && Denominator == -1 || Numerator * P[i] > (num + 1) * Denominator) { //保存分子和分母
Numerator = num + 1;
Denominator = P[i];
}
}
return cnt;
}
void sol() {
cin >> n >> k;
_for(i, n) cin >> P[i];
double l = 0, r = 1;
while (1) {
Numerator = -1, Denominator = -1;
double mid = (l + r) / 2;
LL cnt = find(mid);
if (cnt == k) break;
if (cnt < k) r = mid;
else l = mid;
}
cout << Numerator << "/" << Denominator << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//freopen("in.txt", "r", stdin);
sol();
return 0;
}
C语言版代码
#include <stdio.h>
#include <stdlib.h>
#define maxn 1005
#define _for(i, a) for(LL i = 0; i < (a); ++i)
typedef long long LL;
LL n, k;
LL P[maxn];
LL Numerator, Denominator; //最接近mid的分子和分母
LL find(double mid) {
LL cnt = 0;
_for(i, n) {
LL num = mid * P[i]; //向下取整
cnt += P[i] - num - 1; //比mid小的分数
if (Numerator == -1 && Denominator == -1 || Numerator * P[i] > (num + 1) * Denominator) { //保存分子和分母
Numerator = num + 1;
Denominator = P[i];
}
}
return cnt;
}
void sol() {
scanf("%lld%lld", &n, &k);
_for(i, n) scanf("%lld", &P[i]);
double l = 0, r = 1;
while (1) {
Numerator = -1, Denominator = -1;
double mid = (l + r) / 2;
LL cnt = find(mid);
if (cnt == k) break;
if (cnt < k) r = mid;
else l = mid;
}
printf("%lld/%lld\n", Numerator, Denominator);
}
int main() {
sol();
return 0;
}