zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 047题解(整除,trie,原根,多项式)

    上周打的比赛,这周决定补一下题解。可惜我只会两题啊。看我什么时候能把这个补完吧。

    第一次更新: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;
    }
    
  • 相关阅读:
    Intellij IDEA +genymotion安装配置
    openssl编译参数选项
    shell脚本中sqlite3命令查询数据库失败返回空,并将错误信息打印到标准错误输出
    linux 系统中 /etc/passwd 和 /etc/shadow文件详解
    linux crypt()函数使用总结
    linux popen()函数使用
    AES加解密所遇问题
    linux 修改密码命令
    linux新增动态库后可执行程序找不到的问题
    inet_addr()和inet_ntoa()使用注意
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/13503776.html
Copyright © 2011-2022 走看看