上周打的比赛,这周决定补一下题解。可惜我只会两题啊。看我什么时候能把这个补完吧。
第一次更新:2020-08-14
Integer Product「AGC 047A」
本场送分题,但我还是被卡了好久,wtcl。
因为所有数小数点后最多9位,所以不妨先把所有数搞成带分数(约分后)的形式。
可以分成 (4) 类讨论:
1,两个整数
这种情况乘积肯定是整数,我们只需统计每个整数前有多少个整数即可。
2,一个分数,一个整数
这得满足前面分数的分母是后面整数的因数,这也很好统计,(O(sqrt{n})) 枚举一下因数,然后再搞个桶统计一下每个分母有几个对应的分数。
3,一个整数,一个分数
这样和2其实一样,从后往前再统计一遍就行。
4,两个分数
这种情况比较烦,但由于这道题的性质还是可做的。
注意所有分母只有可能是 (2^a5^b) 的形式,又要求乘起来是整数,所以分母必然不会即有(2)又有(5),因为这样就得有一个既有(2)又有(5)的分子,而这样的分子显然是不存在的,除非是个整数,而我们又只讨论分数的情况。
所以对于一个分母为 (2^a) 的分数((5^b)同理,这里就不讲了),然后我们枚举分子的(5^t)的约数,我们统计一下以(5^t)为分母的分子是(2^a)的倍数的有多少个加上就行。在求的时候其实就可以统计,以 (2^a) 为分母的分子是(5^t)的倍数其实就可以加 (1)。
时间复杂度:对于每个整数会有 (sqrt{a}) 的复杂度,而对于每个分数只有 (log{a}) 的复杂度,瓶颈在整数,而整数最大才(10^4),所以不会超时。用了map,常数可能会大一点。
做法好麻烦,wtcl,如果有更简单的做法可以来教我qwq。
First Second「AGC 047B」
这题也是可做的,而且我觉得较A还更简单一点。
我们不妨先求一个字符串 (s) 前有几个可以变成 (s),然后再倒过来求一遍一样的东西即可。
有一个很妙的性质,除了字符串 (s) 的第一位其它位都必须紧贴前面的字符串的最后面,这很好理解,如果有一个不是那其后面那个空出来的就无法消掉。
那我们可以把所有前面的字符串反向建trie树,然后反过来在trie树上跑,如果不是第一位就走当前为,如果是第一位就随便走,如果走了当前为就返回。
可这样复杂度不对,考虑优化。对于每个trie树上的节点记录一下这个节点开始有几个字符串在前面有某个字符,然后如果是第一位就直接加上。那怎么维护呢,其实很简单,我们把插入边成递归插入,统计在回溯的时候记录一下当前哪些字符出现了,如果出现了某个字符回溯时就更新。
蒟蒻sb没看到字符串各不相同,所以还搞了个map去重。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 200010;
ll n;
ll a[N], b[N], c[N];
char s[20];
map<ll, ll> mp1, mp2;
map<ll, ll> dazhao[2][64];
ll ans, cnt, ans2, ans3;
ll gcd(ll x, ll y) {
return y == 0 ? x : gcd(y, x % y);
}
int main() {
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) {
c[i] = 1;
scanf("%s", s + 1);
ll len = strlen(s + 1), flag = 0;
for (ll j = 1; j <= len; j++) {
if (s[j] == '.') {
flag = 1;
} else {
if (flag) {
b[i] = b[i] * 10 + s[j] - '0';
c[i] *= 10;
} else {
a[i] = a[i] * 10 + s[j] - '0';
}
}
}
ll g = gcd(b[i], c[i]);
b[i] /= g;
c[i] /= g;
}
for (ll i = 1; i <= n; i++) {
if (b[i] == 0) {
ans += cnt;
cnt++;
for (ll j = 1; j * j <= a[i]; j++) {
if (a[i] % j == 0) {
ans += mp1[j];
if (a[i] / j != j) ans += mp1[a[i] / j];
}
}
} else {
ll num = a[i] * c[i] + b[i];
if (c[i] % 2 == 0 && c[i] % 5 != 0) {
ll tmp = c[i], cr = 0;
while (tmp % 2 == 0) {
cr++;
tmp /= 2;
}
for (ll j = 5, t = 1; j <= num; j *= 5, t++) {
if (num % j == 0) {
ans += dazhao[1][t][cr];//以5^t为分母的分子是2^cr的倍数的有多少个
dazhao[0][cr][t]++;//以2^cr为分母的分子是5^t的倍数的加一个
} else {
break;
}
}
} else if (c[i] % 2 != 0 && c[i] % 5 == 0) {
ll tmp = c[i], cr = 0;
while (tmp % 5 == 0) {
cr++;
tmp /= 5;
}
for (ll j = 2, t = 1; j <= num; j *= 2, t++) {
if (num % j == 0) {
ans += dazhao[0][t][cr];//以2^t为分母的分子是5^cr的倍数的有多少个
dazhao[1][cr][t]++;//以5^cr为分母的分子是2^t的倍数的加一个
} else {
break;
}
}
}
mp1[c[i]]++;
}
}
for (ll i = n; i >= 1; i--) {
if (b[i] == 0) {
for (ll j = 1; j * j <= a[i]; j++) {
if (a[i] % j == 0) {
ans += mp2[j];
if (a[i] / j != j) ans += mp2[a[i] / j];
}
}
} else {
mp2[c[i]]++;
}
}
printf("%lld
", ans);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1000010;
ll n;
ll ans;
ll trie[N][26], quick[N][26], tot = 1;
ll sum[N], cnt[26];//子树和
char s[N];
string t[200010];
map<string, int> mp;
void get_ans(ll p, ll len) {
if (len == 1) {
//可以不走当前字符
ans += quick[p][s[len] - 'a'];
return;
} else {
//不可不走当前字符
if (trie[p][s[len] - 'a']) {
get_ans(trie[p][s[len] - 'a'], len - 1);
}
}
}
void insert(int p, int len) {
sum[p]++;
if (!len) return;
int k = s[len] - 'a';
if (!trie[p][k]) {
trie[p][k] = ++tot;
}
insert(trie[p][k], len - 1);
cnt[k]++;
for (int i = 0; i < 26; i++) {
if (cnt[i]) {
quick[p][i]++;
}
}
}
int main() {
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) {
cin >> t[i];
for (int j = 0; j < t[i].length(); j++) {
s[j + 1] = t[i][j];
}
ll len = t[i].length();
get_ans(1, len);
memset(cnt, 0, sizeof(cnt));
insert(1, len);
}
memset(trie, 0, sizeof(trie));
memset(quick, 0, sizeof(quick));
memset(sum, 0, sizeof(sum));
tot = 1;
for (int i = n; i >= 1; i--) {
for (int j = 0; j < t[i].length(); j++) {
s[j + 1] = t[i][j];
}
ans -= mp[t[i]];
ll len = t[i].length();
get_ans(1, len);
memset(cnt, 0, sizeof(cnt));
insert(1, len);
mp[t[i]]++;
}
printf("%lld", ans);
return 0;
}