下午
T1:
小明是一名教官,他正在执教n个学员。学员从左到右排成一排,编号为1~n,每个学员都有一个独一无二的身高Hi,小明想把学员们从矮到高排好序。他排序的方法是:每一次将所有的人划分为尽量少的连续部分,使得每一部分的人的高度都是单调下降,然后将其中所有不少于2 个人的区间全部翻转。重复执行以上操作,最后使得所有人的高度全部单调上升。 小明在划分并翻转完第一次之后,觉得这个工作可能过于繁琐,所以他想找你帮他计算,他一共要执行多少次翻转操作才能把所有人排好序,如果太麻烦他就要调整方案。 巧合的是,他第一次划分出来的所有区间的长度都是偶数。
n <= 1e5
题中给出了一个暗示:小明在翻转第一轮之后发现了麻烦。这实际上是因为第一次翻转之后序列变成了若干个连续单调上升区间,在这之后只有这些区间的交界处可能满足一个逆序关系。这样,之后的操作就全部变成了同冒泡排序一样的两两交换。考虑此时序列中的一个数x:当且仅当x前面的一个数比它大的时候会被交换一次,而因为每次只能交换两个相邻的数,x必然会与它前面的每个更大的数都相遇一次。也就是说,第一次翻转之后的序列中的“每个数会被交换的次数”是它之前比它大的数的个数,那么总的答案就是第一次翻转的次数加上当前序列的逆序对个数。
代码:
- #include <iostream>
- #include <cstring>
- #include <cstdio>
- #define BUG puts("$$$")
- #define maxn 100010
- using namespace std;
- void open_file(string s) {
- string In = s + ".in", Out = s + ".out";
- freopen(In.c_str(), "r", stdin);
- freopen(Out.c_str(), "w", stdout);
- }
- template <class T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- }
- int a[maxn], b[maxn];
- int n;
- int bit[maxn];
- inline int lowbit(int x) { return x & (-x); }
- void modify(int x) {
- while (x <= n) ++bit[x], x += lowbit(x);
- }
- int query(int x) {
- int sum = 0;
- while (x) sum += bit[x], x -= lowbit(x);
- return sum;
- }
- int main() {
- open_file("instructor");
- read(n);
- for (int i = 1; i <= n; ++i) read(b[i]);
- // b[n + 1] = -1;
- long long ans = 0;
- for (int i = 1; i <= n; ++i) {
- int cur = i;
- while (b[i + 1] < b[i] && i < n) ++i;
- for (int j = cur; j <= i; ++j) { // BUG;
- a[j] = b[cur + i - j];
- }
- ++ans;
- }
- for (int i = n; i; --i) {
- ans += query(a[i]);
- modify(a[i]);
- }
- printf("%lld", ans);
- }
T2:
定义f(x) = sqrt(x),f_1(x) = f(x),f_y(x) = f(f_(y-1)(x)),给定一个数n,找出使得f_y(x) = 1的最小的y。特别地,若y大于等于6时输出“TAT”。
第一想法高精度乘法 + 二分,然而不会高精度乘法。花了一个半小时钻研python怎么写……最后语法对了,死在了特判上。
先放python代码,特判0和1的情况即可。
- from math import *
- f = open("sqrt.in", mode = "r");
- k = open("sqrt.out", mode = "w")
- que = f.readlines()
- que = [int(x) for x in que]
- f.close()
- for a in que:
- if a == 0:
- print >> k, 'TAT';
- continue;
- if (a == 1):
- print >> k, '0';
- continue;
- f = 0;
- for i in range (1, 6):
- a = int(sqrt(a));
- if a == 1:
- print >> k, i;
- f = 1;
- break;
- if (f == 0) :
- print >> k, 'TAT';
- k.close()
实际上并不用写高精度。因为最多开5遍根号,那么满足条件的最大整数应是2^(2^5) - 1 = 2^32 - 1,刚好是unsigned int的最大值。用字符串读入给定数据,若数据大于2^32 - 1直接输出"TAT",否则暴力二分开根就好了。本来听郝巨说完正解后打了C++,然而没有特判1……
代码:
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <cmath>
- #define ul unsigned int
- #define BUG puts("$$$")
- using namespace std;
- char s[111], maxs[111];
- int main() {
- freopen("sqrt.in", "r", stdin);
- freopen("sqrt.out", "w", stdout);
- ul a;
- a = (ul)(1) << 31;
- a -= 1, a *= 2, a += 1;
- for (int i = 9; i >= 0; --i) {
- maxs[i] = (a % 10) + 48;
- a /= 10;
- }
- while (scanf("%s", s) != EOF) {
- int len = strlen(s);
- if (len > 10 || (len == 1) && (s[0] == '0')) {
- puts("TAT");
- continue;
- } else if (len == 10 && strcmp(s, maxs) > 0) {
- puts("TAT");
- continue;
- }
- if ((len == 1) && s[0] == '1') {
- puts("0");
- continue;
- }
- a = 0;
- for (int i = 0; i < len; ++i) a = a * 10 + (s[i] ^ 48);
- for (int i = 1; i < 6; ++i) {
- a = sqrt(a);
- if (a == 1) {
- printf("%d\n", i);
- break;
- }
- }
- }
- return 0;
- }