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;
}