第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)
题目总结
A题 切蛋糕
题目信息
解题思路
如果我们将 1/k展开到二进制的形式,那么就可以计算出 需要 多少块1/(2^i) 蛋糕,因此就可以创建出分割的方案,最后进行打包。
本题还有一个有趣的地方是,知道需要多少块的蛋糕后,还需要计算出需要切割多少次,
假如说需要 1个四等分的, 1个八等分的,3个十六等分的,4个三十二等分的
那么 十六等分的需要切割次数
⌈
\lceil
⌈
4
2
\frac{4}{2}
24
⌉
\rceil
⌉=2,可以得到四个三十二等分
八等分需要的切割次数
⌈
\lceil
⌈
十
六
等
分
切
割
次
数
+
十
六
等
分
原
需
要
数
额
2
\frac{十六等分切割次数+十六等分原需要数额}{2}
2十六等分切割次数+十六等分原需要数额
⌉
\rceil
⌉
也就是说,计算切割次数,需要受到前面切割次数信息的影响。
二进制小数点可以直接使用double进行计算,精度是足够的
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const double esp = 1e-10;
const int N = 20;
LL a[N];
LL k;
bool b[N], flag;
int opt[N];
int cut_opt[N];
bool Check(int n)
{
static LL l = a[10] - k, r = a[10] + k;
double sum = 0;
for (int i = 0; i < n; i ++ )
{
if (!b[i]) continue;
if (i <= 10)
sum += (a[10 - i]) * k;
else
sum += k * 1.0 / a[i - 10];
}
if (n <= 10) // (a[10 - i]) * k
{
static int i; i = n;
if (sum + (a[10 - i]) * k >= l && sum + (a[10 - i]) * k <= r)
{
flag = true;
return true;
}
else if (sum + (a[10 - i]) * k < l)
return true;
return false;
}
else // 1.0 / a[i - 10];
{
static int i; i = n;
if (sum + k * 1.0 / a[i - 10] >= l && sum + k * 1.0 / a[i - 10] <= r)
{
flag = true;
return true;
}
else if (sum + k * 1.0 / a[i - 10] < l)
return true;
return false;
}
}
int main()
{
a[0] = 1;
for (int i = 1; i < N; i ++ )
a[i] = (a[i - 1] << 1);
memset(b, false, sizeof b);
cin >> k;
if (k == 1) // 注意
{
printf("1\n");
printf("2 1 0\n");
return 0;
}
flag = false;
double tmp = 1.0 / k;
for (int i = 1; i <= 15; i ++ )
{
if (abs(tmp - 1.0 / a[i]) <= esp)
{
b[i] = true;
break;
}
else if (tmp >= 1.0 / a[i])
{
tmp -= 1.0 / a[i];
b[i] = true;
}
else
{
b[i] = false;
}
}
/*
for (int i = 0; i < 15; i ++ )
{
printf("i=%d, b[%d]=%d\n", i, i, b[i]);
}
*/
for (int i = 15; i >= 1; i -- )
{
opt[i] = b[i] * k;
}
int outcnt = k;
memset(cut_opt, 0, sizeof cut_opt);
for (int i = 14; i >= 1; i -- )
{
cut_opt[i - 1] = (opt[i] + 1) / 2;
opt[i - 1] += ((opt[i] + 1) / 2);
outcnt += cut_opt[i - 1];
}
cout << outcnt << endl;
// 需要切割的
for (int i = 0; i < 15; i ++ )
{
if (cut_opt[i] != 0)
{
for (int j = 1; j <= cut_opt[i]; j ++ )
printf("1 %d\n", i);
}
}
int cnt = 0;
vector<int> pick;
// b[i] 打包的
for (int i = 0; i < 15; i ++)
if (b[i])
{
cnt += b[i];
pick.push_back(i);
}
for (int i = 1; i <= k; i ++ )
{
printf("2 %d", cnt);
for (int j = 0; j < pick.size(); j ++ )
printf(" %d", pick[j]);
putchar('\n');
}
return 0;
}
小宝的幸运数组
题目信息
解题思路
需要注意子序列(如最长上升子序列LIS)和子串的区别
这里子数组的定义和子串类似
这里关键是对 原数组的前缀和 对k进行求余,得到的余数k,记录下来他的位置,然后对余数进行分析
余数 = 0,可以直接将下边作为一种解,进行更新答案
余数 = 1, res = min(res, max - min); 也就是说最长的,min + 1 ~ max这一段子序列是可行的,而且是余数等于1情况下最长的。
…
余数 = k-1
经过分析,发现子需要记录下来 余数中最先遇到的 min下标,与最后遇到的 max下标即可。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
const int INF = 0x3f3f3f3f;
int a[N], b[N];
int n, k;
LL q[N], sum[N];
int main()
{
int T; cin >> T;
while (T -- )
{
cin >> n >> k;
sum[0] = 0;
memset(a, -1, sizeof a);
memset(b, -1, sizeof b);
int res = -1;
for (int i = 1; i <= n; i ++ )
{
scanf("%lld", &q[i]);
sum[i] = (sum[i - 1] + q[i]) % k;
if (a[sum[i]] == -1)
{
a[sum[i]] = i;
}
b[sum[i]] = i;
if (sum[i] == 0)
{
res = max(res, i);
}
else
{
res = max(res, b[sum[i]] - a[sum[i]]);
}
}
/// cout << "###########\n";
if (res == 0) puts("-1");
else cout << res << endl;
}
return 0;
}
C题上进的凡凡
题目信息
解题思路
关键的思路就是使用dp的思想
以 a[i] 为开始的上进数组个数 f[i]
当 a[i]<=a[i+1]
f[i] = 1 + f[i + 1]
否则
f[i] =1
最后累加求和即可
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, INF = 0x3f3f3f3f;
int a[N], f[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
scanf("%d", &a[i]);
LL res = 0;
a[n + 1] = -INF;
for (int i = n; i >= 1; i -- )
{
f[i] = 1;
if (a[i] <= a[i + 1])
f[i] += f[i + 1];
res += f[i];
}
cout << res << endl;
return 0;
}
D题Seek the Joker I
原题信息
解题思路
本题是博弈论方向的题目,对于此类问题,我们需要从 必定失败的局面出发,推导出可以让对手达到必定失败的局面-》必胜
以及无法让对手达到必定失败的局面->必败,最后找到规律,有时间可以证明,写出结果即可
AC代码
#include <bits/stdc++.h>
using namespace std;
bool Check(int n, int k)
{
if (n % (k + 1) == 0) return false;
return true;
}
int main()
{
int t; cin >> t;
while (t -- )
{
static int n, k;
scanf("%d%d", &n, &k);
if (Check(n - 1, k))
{
/// cout << "win" << endl;
puts("yo xi no forever!");
}
else
{
/// cout << "lose" << endl;
puts("ma la se mi no.1!");
}
}
return 0;
}
E Seek the Joker II
原题信息
解题思路
方法与上题目相同,但是推导比较易错,** 最终的规律可以使用dp的形式表达出来 **
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3000010;
bool st[N + 1000000];
LL a[N + 1000000];
bool Check(int x, int y)
{
if (a[x] == -1) return true; // win
else if (a[x] == y) return false; // lose
return true; // win
}
int main()
{
memset(a, -1, sizeof a);
memset(st, false, sizeof st);
a[0] = 0;
int cur = 1;
for (int i = 1; i < N; i ++ )
{
if (st[i])
{
/// if (i <= 20) printf("I=%d, st\n\ta[i]=", i);
a[i] = -1;
}
else
{
/// if (i <= 20) printf("I=%d, No_St\n\ta[i]=", i);
a[i] = i + cur;
if (i + cur <= N)
st[i + cur] = true; // 注意这个
cur ++;
}
/// if (i <= 20) cout << a[i] << endl;
}
/*
cout << a[0] << endl;
for (int i = 0; i <= 9; i ++ )
{
printf("i=%d, a[i]=%lld\n", i, a[i]);
}
*/
int t; cin >> t;
int x, n, mina, maxb;
while (t -- )
{
scanf("%d%d", &n, &x);
mina = x - 1, maxb = n - x;
if (mina > maxb) swap(mina, maxb);
/// printf("a=%d, b=%d\n", mina, maxb);
if (Check(mina, maxb))
/// cout << "\t##win\n",
cout << "yo xi no forever!" << endl;
else
/// cout << "\t##lose\n",
cout << "ma la se mi no.1!" << endl;
}
return 0;
}
成绩查询ing
原题信息
解题思路
思路很清晰的模拟题,就是排序,查找,不会stl顶多就是再加一个二分
但是 时间很卡 应该使用快读 来解决时间的问题,考试的时候没有板子,就T了
AC代码
#include <bits/stdc++.h>
using namespace std;
const int BASE1 = 10, BASE2 = 10000;
map<string, int> t1;
set<string> t2[101];
int n, m, opt;
inline int IntRead()//内联函数稍微快一点点
{
char ch = getchar();
int s = 0, w = 1;
while(ch < '0' || ch > '9')
{
if(ch == '-') w = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0',
ch = getchar();
}
return s * w;
}
inline string StringRead()
{
string str;
char s = getchar();
//处理多余回车或空格
while (s == ' ' || s == '\n' || s == '\r')
{
s = getchar();
}
//不断读入直到遇到回车或空格
while (s != ' ' && s != '\n' && s != '\r')
{
str += s;
s = getchar();
}
return str;
}
inline void StringWrite(std::string str)
{
int i = 0;
while (str[i] != '\0')
{
putchar(str[i]), i++;
}
}
inline void IntWrite(int s)
{
int k = 0, len = 0;
if (s == 0)
putchar('0');
while (s)
{
k = k * 10 + s % 10;
s /= 10, len++;
}
for (int i = 0;i < len;i++)
{
putchar(k % 10 + '0');
k /= 10;
}
}
int main()
{
cin >> n;
string _name; int _id, _sex, _grade;
for (int i = 1; i <= n; i ++ )
{
_name = StringRead();
_grade = IntRead(), _sex = IntRead(), _id = IntRead();
t2[_grade].insert(_name);
t1[_name] = _sex + BASE1 * _grade + BASE2 * _id;
}
cin >> m;
while (m -- )
{
opt = IntRead();
if (opt == 1)
{
_name = StringRead();
static int num;
num = t1[_name];
printf("%d %d %d\n", num % BASE2 / BASE1, num / BASE2, num % BASE1);
}
else
{
_grade = IntRead();
static set<string>::iterator it;
for (it = t2[_grade].begin(); it != t2[_grade].end(); it ++ )
{
StringWrite(*it);
putchar('\n');
}
}
}
return 0;
}
/*
5
N 28 2 7475
UN 83 2 27550
EXF 28 2 17298
OVYNH 51 2 14827
XNV 53 1 7591
2
1
XNV
2
28
*/
G 贪吃的派蒙
原题信息
解题思路
关键就是改变其他人领取的数量,来使得派蒙的前一个人刚刚好领完,这是一个 数学 区间 + 不等式的问题
最后注意上下取整就好
H数羊
原题信息
解题思路
直接打表找规律,我是证明半天没出来,最后快没时间了,才打的表,血亏
AC代码
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
typedef long long LL;
int fun(int x, int y)
{
if (x == 1 && y == 0) return 2;
else if (x == 0 && y >= 0) return 1;
else if (x >= 2 && y == 0) return x + 2;
else return fun(fun(x - 1, y), y - 1);
}
int qmi(LL a, int k, int MOD)
{
LL res = 1;
while (k)
{
if (k & 1)
{
res = (res * a) % MOD;
}
a = a * a % MOD;
k >>= 1;
}
return res;
}
int cal(int x, int y)
{
if(x == 1) return 2;
if (y == 0)
{
return (x + 2) % MOD;
}
else if (y == 1)
{
return (x * 2) % MOD;
}
else
{
return qmi(2, x, MOD);
}
}
int main()
{
/*
for (int i = 1; i <= 18; i ++ )
{
printf("i=%d, ", i);
for (int j = 0; j <= 2; j ++ )
{
printf("%d, ", fun(i, j));
}
cout << endl;
}*/
int t; cin >> t;
while (t -- )
{
static int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", cal(x, y));
}
}
I 买花
原题信息
解题思路
直接就是 2 i 2^i 2i 的前缀和,但是要注意 不可以是一天的
AC代码
#include <bits/stdc++.h>
using namespace std;
int a[200];
bool Check(int x)
{
for (int i = 2; i <= 15; i ++ )
{
if (x % a[i] == 0)
return true;
}
return false;
}
int main()
{
a[1] = 1;
for (int i = 2; i <= 15; i ++ )
{
a[i] = (a[i - 1] << 1);
}
for (int i = 1; i <= 15; i ++ )
a[i] = a[i - 1] + a[i];
int t; cin >> t;
while (t -- )
{
static int n;
scanf("%d", &n);
if (Check(n))
{
puts("YE5");
}
else
{
puts("N0");
}
}
return 0;
}
J 这是一题简单的模拟
原题信息
解题思路
直接就是模拟就行了,关键是题目描述的不太清楚,多读几遍 就好了
对于一个可行的道路,必须要经过所有的城市,是N个,不是n个
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 310, INF = 0x3f3f3f3f;
int n, m;
int e[N][N];
bool st[N];
int k;
int path[N * 1000];
int res = INF;
int main()
{
cin >> n >> m;
memset(e, 0x3f, sizeof e);
for (int i = 1; i <= m; i ++ )
{
static int x, y, w;
scanf("%d%d%d", &x, &y, &w);
e[x][y] = e[y][x] = w;
}
cin >> k;
res = INF;
int tmpres;
while (k -- )
{
tmpres = 0;
static int n2; scanf("%d", &n2);
for (int i = 1; i <= n2; i ++ )
scanf("%d", &path[i]);
if (n2 != n)
continue;
n2 ++; path[n2] = 0;
path[0] = 0;
memset(st, false, sizeof st);
for (int i = 1, pre, cur; i <= n2; i ++ )
{
pre = path[i - 1], cur = path[i];
if (st[cur] == true)
{
tmpres = INF;
break;
}
st[cur] = true;
if (e[pre][cur] == INF)
{
tmpres = INF;
break;
}
tmpres += e[pre][cur];
}
res = min(res, tmpres);
}
if (res == INF) res = -1;
cout << res << endl;
return 0;
}
K黑洞密码
原题信息
解题思路
就是一个简单的字符串问题,和模拟相关,注意 字母谁在谁的后面,特判就好
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int num[N];
char str[N];
char s[N];
int nidx, cidx;
char Change(char ch, int num)
{
if (ch >= 'A' && ch <= 'Z')
{
if (ch + num <= 'Z') return ch + num;
else return ch + num - 'Z' - 1 + 'b';
}
else
{
if (ch + num <= 'z') return ch + num;
else return ch + num - 'z' - 1 + 'B';
}
}
int main()
{
nidx = cidx = 0;
cin >> str;
for (int i = 0; i < 32; i ++ )
{
if (str[i] >= '0' && str[i] <= '9')
{
num[nidx ++] = str[i] - '0';
}
else
{
s[cidx ++] = str[i];
}
}
for (int i = 0; i < 16; i ++ )
{
s[i] = Change(s[i], num[i]);
}
for (int i = 0; i < 4; i ++ )
{
for (int j = 3 + i * 4; j >= i * 4; j -- )
{
cout << s[j];
}
}
cout << endl;
return 0;
}
L建立火车站
原题信息
解题思路
求最大值最小,典型的 二分
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
LL a[N];
LL n, k;
bool Check(LL l, LL k)
{
if (l == 0)
{
for (int i = 2; i <= n; i ++)
{
if (a[i] != a[i - 1]) return false;
}
return true;
}
LL dist;
for (int i = 2; i <= n; i ++ )
{
dist = a[i] - a[i - 1];
k = k - (dist - 1) / l;
if (k < 0) return false;
}
return true;
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++ )
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
LL l, r, mid;
l = 0, r = 1e12+10;
while (l < r)
{
mid = l + r >> 1;
if (Check(mid, k))
{
r = mid;
}
else
{
l = mid + 1;
}
}
cout << l << endl;
return 0;
}