sorce: 120
T1: 100
T2: 20
T3: 0
吐槽: 感觉 T2 和 T3 题目表述都不是太清楚,T3 考试结束也不知道题目啥意思/kk
T1 序列(sequence)
题目大意:
小 Z 有一个序列,定义 (f(x)) 为 (x) 在十进制下的位数,特别地,求 (sum_{1leq i<jleq n}f(a_i + a_j))
(2 leq n leq 1000000), $ 1leq a_i leq 10^8$
solution
(n^2) 暴力:枚举 (i,j) ,求个位数,最后求和就好。
(O(n log n))
排序,一个数与比它小的数相加最多只可能进一位。
这样就可以 (O(n)) 扫一遍序列,每扫到一个数,就二分前面第一个能与它产生进位的数,然后就可以分两部分统计答案就好了。
code
/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define int long long
#define rg register
using namespace std;
const int MAXN = 1e6 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, min_digit, max_digit, pos, Ans, a[MAXN];
int get_digit(int x) {
if (x >= 1e8) return 9;
else if(x >= 1e7) return 8;
else if(x >= 1e6) return 7;
else if(x >= 1e5) return 6;
else if(x >= 1e4) return 5;
else if(x >= 1e3) return 4;
else if(x >= 100) return 3;
else if(x >= 10) return 2;
else return 1;
}
int get_L(int L, int R, int digit, int num) {
int ret = 0;
while(L <= R) {
int mid = (L + R) >> 1;
if (get_digit(a[mid] + num) >= digit) R = mid - 1, ret = mid;
else L = mid + 1;
}
return ret;
}
signed main(){
//freopen("sequence.in", "r", stdin);
//freopen("sequence.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + n + 1);
for (int i = 2; i <= n; i++) {
max_digit = get_digit(a[i] + a[i - 1]), min_digit = get_digit(a[i] + a[1]), pos = i - 1;
for (int j = max_digit; j >= min_digit; j--) {
Ans += (pos - get_L(1, pos, j, a[i]) + 1) * j;
pos = get_L(1, pos, j, a[i]) - 1;
}
}
printf("%lld", Ans);
puts("");
return 0;
}
T2 锁(lock)
直接看 题面 叭,实属概括不了题面。
关于 (a_i = 1) 的暴力。
摸样例,找规律,发现和组合数公式有关系
(C_{n}^{m - 1}) 从三十挂到了二十,组合数乘暴了,看别人代码发现,这样乘就不会爆。
(30pts)
ll C(int n, int m) {
ll ret1 = 1, ret2 = 1, ret3 = 1;
for (ll i = 1; i <= n; i++) ret1 *= i;
for (ll i = 1; i <= m; i++) ret2 *= i;
for (ll i = 1; i <= n - m; i++) ret3 *= i;
ret1 /= ret2 * ret3;
return ret1;
}
(100pts)
结论:重要程度之和小于 m,但加入任意一个居民都会导致重要度之和大于等于 m 的居民集合个数。
打算详细再证一下:
这样分组后的一组的重要度小于 (m) ,所以它们每一组都至少缺一把钥匙。
显然每一组加上其他任何一个人后重要值都会大于等于 (m) ,此时就要满足必须具有所有的钥匙,也就是其他人都必须有这一组缺少的所有钥匙。
这样分得的每两组都不同,所以每组对其他的人所要求的钥匙种类也不同。
反证法:假如 (B) 组有一个数 (A) 组之外如果这两组,并且 (A, B) 对其他人所要求的钥匙种类相同的话,那么 (A) 与 (B) 的要求在这个数上会发生矛盾。
这样推广到所有的组,如果每一组都只缺一种钥匙,那么总的需要的钥匙数 (x) ,因为要求钥匙数显然这样是最优的。
code
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, a[21], maxnum[21], ans;
void dfs(int p, int left, int gaveup = inf) {
if (p > n || left <= maxnum[p]) {
if (gaveup >= left) ans++;
return;
}
if (left > a[p]) dfs(p + 1, left - a[p], gaveup);
dfs(p + 1, left, min(gaveup, a[p]));
}
int main(){
//freopen("lock.in", "r", stdin);
//freopen("lock.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
maxnum[n] = a[n];
for (int i = n - 1; i >= 1; i--) maxnum[i] = min(maxnum[i + 1], a[i]);
dfs(1, m);
cout << ans << endl;
return 0;
}
T3 square
小 (Z) 现在有规格为 (a imes b) ((1 leq a,b leq n)) 的地板,她只能用这一种规格的地板来拼成正方形(边长任意,不能旋转),为了省钱,她会购买尽可能数量少的地板。
对于每一对 (a,b) 你要求出她最少需要购买的地板数量。
由于输出可能很大,所以你只需要输出所有答案的乘积即可,为了避免高精度,小 (Z) 很良心的让你将答案对 (19260817) 取模。
solution
读懂题目之后,很容易列出这么个式子((a, b) 都用 (i ,j) 表示了):
(prod_{i, j = 1}^{n}frac{lcm(i,j)}{i} imes frac{lcm(i,j)}{j})
由 (lcm(i, j) = frac{i,j}{gcd(i,j)}) 化简得到:
(prod_{i = 1}^{n}prod_{j = 1}^{n} frac {ij}{gcd(i,j)^2}) 这不就是这道题么 P5221 Product
先看上面 (prod_{i = 1}^{n}prod_{j = 1}^{n} ij)
(prod_{j = 1}^{n} ij = 1 imes i imes2 imes idots n imes i)
于是可以得到 (prod_{i=1}^{n} i^nn! = (prod_{i = 1}^{n}i^n) imes (n!)^n = (n!)^n imes (n!)^n = (n!)^{2n})
再看下面 (prod_{i = 1}^{n}prod_{j = 1}^{n} gcd(i, j)^{-2})
先不看 -2 次方。
系数就是个欧拉函数常见的应用,预处理出欧拉函数的前缀和就可以 (O(1)) 算出指数了。
(sum[x] = sum_{i=1}^{x} phi(i))
然后原式就为
((n!)^{2n} imes (prod_{d = 1}^{n} d^{2 imes sum[frac{n}{d}] - 1})^{-2})
然后就可以 (O(nlogn)) 求答案了。
欧拉函数的前缀和会爆 int,但用long long 会 MLE,根据欧拉定理,可以直接再质数上取模,又因为 mod 是个质数,所以直接对 mod - 1 取模就好。
((n!)^{2n} imes (prod_{d = 1}^{n} d^{(2 imes sum[frac{n}{d}] - 1)\%(mod - 1)})^{-2})
这样就不需要开 long long 了。