【2019.8.22】测试题目订正
上午
T3:
输入:
第1 行是质数中各位数字之和;
第2 行是方阵左上角中的数字。
输入文件只有2 行,保证测试数据一定有解。
需要技巧的爆搜。尽可能早地剪掉多余状态,就需要优先枚举限制较多的状态。
考虑末行末列的数字必须是{1, 3, 5, 9},那么先预处理由这几个数字构成的质数,填入最后一行和最后一列。注意到此时两条对角线的首尾已知,还可以预处理已知首尾的素数扔进对角线里。之后第2、4行和2、4列都有三位已知,因此考虑再预处理出已知第2、4、5位的质数。把这四个数填好之后,最后的两个数字已经确定了,判断是否合法即可。
(以下是迄今以来写过最恶心的代码……)
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <algorithm>
- #include <vector>
- #define p_b push_back
- #define maxn 100000
- 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);
- }
- int sum, sq[6][6];
- int prime[maxn];
- bool del[maxn + 100], isp[10][10][10][10][10];
- void euler() {//欧拉筛
- for (int i = 2; i < maxn; ++i) {
- if (!del[i]) prime[++prime[0]] = i;
- for (int j = 1; j <= prime[0] && prime[j] * i < maxn; ++j) {
- del[prime[j] * i] = true;
- if (i % prime[j] == 0) break;
- }
- }
- }
- vector<int> lst;
- inline int get_sum(int x) {//求某数各位和,顺便预处理末行质数
- int ret = 0, cur = x;
- bool f = true;
- while (x) {
- if (!(x % 10 == 1 || x % 10 == 3 || x % 10 == 7 || x % 10 == 9)) f = false;
- ret += x % 10, x /= 10;
- }
- if (f && ret == sum) lst.p_b(cur);
- return ret;
- }
- vector<int> setB[10][10], setC[10][10][10];//B已知首尾,C已知2、4、5位
- void init() {
- for (int i = 10000; i < maxn; ++i) {
- if (del[i] || get_sum(i) != sum) continue;
- isp[i / 10000][(i / 1000) % 10][(i / 100) % 10][(i / 10) % 10][i % 10] = true;
- //拆分质数到一个五维bool,用来判最终状态合法与否
- setB[i / 10000][i % 10].p_b(i);
- setC[(i / 1000) % 10][(i / 10) % 10][i % 10].p_b(i);
- }
- }
- int get_bit(int x, int bit) { //取出某个数的某一位
- for (int i = 1; i < bit; ++i)
- x /= 10;
- return x % 10;
- }
- inline void write(int x, int bit, int& a) { //把x的某一位写入位置a
- for (int i = 1; i < bit; ++i)
- x /= 10;
- a = x % 10;
- }
- bool check() { //判断答案是否合法
- int sum1 = sq[1][1] + sq[1][2] + sq[1][4] + sq[1][5];
- int sum2 = sq[2][3] + sq[3][3] + sq[4][3] + sq[5][3];
- if (sum1 != sum2) return false;//一行三列的冲突
- sq[1][3] = sum - sum1;
- if (!isp[sq[1][1]][sq[1][2]][sq[1][3]][sq[1][4]][sq[1][5]] || !isp[sq[1][3]][sq[2][3]][sq[3][3]][sq[4][3]][sq[5][3]])
- return false;//判最终写入的一个数是否满足一、3是质数
- sum1 = sq[1][1] + sq[2][1] + sq[4][1] + sq[5][1];
- sum2 = sq[3][2] + sq[3][3] + sq[3][4] + sq[3][5];
- if (sum1 != sum2) return false;//三行一列的冲突
- sq[3][1] = sum - sum1;
- if (!isp[sq[1][1]][sq[2][1]][sq[3][1]][sq[4][1]][sq[5][1]] || !isp[sq[3][1]][sq[3][2]][sq[3][3]][sq[3][4]][sq[3][5]])
- return false;
- return true;
- }
- struct Ans {
- int data[6][6];
- } ans[1000];
- bool operator < (Ans a, Ans b) {
- for (int i = 1; i <= 5; ++i)
- for (int j = 1; j <= 5; ++j)
- if (a.data[i][j] != b.data[i][j]) return a.data[i][j] < b.data[i][j];
- }
- int top = 0;
- void add() {
- ++top;
- for (int i = 1; i <= 5; ++i)
- for (int j = 1; j <= 5; ++j)
- ans[top].data[i][j] = sq[i][j];
- }
- void print() {
- for (int k = 1; k <= top; ++k) {
- for (int i = 1; i <= 5; ++i) {
- for (int j = 1; j <= 5; ++j)
- printf("%d", ans[k].data[i][j]);
- putchar('\n');
- }
- putchar('\n');
- }
- }
- int main() {
- open_file("prime");
- euler();
- cin >> sum >> sq[1][1];
- init();
- for (auto i: lst)
- for (auto j: lst) {//确定最后一行和最后一列
- if (i % 10 != j % 10) continue;
- int ptr = 6, tmp = i;
- while (tmp) { //写入末行
- sq[5][--ptr] = tmp % 10, tmp /= 10;
- }
- tmp = j, ptr = 6;
- while (tmp) { //写入末列
- sq[--ptr][5] = tmp % 10, tmp /= 10;
- }
- for (auto k: setB[sq[1][1]][sq[5][5]]) {
- tmp = k, ptr = 6;
- while (tmp) { //写入对角线
- --ptr, sq[ptr][ptr] = tmp % 10, tmp /= 10;
- }
- for (auto l: setB[sq[5][1]][sq[1][5]]) {
- if (get_bit(k, 3) != get_bit(l, 3)) continue;
- tmp = l, ptr = 6;
- while (tmp) { //写入另一条对角线
- --ptr, sq[6 - ptr][ptr] = tmp % 10, tmp /= 10;
- }
- for (auto m: setC[sq[2][2]][sq[2][4]][sq[2][5]]) { //枚举第二行
- write(m, 5, sq[2][1]), write(m, 3, sq[2][3]);//写入
- for (auto n: setC[sq[4][2]][sq[4][4]][sq[4][5]]) { //枚举第四行
- write(n, 5, sq[4][1]), write(n, 3, sq[4][3]);
- for (auto o: setC[sq[2][2]][sq[4][2]][sq[5][2]]) { //第二列
- write(o, 5, sq[1][2]), write(o, 3, sq[3][2]);
- for (auto p: setC[sq[2][4]][sq[4][4]][sq[5][4]]) { //第四列
- write(p, 5, sq[1][4]), write(p, 3, sq[3][4]);
- if (check()) add(); //判断合法,添加答案
- }
- }
- }
- }
- }
- }
- }
- sort(ans + 1, ans + 1 + top);//字典序
- print();
- return 0;
- }
T4:
下午
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;
- }