1103上午考试

1103上午考试

T2

​ 题目大意:

​ 给定一张\(n\)个节点\(m\)条边的有向图,问有多少个子图(一个边集)是没有环的.\(n <= 17\)

​ 状压DP.

​ 设\(f[i]\)表示点集为\(i\)时有多少个无环的子图.

​ 我们对一个有向图分层,入度为0的点构成第一层,删去第一层后入度为0的点构成第二层,以此类推.

​ 然后我们可以枚举最后一层的点集\(j\), \(i\)\(j\)交集为空,然后我们可以得到\(i\)\(j\)之间的连边为\(cnt\)条,可以得到DP转移方程:

\(f[i | j] = f[i] * 2^{cnt}\),因为这\(cnt\)条边每条边都可以选择加或不加进新的点集中.

​ 可是这么写会算重复,因为\(i | j\)的点集会有很多种组成.所以考虑容斥,新的转移方程就是:

\(f[i| j] = f[i] * 2 ^{cnt} * (-1) ^{size[j] + 1}\).

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
	long long s = 0, f = 1; char ch;
	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
	return s * f;
}

const int N = 17, mod = 1e9 + 7;
int n, m, ans, U;
int mp[N + 1][N + 1], d[(1 << N) + 1], siz[(1 << N) + 1], powe[305], f[(1 << N) + 1], sum[(1 << N) + 1];

void make_pre() {
	U = (1 << n) - 1;
	siz[0] = -1; powe[0] = 1;
	for(int i = 1;i <= U; i++) siz[i] = siz[i >> 1] * (i & 1 ? -1 : 1);
	for(int i = 1;i <= m; i++) powe[i] = powe[i - 1] * 2 % mod; 
}

int main() {
	
	n = read(); m = read();
	for(int i = 1, x, y;i <= m; i++) x = read() - 1, y = read() - 1, mp[x][y] = 1;
	make_pre(); f[0] = 1;
	for(int i = 0;i < U; i++) {
		for(int j = 0;j < n; j++) d[1 << j] = 0;
		for(int j = 0;j < n; j++) if(i & (1 << j)) 
				for(int k = 0;k < n; k++) d[1 << k] += mp[j][k];
		int C = U ^ i; sum[0] = 0;			
		for(int tmp = (C - 1) & C; ; tmp = (tmp - 1) & C) {
			int now = tmp ^ C, low = now & -now; // tmp ^ C 是因为更新sum要从小往大
			sum[now] = sum[now - low] + d[low]; // 边数
			f[i ^ now] = (f[i ^ now] + (1ll * f[i] * powe[sum[now]] % mod * siz[now] % mod + mod) % mod) % mod;
			if(!tmp) break;
		}
	}
	printf("%d", f[U]);
	
	fclose(stdin); fclose(stdout);
	return 0;	
}

T3

​ 题目大意:

​ 求满足如下条件的数对(a,b)对数:a,b 均为正整数且 a,b<=n 而lcm(a,b)>n。其中的 lcm 当然表示最小公倍数。答案对 1,000,000,007取模.\(n <= 1e10\)

​ 莫比乌斯反演. 推式子.

​ 题目让我们求\(\frac{a * b}{ gcd(a, b)} > n\) 的对数,单步容斥一下,求\(n ^ 2 - \frac{a * b}{gcd(a, b)} <= n\) 的对数.

​ 瞎搞一波:

\(ans = \displaystyle \sum_{d = 1}^{n}\sum _{j = 1}^{n}\sum_{i = 1}^{n} [ij <= n * d] [gcd(i, j) == d] = \sum_{d = 1}^{n}\sum _{j = 1}^{\lfloor \frac{n}{d}\rfloor }\sum_{i = 1}^{\lfloor \frac{n}{d}\rfloor } [i * d * j * d <= n * d] [gcd(i, j) == 1] = \sum_{d = 1}^{n}\sum _{j = 1}^{\lfloor \frac{n}{d}\rfloor }\sum_{i = 1}^{\lfloor \frac{n}{d}\rfloor } [ijd<= n] [gcd(i, j) == 1]\)

​ 再瞎搞一波:

\(ans = \displaystyle \sum_{d = 1}^{n}\sum_{j = 1}^{\lfloor \frac{n}{d} \rfloor} \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \sum_{k \mid gcd(i, j)} \mu(k)[ijd <= n] = \sum_{d = 1}^{n} \sum_{k = 1}^{\lfloor \frac{n}{d} \rfloor} \mu(k)\sum_{j = 1}^{\lfloor \frac{n}{dk} \rfloor} \sum_{i = 1}^{\lfloor \frac{n}{dk} \rfloor} [ijd *k * k <= n]\)

​ 我们发现:\(\lfloor \frac{n}{dk} \rfloor\)以上的部分是没有必要枚举的,所以说\(k\)只需枚举到\(\sqrt n\)所以上式可变为:\(\displaystyle \sum_{k = 1}^{\sqrt n} \mu(k) \sum _{d = 1}^{n}\sum_{j = 1}^{n} \sum_{i = 1}^{n} [ijd <= \frac{n}{k ^ 2}]\)

​ 我们假定\(d < j < i\),那么答案将乘6(可以类比一下如果只有两个数a,b, 可以只统计\(a < b\)的个数,然后乘2).如果说\(d, j ,i\)这三个数有两个相等,那么答案乘3.

​ 然后复杂度我不会分析,网上说用积分可得到函数的时间复杂度是\(Θ(n^{\frac{2}{3}})\).(md什么鬼).

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e5 + 5, mod = 1e9 + 7;
int cnt, mu[N], prime[N], is_prime[N];
long long n, res;

void make_mu() {
	mu[1] = 1;
	for(int i = 2;i < N; i++) {
		if(!is_prime[i]) { prime[++ cnt] = i; mu[i] = -1; }
		for(int j = 1;j <= cnt && i * prime[j] < N; j++) {
			is_prime[i * prime[j]] = 1;
			if(!(i % prime[j])) break; 
			mu[i * prime[j]] = -mu[i];
		}
	}
}

long long get_ans(int x) {
	long long ans = 0;
	for(int i = 1;i * i * i <= x; i++) {
		ans ++; //三个数相等 i * i * i <= x
		for(int j = i + 1;j * j * i < x; j++) 
			ans = (ans + 6ll * (x / i / j - j) % mod) % mod; // i是第一个数, j是第二个数, x / i / j是第三个数,减去j是因为强制了第三个数大于第二个数,相当于把第三个数小于等于第二个数的部分减去了
	}
	for(int i = 1;i * i <= x; i++) 
		ans = (ans + 3ll * (x / i / i - (x / i / i >= i)) % mod) % mod; //强制两个数相等,那么第三个数就是x / i / i,然后强制第三个数不等于第一二个数,如果说第三个数大于等于第一二个数,那么将会包含三个数相等的情况,所以减去1.
	return ans;
}

signed main() {
	
	cin >> n; make_mu();
	for(int i = 1;i * i <= n; i++) 
		if(mu[i]) res = (res + (mu[i] * get_ans(n / i / i) % mod + mod) % mod) % mod;
	n %= mod;
	printf("%lld", (1ll * n * n % mod - res + mod) % mod);
	
	return 0;
}
posted @ 2020-11-04 19:32  C锥  阅读(79)  评论(0编辑  收藏  举报