赞
踩
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
long long lcm(int a, int b) { return 1ll * a / gcd(a, b) * b; }
从小到大枚举因子
p[i] : i的最小素因子
prime[i]:素数的值
void init(int n) {
p[1] = 1;
for(int i = 2; i <= n; i ++) {
if(!p[i]) p[i] = i, prime[++tot] = i;
for (int j = 1; j <= tot && prime[j] * i <= n; j ++) {
p[i * prime[j]] = prime[j];
if(p[i] == prime[j]) break;
}
}
}
用来求解不定方程
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)
int exgcd(int a, int b, int &x, int &y) {
if(b == 0) {
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
求解非负整数解(x, y),输出x最小的解
a
x
+
b
y
=
d
ax+by=d
ax+by=d
cin >> a >> b >> m; ll d = exgcd(a, b, x, y); if(m % d != 0) { cout << -1 << "\n"; continue; } a /= d; b /= d; m /= d; __int128 xx = (__int128)x * m; xx %= b; if (xx < 0) xx += b; __int128 yy = (m - a * xx) / b; if(yy < 0) { cout << -1 << "\n"; continue; } cout << (ll)xx << " " << (ll)yy << "\n";
任何一个大于1的自然数N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积
N
=
p
1
e
1
∗
p
2
e
2
∗
p
3
e
3
.
.
.
∗
p
n
e
n
(
p
1
<
p
2
<
p
3
<
.
.
.
<
p
n
)
N={p_{1}}^{e_{1}}*{p_{2}}^{e_{2}}*{p_{3}}^{e_{3}}...*{p_{n}}^{e_{n}} (p_{1} < p_{2} < p_{3} < ...<p_{n})
N=p1e1∗p2e2∗p3e3...∗pnen(p1<p2<p3<...<pn)
对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目。
φ
(
x
)
=
x
∗
∏
i
=
1
n
(
1
−
1
p
i
)
\varphi (x) = x*\prod_{i = 1}^{n}(1-\frac{1}{p_{i}})
φ(x)=x∗i=1∏n(1−pi1)
int phi(int n) {
int phin = n, res = n;
for(int i = 2; i * i <= n; i++) {
if(n % i == 0) {
phin = phin / i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1) phin = phin / n * (n - 1);
return phin;
}
设a,m都为正整数,且gcd(a,m) = 1,则有
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{\varphi (m)}\equiv 1(mod\ m)
aφ(m)≡1(mod m)
任意整数a和其逆元满足
a
a
−
1
≡
1
(
m
o
d
n
)
aa^{-1}\equiv 1(mod\ n)
aa−1≡1(mod n)
在对除法运算进行取模时,利用逆元有 (a / b) mod p = a * inv(b) (mod p).
当a为b的倍数时,有
a
÷
b
m
o
d
p
=
(
a
m
o
d
(
b
×
p
)
)
÷
b
a÷bmodp=(amod(b×p))÷b
a÷bmodp=(amod(b×p))÷b
费马小定理求逆元(模数为素数时)
long long quickpow(long long a, long long b) {
if (b < 0) return 0;
long long ret = 1;
a %= mod;
while(b) {
if (b & 1) ret = (ret * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ret;
}
long long inv(long long a) {
return quickpow(a, mod - 2);
}
扩展欧几里得求逆元
int getInv(int a, int mod) {
int x, y;
int d = exgcd(a, mod, x, y);
return d == 1 ? (x % mod + mod) % mod : -1;
}
欧拉定理同样可以求逆元
求解1到n的逆元
inv[1] = 1;
for(int i = 2; i <= n; i++) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
求解阶乘逆元
void init()
{
fac[0] = 1;
for (int i = 1; i < maxn; i++)
{
fac[i] = fac[i - 1] * i % mod;
}
inv[maxn - 1] = quick_pow(fac[maxn - 1],mod - 2,mod);
for (int i = maxn - 2; i >= 0; --i)
{
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
求n个数的逆元
s[0] = 1;
for (int i = 1; i <= n; i++) s[i] = s[i - 1] * a[i] % p;
int x, y;
exgcd(s[n], p, x, y);
if (x < 0) x += p;
t[n] = x;
assert(s[n] * x % p == 1);
for(int i = n; i >= 1; i --) t[i - 1] = t[i] * a[i] %p;
for(int i = 1; i <= n; i++) {
inv[i] = s[i - 1] * t[i] % p;
}
for(ll l = 1; l <= n; l ++) {
ll d = n / l, r = n / d;
sum += (r - l + 1) * d;
// l .. r n / x = d
l = r;
}
ll mul(ll x, ll y, ll m) {
x %= m; y %= m;
ll d = ((long double) x * y / m);
d = x * y - d * m;
if (d >= m) d -= m;
if(d < 0) d += m;
return d;
}
long long Lucas(long long n, long long m, long long p) {
if(m == 0) return 1;
return (c(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}
a b % m = a b % φ ( m ) + φ ( m ) % m , g c d ( a , m ) ! = 1 & b > = ϕ ( m ) a b % m = a b % m , g c d ( a , m ) ! = 1 & b < ϕ ( m ) a b % m = a b % ϕ ( m ) % m , g c d ( a , m ) = 1 a^{b}\ \% \ m = a^{b \% \ \varphi(m) + \varphi(m)}\ \%\ m\ , \ gcd(a,m)!=1\&b>=\phi(m) \\a^{b}\%m=a^b\%m\ ,\ gcd(a,m)!=1\&b<\phi(m) \\a^{b}\%m=a^{b\%\phi(m)}\%m, \ gcd(a,m)=1 ab % m=ab% φ(m)+φ(m) % m , gcd(a,m)!=1&b>=ϕ(m)ab%m=ab%m , gcd(a,m)!=1&b<ϕ(m)ab%m=ab%ϕ(m)%m, gcd(a,m)=1
定义:(a,b) = 1, f(ab) = f(a)f(b)
i
d
(
x
)
=
x
id(x) = x
id(x)=x
2.
1
(
x
)
=
1
1(x) = 1
1(x)=1
3.
e
(
x
)
=
{
1
x
=
1
0
x
≠
1
e(x) = \left\{
4.欧拉函数
φ
(
n
)
=
n
∑
p
∣
n
(
1
−
1
p
)
\varphi (n) = n\sum_{p | n}(1-\frac{1}{p})
φ(n)=np∣n∑(1−p1)
5.d(n)因子个数
d
(
p
e
)
=
e
+
1
d(p^{e}) = e + 1
d(pe)=e+1
6.
σ
(
p
e
)
=
p
0
+
p
1
+
.
.
.
+
p
e
\sigma (p^{e}) = p^{0} + p^{1} + ...+p^{e}
σ(pe)=p0+p1+...+pe
7.莫比乌斯函数
μ
(
p
e
)
=
{
1
e
=
0
−
1
e
=
1
0
e
≥
2
\mu (p^{e})=\left\{
性质
∑
d
∣
n
μ
(
d
)
=
[
n
=
1
]
\sum_{d|n}\mu(d)=[n=1]
d∣n∑μ(d)=[n=1]
由
f
(
n
)
=
f
(
p
1
e
1
)
∗
f
(
p
2
e
2
)
∗
.
.
.
∗
f
(
p
k
e
k
)
f(n) = f(p_{1}^{e_{1}})*f(p_{2}^{e_{2}}) *...*f(p_{k}^{e_{k}})
f(n)=f(p1e1)∗f(p2e2)∗...∗f(pkek)
得
f
(
n
)
=
f
(
p
1
e
1
)
∗
f
(
n
/
p
1
e
1
)
f(n) = f(p_{1}^{e_{1}})*f(n/p_{1}^{e_{1}})
f(n)=f(p1e1)∗f(n/p1e1)
const int N = 2e7 + 1000; int p[N], pr[N / 5], n, pe[N], tot; int f[N], a, b, ans; void prime() { p[1] = 1; for(int i = 2; i <= n; i ++) { if(!p[i]) p[i] = i, pe[i] = i, pr[++tot] = i; for(int j = 1; j <= tot && pr[j] * i <= n; j ++) { p[i * pr[j]] = pr[j]; if (p[i] == pr[j]) { pe[i * pr[j]] = pe[i] * pr[j]; break; } else { pe[i * pr[j]] = pr[j]; } } } } void compute(int n, function<void(int)> calcpe) { f[1] = 1; for(int i = 2; i <= n; i ++) { if(i == pe[i]) calcpe(i); else f[i] = f[pe[i]] * f[i / pe[i]]; } } //因子个数 compute(n, [&](int x){ f[x] = f[x / p[x]] + 1; }); //因子和 compute(n, [&](int x){ f[x] = f[x / p[x]] + x; }); //欧拉函数 compute(n, [&](int x){ f[x] = x / p[x] * (p[x] - 1); }); //mo'b compute(n, [&](int x){ f[x] = x == p[x] ? -1 : 0; });
求一个数不同的质因子个数 p[i]。(加性函数)
void init(int n) {
p[1] = 1;
for(int i = 2; i <= n; i ++) {
if(!vis1[i]) p[i] = 1, prime[++tot] = i;
for (int j = 1; j <= tot && prime[j] * i <= n; j ++) {
vis1[i * prime[j]] = 1;
if(i % prime[j] == 0) {
p[i * prime[j]] = p[i];
break;
}
p[i * prime[j]] = p[i] + p[prime[j]];
}
}
}
h ( n ) = ∑ d ∣ n f ( d ) g ( n / d ) = ∑ d 1 d 2 = n f ( d 1 ) g ( d 2 ) h(n) = \sum_{d|n}f(d)g(n/d) = \sum_{d_{1}d_{2} = n}f(d_{1})g(d_{2}) h(n)=d∣n∑f(d)g(n/d)=d1d2=n∑f(d1)g(d2)
常见的卷积
1.d(n) 因子个数
d
(
n
)
=
∑
d
∣
n
1
(
d
)
1
(
d
/
n
)
d(n) = \sum_{d|n}1(d)1(d/n)
d(n)=d∣n∑1(d)1(d/n)
2.因子和
σ
(
n
)
=
∑
d
∣
n
i
d
(
d
)
1
(
n
/
d
)
\sigma(n) = \sum_{d|n}id(d)1(n/d)
σ(n)=d∣n∑id(d)1(n/d)
3.
f
=
f
∗
e
f = f*e
f=f∗e
4.
e
=
1
∗
μ
e = 1*\mu
e=1∗μ
1.交换律
h
=
f
∗
g
=
g
∗
f
h = f*g = g*f
h=f∗g=g∗f
2.结合律
p
=
(
f
∗
g
)
∗
h
=
f
∗
(
g
∗
h
)
p=(f*g)*h=f*(g*h)
p=(f∗g)∗h=f∗(g∗h)
3.f和g是积性函数,则f*g也是积性函数
f ( n ) = ∑ d ∣ n g ( d ) < = > g ( n ) = ∑ μ ( n / d ) f ( d ) f(n) = \sum_{d|n}g(d) <=>g(n) = \sum \mu(n/d)f(d) f(n)=d∣n∑g(d)<=>g(n)=∑μ(n/d)f(d)
f = g ∗ 1 < = > g = f ∗ u f = g*1 <=>g=f*u f=g∗1<=>g=f∗u
在求
∑
i
=
1
n
∑
j
=
1
m
[
g
c
d
(
i
,
j
)
=
1
]
(
n
<
m
)
\sum^{n}_{i=1}\sum^{m}_{j=1}[gcd(i,j)=1](n<m)
i=1∑nj=1∑m[gcd(i,j)=1](n<m)
时,可以通过上式变化为化简得该式可以通过整除分块O(根号n)求解
形如同样可以通过上述方法求解
code如下:
//P3455 #include <bits/stdc++.h> using namespace std; using ll = long long; const int N = 1e6 + 1000; int p[N], pr[N / 5], n, pe[N], tot; int f[N], smu[N], a, b, ans; void prime() { p[1] = 1; for(int i = 2; i <= n; i ++) { if(!p[i]) p[i] = i, pe[i] = i, pr[++tot] = i; for(int j = 1; j <= tot && pr[j] * i <= n; j ++) { p[i * pr[j]] = pr[j]; if (p[i] == pr[j]) { pe[i * pr[j]] = pe[i] * pr[j]; break; } else { pe[i * pr[j]] = pr[j]; } } } } void compute(int n, function<void(int)> calcpe) { f[1] = 1; for(int i = 2; i <= n; i ++) { if(i == pe[i]) calcpe(i); else f[i] = f[pe[i]] * f[i / pe[i]]; } }//莫比乌斯函数求解 int main() { int q; cin >> q; n = 5e4 + 10; prime(); compute(n, [&](int x){ f[x] = x == p[x] ? -1 : 0; }); for(int i = 1; i <= n; i ++) { smu[i] = smu[i - 1] + f[i]; } while(q --) { int a, b, k; cin >> a >> b >> k; a /= k, b /= k; if(a > b) swap(a, b); ll ans = 0; for(int l = 1; l <= a; l ++) { int r = min(a / (a / l), b / (b / l)); ans += (1ll*smu[r] - smu[l - 1]) * (a / l) * (b / l); l = r; // cout << l << endl; }//整除分块 cout << ans << endl; } }
a和m互质,求解最小的非负整数t,使得下式成立
a
t
≡
b
(
m
o
d
m
)
a^t \equiv b(\bmod m)
at≡b(modm)
由欧拉定理
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{\varphi(m)} \equiv 1(\bmod m)
aφ(m)≡1(modm)
说明
a
t
a^t
at在模m的意义下有一个长度为
φ
(
m
)
\varphi(m)
φ(m)的循环节,所以我们只需要考虑
t
≤
φ
(
m
)
t \leq \varphi(m)
t≤φ(m)的情形就可以了。当m是质数时
φ
(
m
)
=
m
−
1
\varphi(m)=m-1
φ(m)=m−1所以暴力枚举的复杂度为O(m)。
考虑优化,我们把 t t t拆成 k x − y kx-y kx−y, k = m + 1 k=\sqrt m + 1 k=m +1, x ∈ [ 1 , k ] x \in[1, k] x∈[1,k], y ∈ [ 0 , k − 1 ] y \in [0,k-1] y∈[0,k−1]
当 x = 1 , y = k − 1 x=1,y=k-1 x=1,y=k−1时, t = 1 t=1 t=1; 当 x = k , y = 0 x=k,y=0 x=k,y=0时, t = k 2 t=k^2 t=k2 ;这样我们通过遍历x和y,可以让t遍历完 [ 1 , m ] [1,m] [1,m]。
用x,y替换t后 a k x − y ≡ b ( m o d m ) a^{kx-y}\equiv b(\bmod m) akx−y≡b(modm),移项可得, a k x ≡ b ∗ a y ( m o d m ) a^{kx} \equiv b*a^{y}(\bmod m) akx≡b∗ay(modm)。我们可以预处理出 b y ( m o d m ) b^y(\bmod m) by(modm) 的所有值对应的最大的y。然后枚举x,看是否有对应的y和它相等。复杂度为 O ( m ) O(\sqrt{m}) O(m )
cin >> m >> a >> b; int k = sqrt(m) + 1; if(b == 1) { cout << 0; return 0; } map<int, int> vis; int now = 1; for(int y = 0; y <= k - 1; y ++) { vis[(now * b) % m] = y; now = 1ll * now * a % m; } int cur = now; for(int x = 1; x <= k; x ++) { if(vis.find(now % m) != vis.end()) { int y = vis[now % m]; if(k * x - y >= 0) ans = min(ans, k*x-y); } now = 1ll * now * cur % m; }
int xor_gauss() { int r = 1; for (int c = n; c >= 1; c--) { int temp = r; for (int i = r; i <= n; i++) if (a[i][c]) { temp = i; break; } if (!a[temp][c]) continue; swap(a[r], a[temp]); for (int i = r + 1; i <= n; i++) { if (a[i][c]) a[i] ^= a[r]; } r++; } if (r <= n) { for (int i = 1; i <= n; i++) { if (a[i] == 1) return -1; if (a[i] == 0) return quick_pow(2, n - i + 1, mod); } } for (int i = 1; i <= n; i++) { int res = a[i][0]; for (int j = i - 1; j; j--) res ^= a[i][j] ^ ans[j]; ans[i] = res; } return 1; }
int gauss(int a[N][N],int n) { int res=1; for(int i=1; i<=n; i++) { for(int j=i+1; j<=n; j++) { while(a[j][i]) { int t=a[i][i]/a[j][i]; for(int k=i; k<=n; k++) a[i][k]=(a[i][k]-t*a[j][k]+mod)%mod; swap(a[i],a[j]); res=-res; } } res=(res*a[i][i])%mod; } return (res+mod)%mod; }
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
求树的直径并输出
两次dfs(边权不能为负)
vector<LL> d(N); vector<int> p(N); function<void(int, int, LL)> dfs = [&](int u, int par, LL dep) { d[u] = dep; p[u] = par; for (auto [v, w] : G[u]) if (v != par) { dfs(v, u, dep + w); } }; dfs(0, -1, 0); int r = max_element(d.begin(), d.end()) - d.begin(); dfs(r, -1, 0); vector<int> v; int s = max_element(d.begin(), d.end()) - d.begin(); for (int x = s; x >= 0; x = p[x]) v.push_back(x); cout << d[s] << " " << v.size() << "\n"; for (int x : v) cout << x << " ";
int fa[N][16] void bfs() { memset(depth, 0x3f, sizeof depth); depth[root] = 1, depth[0] = 0; int hh = 0, tt = -1; q[++tt] = root; while(hh <= tt) { int t = q[hh ++]; for(int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if(depth[j] > depth[t] + 1) { depth[j] = depth[t] + 1; q[++tt] = j; fa[j][0] = t; for(int k = 1; k <= 15; k++) fa[j][k] = fa[fa[j][k - 1]][k - 1]; } } } } int lca(int a, int b) { if(depth[a] < depth[b]) swap(a, b); for(int k = 15; k >= 0; k--) if(depth[fa[a][k]] >= depth[b]) a = fa[a][k]; if(a == b) return a; for(int k = 15; k >= 0; k --) if(fa[a][k] != fa[b][k]) { a = fa[a][k]; b = fa[b][k]; } return fa[a][0]; }
点差分
将两点u,v之间路径上的所有点权增加x, o = LCA(u, v), o的父亲结点为p,则:
d
i
f
f
[
u
]
+
=
x
,
d
i
f
f
[
v
]
+
=
x
,
d
i
f
f
[
o
]
−
=
x
,
d
i
f
f
[
p
]
−
=
x
;
diff[u]+=x,diff[v]+=x,diff[o]-=x,diff[p]-=x;
diff[u]+=x,diff[v]+=x,diff[o]−=x,diff[p]−=x;
code
void dfs(int u, int fa) { int res = 0; for(int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if(j == fa) continue; dfs(j, u); res += sum[j]; } sum[u] = res + diff[u]; } cin >> u >> v; int x = lca(u, v); diff[u] += 1; diff[v] += 1; diff[x] -= 1; diff[p[x]] -= 1;
边差分
将两点u, v之间路径上的边权增加x,o = LCA( u,v),以每条边两端深度较大的节点存储改边的差分数组,则操作如下:
d
i
f
f
[
u
]
+
=
x
,
d
i
f
f
[
v
]
+
=
x
,
d
i
f
f
[
o
]
−
=
2
∗
x
;
diff[u]+=x,diff[v]+=x,diff[o]-=2*x;
diff[u]+=x,diff[v]+=x,diff[o]−=2∗x;
for(int k = 1; k <= n; k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
d[i][j] |= d[i][k] & d[k][j];
连通分量:对于分量中任意两点u,v必然可以从u走到v,且从v走到u
强连通分量:极大连通分量
通过Tarjan缩点可以让有向图转变为有向无环图,转变后的图里面的每一个点是原图的一个强连通分量。
一个有向图,变成一个强连通分量至少需要添加max(p,q)条边,p为缩点后入度为0的点,q为缩点后出度为0的点
int dfn[N], low[N], timestamp; int stk[N], top; bool in_stk[N]; int id[N], scc_cnt, size[N]; void tarjan(int u) { dfn[u] = low[u] = ++ timestamp; stk[ ++ top ] = u, in_stk[u] = 1; for(int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if(!dfn[j]) { tarjan(j); low[u] = min(low[u], low[j]); } else if(in_stk[j]) low[u] = min(low[u], dfn[j]); } if(dfn[u] == low[u]) { ++ scc_cnt; int y; do { y = stk[top -- ]; in_stk[y] = 0; id[y] = scc_cnt; size[scc_cnt] ++; } while(y != u); } }
for(int i = 1; i <= m; i++) { int f, x, y; std::cin >> f >> x >> y; if(f == 1) { // 等于 x == y, x -> y, w = 0, y -> x, w = 0; add(h,x,y,0);add(h,y,x,0); } else if(f == 2) { // 小于 x < y, x->y, w = 1; add(h,x,y,1); } else if(f == 3) { // 大于等于 x >= y, y->x, w = 0; add(h,y,x,0); } else if(f == 4) { // 大于 x > y, y->x, w = 1; add(h,y,x,1); } else if(f == 5) { // 小于等于 x <= y, x->y, w = 0; add(h,x,y,0); } } for(int i = 1; i <= n; i++) { add(h,0,i,1); } // 缩点 tarjan(0); // 建DAG int f = 1; for(int i = 0; i <= n; i ++) { for(int j = h[i]; ~j; j = ne[j]) { int k = e[j]; int u = id[i], v = id[k]; if(u == v) { // 不符合不等式关系 if(w[j] > 0) { f = 0; break; } } else add(h1, u, v, w[j]); } if(!f) break; } if(!f) { std::cout << "-1\n"; return 0; } // DAG求最长路 for(int i = scc_cnt; i >= 0; i --) { for(int j = h1[i]; ~j; j = ne[j]) { int k = e[j]; d[k] = std::max(d[i] + w[j], d[k]); } }
无向图中,极大的不含有桥的连通块被称为边的双连通分量
在里面不管删掉哪条边,仍然连通。
每对点之间至少存在两条没有公共边的路径
一个无向图变成边双连通分量,至少需要添加(cnt + 1)/ 2 条边,cnt为缩点后度数为1的点的个数
int dfn[N], low[N], timestamp; // 时间戳 int stk[N], top; int id[N], dcc_cnt; // 每个点所属分量编号 bool is_bridge[M]; void tarjan(int u, int from) { dfn[u] = low[u] = ++ timestamp; stk[ ++ top] = u; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!dfn[j]) { tarjan(j, i); low[u] = min(low[u], low[j]); if (dfn[u] < low[j]) is_bridge[i] = is_bridge[i ^ 1] = true; } else if (i != (from ^ 1)) low[u] = min(low[u], dfn[j]); } if (dfn[u] == low[u]) { ++ dcc_cnt; int y; do { y = stk[top -- ]; id[y] = dcc_cnt; } while (y != u); } }
极大的不包含割点的连通块被称为点的双连通分量
每个割点至少属于两个点双连通分量
int h[N], e[M], ne[M], idx; int dfn[N], low[N], timestamp; // 时间戳 int stk[N], top; int dcc_cnt; vector<int> dcc[N]; // 每个分量有哪些点 bool cut[N]; // 是否为割点 int root; void tarjan(int u) { dfn[u] = low[u] = ++ timestamp; stk[ ++ top] = u; if (u == root && h[u] == -1) { dcc_cnt ++ ; dcc[dcc_cnt].push_back(u); return; } int cnt = 0; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!dfn[j]) { tarjan(j); low[u] = min(low[u], low[j]); if (dfn[u] <= low[j]) { cnt ++ ; if (u != root || cnt > 1) cut[u] = true; ++ dcc_cnt; int y; do { y = stk[top -- ]; dcc[dcc_cnt].push_back(y); } while (y != j); dcc[dcc_cnt].push_back(u); } } else low[u] = min(low[u], dfn[j]); } }
1.二分图不存在奇数环,染色法不存在矛盾
2.匈牙利算法,匹配,最大匹配,匹配点,增广路径
3.最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径覆盖
bool find(int u) { for(auto v : eg[u]) { if(!st[v]) { st[v] = 1; int t = match[v]; if(t == 0 || find(t)) { match[v] = u; return true; } } } return false; } for(int i = 1; i <= n; i++) { memset(st, 0, sizeof st); ans += find(i); }
不相交
DAG,有向无环图,用最少的互不相交的路径,将所有点覆盖。
拆点,将原图中1…n,拆成新图1…n,1’…n’,原图i->j的有向边变成新图中i->j’的无向边,新图一定为二分图。
(实际上不需要新建一张图)
最小路径覆盖 = 原图点数 - 新图最大匹配数
可相交
用最少的可相交的路径,将所有点覆盖
对原图G,先求出传递闭包G’,然后对G’转化为不相交问题
code
//floyd求传递闭包
for(int k = 1; k <= n; k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
d[i][j] |= d[i][k] & d[k][j];
int ans = 0;
//匈牙利算法
for(int i = 1; i <= n; i ++) {
memset(st, 0, sizeof st);
ans += find(i);
}
cout << n - ans << "\n";
void topo() { queue<int> q; for(int i=1;i<=n;i++) if(d[i]==1) q.push(i); //不是环内的点,入队 while(!q.empty()) { int u=q.front(); vis[u]=1; q.pop(); for(int i=0;i<g[u].size();i++) { int v=g[u][i]; if(--d[v]==1) q.push(v); //u和v断开,v的度数-1,减一后若度数变为,则可以入队 } } } // 度数为2的点是环内的点
对所有边都连通的无向图
(1)存在欧拉路径的充分必要条件:度数为奇数的点只能有0或2个。
(2)存在欧拉回路的充分必要条件:度数为奇数的点只能有0个。
void dfs(int u) {
for(int i = 1; i <= 500; i ++) {
if(g[u][i]) {
g[u][i] --, g[i][u] --;
dfs(i);
}
}
ans[++ cnt] = u;
}
对所有边都连通的有向图
(1)存在欧拉路径的充分必要条件:要么所有点的出度均等于入度;要么除了两个点之外,其余所有点的出度等于入度,剩余的两个点:一个满足出度比入度多1(起点),另一个满足入度比出度多1(终点)。
(2)存在欧拉回路的充分必要条件:所有点的出度均等于入度。
int get(int x) {
if(fa[x] != x) fa[x] = get(fa[x]);
return fa[x];
}
void merge(int x, int y) {
x = get(x), y = get(y);
if(x == y) return;
fa[x] = y;
val[y] += val[x];
}
struct node { int ls, rs, d; ll cost, sum, siz; }t[N]; int id[N], val[N], cost[N]; void pushup(int x) { t[x].sum = t[t[x].ls].sum + t[t[x].rs].sum + t[x].cost; t[x].siz = t[t[x].ls].siz + t[t[x].rs].siz + 1; } int merge(int x, int y) { if (!x || !y) return x | y; // 若一个堆为空则返回另一个堆 if (t[x].cost < t[y].cost) swap(x, y); // 取值较大的作为根 t[x].rs = merge(t[x].rs, y); // 递归合并右儿子与另一个堆 if (t[t[x].rs].d > t[t[x].ls].d) swap(t[x].ls, t[x].rs); // 若不满足左偏性质则交换左右儿子 t[x].d = t[t[x].rs].d + 1; // 更新dist pushup(x); return x; } void build() { for(int i = 1; i <= n; i ++) id[i] = i, t[i].siz = 1, t[i].cost = cost[i], t[i].sum = cost[i]; }
int lowbit(int x)
{
return x & -x;
}
void update(int x, int c) // 位置x加c
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
int ask(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
struct Node { int l, r; // TODO: 需要维护的信息和懒标记 }tr[N * 4]; void pushup(int u) { // tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v); // TODO: 利用左右儿子信息维护当前节点的信息 } void pushdown(int u) { // TODO: 将懒标记下传 } void build(int u, int l, int r) { if (l == r) tr[u] = {l, r}; else { tr[u] = {l, r}; int mid = l + r >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); pushup(u); } } void update(int u, int l, int r, int d) { if (tr[u].l >= l && tr[u].r <= r) { // TODO: 修改区间 } else { pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) update(u << 1, l, r, d); if (r > mid) update(u << 1 | 1, l, r, d); pushup(u); } } int query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) { return ; // TODO 需要补充返回值 } else { pushdown(u); int mid = tr[u].l + tr[u].r >> 1; int res = 0; if (l <= mid ) res = query(u << 1, l, r); if (r > mid) res += query(u << 1 | 1, l, r); return res; } }
void pushup(int u) { if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l]; else if (tr[u].l != tr[u].r) { tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len; } else tr[u].len = 0; } void modify(int u, int l, int r, int k) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].cnt += k; pushup(u); } else { int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) modify(u << 1, l, r, k); if (r > mid) modify(u << 1 | 1, l, r, k); pushup(u); } } int main() { for (int i = 0, j = 0; i < n; i ++ ) { double x1, y1, x2, y2; scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2); seg[j ++ ] = {x1, y1, y2, 1}; seg[j ++ ] = {x2, y1, y2, -1}; ys.push_back(y1), ys.push_back(y2); } sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end()); build(1, 0, ys.size() - 2); sort(seg, seg + n * 2); double res = 0; for (int i = 0; i < n * 2; i ++ ) { if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x); modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k); } }
int id[N], len, tot; struct node { int l, r; ll sum, tag; }blk[N]; int n, m; void update(int l, int r, ll val) { int idx = id[l], idy = id[r]; if(idx == idy) { for(int i = l; i <= r; i ++) a[i] += val; blk[idx].sum += (r - l + 1) * val; } else { for(int i = l; i <= blk[idx].r; i ++) a[i] += val; blk[idx].sum += (blk[idx].r - l + 1) * val; for(int i = blk[idy].l; i <= r; i ++) a[i] += val; blk[idy].sum += (r - blk[idy].l + 1) * val; for(int i = idx + 1; i < idy; i ++) { blk[i].tag += val; blk[i].sum += val * (blk[i].r - blk[i].l + 1); } } } ll query(int l, int r) { int idx = id[l], idy = id[r]; ll ans = 0; if(idx == idy) { for(int i = l; i <= r; i ++) { ans += a[i] + blk[idx].tag; } return ans; } else { for(int i = l; i <= blk[idx].r; i ++) ans += a[i] + blk[idx].tag; for(int i = blk[idy].l; i <= r; i ++) ans += a[i] + blk[idy].tag; for(int i = idx + 1; i < idy; i ++) ans += blk[i].sum; return ans; } } void build() { len = sqrt(n); for(int i = 1; i <= n; i ++) { id[i] = (i - 1) / len + 1; blk[id[i]].sum += a[i]; if((i - 1) % len==0) blk[++tot].l = i; if(i%len == 0) blk[tot].r = i; } blk[tot].r = n; }
struct mo {
int l, r;
int id;
bool operator < (const mo &o) const
{
if(l / len != o.l / len)
return l < o.l;
if(l / len & 1)
return r < o.r;
else return r > o.r;
}
}q[N];
l = 1, r = 0, len = sqrt(n);
// 增加时间戳 bool operator < (const mo &o) const { if(l / len != o.l / len) return l < o.l; if(r / len != o.r / len) return r < o.r; return t < o.t; } // 进行单点修改后,交换原数组与操作子的值 while(now < q[i].t) { now ++; if(upd[now].x >= l && upd[now].x <= r) { cnt[a[upd[now].x]] --; if(cnt[a[upd[now].x]] == 0) cur --; if(cnt[upd[now].y] == 0) cur ++; cnt[upd[now].y] ++; } swap(a[upd[now].x], upd[now].y); } while(now > q[i].t) { if(upd[now].x >= l && upd[now].x <= r) { cnt[a[upd[now].x]] --; if(cnt[a[upd[now].x]] == 0) cur --; if(cnt[upd[now].y] == 0) cur ++; cnt[upd[now].y] ++; } swap(a[upd[now].x], upd[now].y); now --; }
int n, m; int s[N]; int tr[M][2], max_id[M]; int root[N], idx; void insert(int i, int k, int p, int q) { if(k < 0) { max_id[q] = i; return; } int v = s[i] >> k&1; if (p) tr[q][v ^ 1] = tr[p][v ^ 1]; tr[q][v] = ++ idx; insert(i, k - 1, tr[p][v], tr[q][v]); max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]); } int query(int root, int C, int L) { int p = root; for (int i = 23; i >= 0; i --) { int v = C >> i & 1; if (max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1]; else p = tr[p][v]; } return C ^ s[max_id[p]]; } void tire_init() { max_id[0] = -1; root[0] = ++idx; insert(0, 23, 0, root[0]); } for(int i = 1; i <= n; i ++) { ll tmp; cin >> tmp; s[i] = s[i - 1] ^ tmp; root[i] = ++ idx; insert(i, 23, root[i - 1], root[i]); }
//有多少个不同的模式串在文本串里出现过 namespace AC { int tr[N][26], tot; int e[N], fail[N]; void insert(char *s) { int u = 0; for (int i = 1; s[i]; i ++) { if(!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot; u = tr[u][s[i] - 'a']; } e[u] ++; } queue<int> q; void build() { for(int i = 0; i < 26; i++) if(tr[0][i]) q.push(tr[0][i]); while (q.size()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i ++) { if (tr[u][i]) { fail[tr[u][i]] = tr[fail[u]][i]; q.push(tr[u][i]); } else tr[u][i] = tr[fail[u]][i]; } } } int query(char *t) { int u = 0, res = 0; for (int i = 1; t[i]; i++) { u = tr[u][t[i] - 'a']; for (int j = u; j && e[j] != -1; j = fail[j]) { res += e[j], e[j] = -1; } } return res; } }
//AC自动机 #include <bits/stdc++.h> using namespace std; const int N = 2e6 + 10; char s[N]; char a[N]; namespace AC { int tr[N][26], tot; int idx[N], fail[N]; int val[N]; int vis[N]; int cnt[N]; // 第i个字符串的出现次数 void insert(char *s, int id) { int u = 0; for (int i = 1; s[i]; i ++) { if(!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot; u = tr[u][s[i] - 'a']; } vis[id] = u; } void init() { memset(fail, 0, sizeof fail); memset(tr, 0, sizeof tr); memset(idx, 0, sizeof idx); memset(val, 0, sizeof val); memset(cnt, 0, sizeof cnt); tot = 0; } queue<int> q; void build() { for(int i = 0; i < 26; i++) if(tr[0][i]) q.push(tr[0][i]); while (q.size()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i ++) { if (tr[u][i]) { fail[tr[u][i]] = tr[fail[u]][i]; q.push(tr[u][i]); } else tr[u][i] = tr[fail[u]][i]; } } } int query(char *t) { int u = 0, res = 0; for (int i = 1; t[i]; i++) { u = tr[u][t[i] - 'a']; for (int j = u; j ; j = fail[j]) res val[j] ++; } return res; } } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; // AC::init(); for(int i = 1; i <= n; i ++) { cin >> s + 1; AC::insert(s, i); } AC::build(); cin >> a + 1; int x = AC::query(a); for(int i = 1; i <= n; i ++) { cout << AC::val[AC::vis[i]] << "\n"; } }
int head[N], nxt[N], to[N], cnt; void add(int u, int v) { nxt[++cnt] = head[u]; head[u] = cnt; to[cnt] = v; } void dfs(int u) { int i, v; for(i = head[u]; i; i = nxt[i]) { v = to[i]; dfs(v); val[u] += val[v]; } } int query(char *t) { int u = 0, res = 0; for (int i = 1; t[i]; i++) { u = tr[u][t[i] - 'a']; ++val[u]; } for(int i = 1; i <= tot; i ++) add(fail[i], i); dfs(0); return res; }
每个节点代表的是一个连续长度的字符串的集合,这个集合里面的字符串的长度是连续的,并且每个长度都只会出现一次,字符串集合里面字符串都是长度比它大的字符串的后缀
跳到比当前节点短的最长的相同后缀字符串集
Fail[x]的字符串集长度和x的字符串集长度是连续的
struct Suffix_Automata { //两倍字符串长度的空间 int maxlen[Maxn], trans[Maxn][26], link[Maxn], Size, Last; int a[Maxn], b[Maxn], endpos[Maxn]; int dp[Maxn], sum[Maxn]; Suffix_Automata() {Size = Last = 1;} inline void Extend(int id) { int cur = (++ Size), p; maxlen[cur] = maxlen[Last] + 1; endpos[cur] = 1; for(p = Last; p && !trans[p][id]; p = link[p]) trans[p][id] = cur; if (!p) link[cur] = 1; else { int q = trans[p][id]; if (maxlen[q] == maxlen[p] + 1) link[cur] = q; else { int clone = (++ Size); maxlen[clone] = maxlen[p] + 1; for(int i = 0; i < 26; ++i) trans[clone][i] = trans[q][i]; link[clone] = link[q]; for(; p && trans[p][id] == q; p = link[p]) trans[p][id] = clone; link[cur] = link[q] = clone; } } Last = cur; } void getTP(int &Len) { //getendpos 前置 for(int i = 1; i <= Size; i ++) a[maxlen[i]] ++; for(int i = 1; i <= Len; i ++) a[i] += a[i - 1]; for(int i = 1; i <= Size; i++) b[a[maxlen[i]] --] = i; } void getendpos() { //求每类子串的数量 for(int i = Size; i >= 1; i --) { int e = b[i]; endpos[link[e]] += endpos[e]; } } ll getSubNum() { // 求不相同子串数量 ll ans = 0; for(int i = 2 ;i <= Size; ++i) ans += maxlen[i] - maxlen[link[i]] ; return ans; } void get_len_max(int Len) { // 求长度为i的出现次数最多的子串 for(int i = 1; i <= Size; i++) dp[maxlen[i]] = max(dp[maxlen[i]], endpos[i]); for(int i = Len - 1; i >= 1; i --) dp[i] = max(dp[i], dp[i + 1]); for(int i = 1; i <= Len; i ++) { cout << dp[i] << "\n"; } } void getsum() { //getmink前置 for(int i = Size; i >= 1; i --) { int &e = b[i]; sum[e] = 1; for(int j = 0; j < 26; j ++) { sum[e] += sum[trans[e][j]]; } } } void getmink(int k) { // 求字典序第k小的子串 int now = 1, p; string s = ""; while(k) { for (int i = 0; i < 26; i ++) { if (trans[now][i] && k) { p = trans[now][i]; if (sum[p] < k) k -= sum[p]; else { k --; now = p; s += (char)(i + 'a'); break; } } } } cout << s << "\n"; } void LCS(char s[], int Len) { // 求两个串的最长公共子串 int ans = 0, cnt = 0; int now = 1; char base = 'a'; for(int i = 0; i < Len; i++) { int c = s[i] - base; if (trans[now][c]) { cnt ++; now = trans[now][c]; } else { while(now&&!trans[now][c]) now = link[now]; if (!now) cnt=0, now=1; else cnt = maxlen[now]+1, now = trans[now][c]; } ans = max(ans, cnt); } cout << ans << "\n"; } int ans[Maxn]; void init_ans(){ for(int i=1;i<=Size;i++) ans[i]= maxlen[i]; } void LCS2(char s[],int Len){ //求多个串的最长公共子串 for(int i=1;i<=Size;i++) dp[i]=0; int cnt=0; int now=1; char base='a'; for(int i=0;i<Len;i++){ int c=s[i]-base; if(trans[now][c]){ cnt++; now=trans[now][c]; } else{ while(now&&!trans[now][c]) now=link[now]; if(!now) cnt=0,now=1; else cnt=maxlen[now]+1,now=trans[now][c]; } dp[now]=max(dp[now],cnt); } for(int i=Size;i>=1;i--){ int e=b[i]; dp[link[e]]=max(dp[link[e]],min(dp[e],maxlen[link[e]])); } for(int i=1;i<=Size;i++) ans[i]=min(ans[i],dp[i]); } void get_LCS2_ans(){ int cnt=0; for(int i=1;i<=Size;i++) cnt=max(cnt,ans[i]); printf("%d\n",cnt); } void get_cntk(int k) { //求出现次数为k的子串种数 ll ans = 0; for (int i = 1; i <= Size; i++) { if(endpos[i] == k) ans += maxlen[i] - maxlen[link[i]]; } cout << ans << "\n"; } int d[Maxn]; void get_sumk(int l, int r) { //求出现次数 l <= k <= r 的子串种数 for(int i = Size; i > 1; i--) { int v = b[i]; if(endpos[v] >= l && endpos[v] <= r) d[v] ++; for(int j = 0; j < 26; j ++) { if(trans[v][j]) d[v] += d[trans[v][j]]; } } ll ans = 0; for (int i = 0; i < 26; i ++) { if (trans[1][i]) ans += d[trans[1][i]]; } cout << ans << "\n"; } } T;
#include <bits/stdc++.h> using namespace std; using ll = long long; const int N = 1e5 + 10; const int base = 131; using ull = unsigned long long; char s[N]; ull h1[N], p[N], h2[N], ans = 0; int n; ull gh1(int l, int r) { if(l > r) return 0; return h1[r] - h1[l - 1] * p[r - l + 1]; } ull gh2(int l, int r) { if(l > r) return 0; return h2[l] - h2[r + 1] * p[r - l + 1]; } int main() { scanf("%s", s + 1); p[0] = 1; n = strlen(s + 1); for(int i = 1; i <= n; ++i) { h1[i] = h1[i - 1] * base + s[i]; p[i] = p[i - 1] * base; } int f = 1; for(int i = n; i; i--) h2[i] = h2[i + 1] * base + s[i]; for(int i = 1; i <= n; i ++) { if(s[i] != s[n - i + 1]) { f = 0; for(int j = i; j <= n - i + 1; j ++) { ull tmp1 = gh2(i, j); ull tmp2 = gh1(j + 1, n - i + 1); ull tmp3 = tmp1 * p[n - i + 1 - j] + tmp2; tmp1 = gh2(j + 1, n - i + 1); tmp2 = gh1(i, j); ull tmp4 = tmp1 * p[j - i + 1] + tmp2 ; if(tmp3 == tmp4) { cout << i << " " << j << "\n"; return 0; } } for(int j = n - i + 1; j >= i; j --) { ull tmp2 = gh2(j, n - i + 1); ull tmp1; tmp1 = gh1(i, j - 1); ull tmp3 = tmp1 * p[n - i + 2 - j] + tmp2; tmp2 = gh2(i, j - 1); tmp1 = gh1(j, n - i + 1); ull tmp4 = tmp1 * p[j - i] + tmp2 ; if(tmp3 == tmp4) { cout << j << " " << n - i + 1 << "\n"; return 0; } } break; } } if(f) { cout << 1 << " " << 1 << "\n"; } else cout << -1 << " " << -1 << "\n"; }
给定两个多项式 F ( x ) F(x) F(x)和 G ( x ) G(x) G(x),求解 F ( x ) ∗ G ( x ) F(x)*G(x) F(x)∗G(x),时间复杂度为 n l o g ( n ) nlog(n) nlog(n)
只能求整数,并且模数必须是 q ∗ 2 k + 1 q*2^{k}+1 q∗2k+1的质数p, ( q , k ) (q,k) (q,k)可以取 ( 7 , 26 ) , ( 199 , 23 ) , ( 17 , 27 ) , ( 479 , 21 ) (7,26),(199,23),(17,27),(479,21) (7,26),(199,23),(17,27),(479,21)。
constexpr ll mod = 998244353, g = 3; LL power(LL a, LL r) { LL res = 1; for (; r; r >>= 1, a = a * a % mod) if (r & 1) res = res * a % mod; return res; } void DFT(LL* a, int n, bool inv = false) { static vector<int> r; if ((int)r.size() != n) { r.resize(n); for (int i = 0; i < n; i += 1) r[i] = i == 1 ? (n >> 1) : r[i >> 1] >> 1 | r[i & 1]; } for (int i = 0; i < n; i += 1) if (i < r[i]) std::swap(a[i], a[r[i]]); for (int m = 1; m < n; m <<= 1) { LL m2 = m << 1, w0 = power(inv ? power(g, mod - 2) : g, (mod - 1) / m2); for (int i = 0; i < n; i += m2) for (int j = 0, w = 1; j < m; j += 1, w = w * w0 % mod) { LL &x = a[m + i + j], &y = a[i + j], t = w * x % mod; x = y - t; if (x < 0) x += mod; y += t; if (y >= mod) y -= mod; } } if (inv) for (int i = 0, inv = power(n, mod - 2); i < n; i += 1) a[i] = a[i] * inv % mod; } using POFF = vector<LL>; POFF operator * (const POFF& f, const POFF& g){ int n = f.size() + g.size() - 1, k = 1; while (k < n) k <<= 1; POFF h(k << 1); copy(f.begin(), f.end(), h.begin()); copy(g.begin(), g.end(), h.begin() + k); DFT(h.data(), k); DFT(h.data() + k, k); for (int i = 0; i < k; i += 1) h[i] = h[i] * h[i + k] % mod; DFT(h.data(), k, true); h.resize(n); return h; } int main() { cin.tie(nullptr)->sync_with_stdio(false); int n, m; cin >> n >> m; POFF f(n), g(m); for (LL& x : f) cin >> x; for (LL& x : g) cin >> x; POFF h = f * g; for (LL x : h) cout << x << " "; return 0; } // from https://judge.yosupo.jp/submission/61121 Heltion
∑ i = 1 n a i b i + k 翻转 a 后得到 ∑ i = 1 n a n − i + 1 b i + k ,因为 n − i + 1 + i + k = n + 1 + k \sum_{i=1}^na_ib_{i+k}\ 翻转a后得到\sum_{i=1}^na_{n-i+1}b_{i+k},因为n-i+1+i+k=n+1+k i=1∑naibi+k 翻转a后得到i=1∑nan−i+1bi+k,因为n−i+1+i+k=n+1+k
所以实际上等价于将数组a翻转后,将a倍长,然后求a和b的卷积,它们卷积的第 n + 1 n+1 n+1项到第 n 2 n^2 n2项就是答案。
有k种元素,每个元素有ai,从中无序地选出r个元素,r <= ai,则有选法:
C
k
−
1
+
r
r
C_{k-1+r}^{r}
Ck−1+rr
有k个元素,每个元素有ai个,不同排列的个数为
(
∑
i
=
1
k
a
i
)
!
∏
i
=
1
k
(
a
i
!
)
\frac{(\sum_{i=1}^{k}a_i)!}{\prod_{i=1}^k(a_i!)}
∏i=1k(ai!)(∑i=1kai)!
∑ x = 0 m i n ( i , j ) C i x C j x = C i + j j \sum_{x=0}^{min(i,j)}C_{i}^{x}C{j}^{x}=C_{i+j}^{j} x=0∑min(i,j)CixCjx=Ci+jj
两个集合中各选出x个数,相当于从长度为i+j的数组中选出i个数
将长度为i+j的数组划分为前后两段,第一段i个数,第二段j个数
从中选取i个数,假设其中有x个数落到了第二段,有i−x个数落到了第一段。我们让第一段剩下x个数变成被选择的数,这样也就是 C i x C j x {}C_i^{x}C_j^{x}{} CixCjx
故容易得到原式子成立。
第一行包含一个整数 n,表示待处理的整数个数。
第二行包含空格分隔的 n 个整数,依次表示 a1,a2,⋯,an。
n = int(input())
arr = [int(x) for x in sys.stdin.readline().split()]
# 初始化
vis = {}
# 是否在vis内
vis.has_key(key)
# 以列表返回可遍历的(键, 值) 元组数组
vis.items()
# 以列表返回一个字典所有的键
vis.keys()
# 删除字典内所有元素
vis.clear()
# 删除字典给定键key对应的值
vis.pop(key)
覆盖[1,m],给很多[l,r],必须两个区间有重合时才能合并成大区间。举例,要覆盖[1,5],现在有[1,3],[4,5],[3,5]必须选择[1,3],[3,5] 而不是[1,3], [4,5] ,因为后者两个区间没有重合部分。如何维护这种情形下,区间[1,m]是否全部被覆盖?对每个[l,r]使r–即可,只需要用线段树维护区间[1,m-1]的最小值是否大于等于1,添加一个区间就是让区间[l,r-1]加1。
对于一个01串,我们可以将交换两个字符,变成翻转两个字符各一次,保证翻转结束,1的数量不变,就可以等价成交换次数为翻转次数的一半。
|a-b| = max(a-b, b-a)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。