退役贴 && ACM/ICPC Template

没想到这么快就轮到我写退役贴了……

以两块铁牌结束我的ACM生涯,稍微有点不甘心呐,

但也只能说我好菜啊……(毕竟当年oi也逃了一波2333

以后肯定会很怀念这两年看书刷题以及晚上熬夜打cf的时光吧……

感谢我的两名队友以及集训队的小伙伴们,

能认识你们真是太好了。


一直想把这两个号打上橙名,可是最终还是太菜了打不上去,想到以后恐怕更没有机会了,赶紧截个图233……


握草想不出说啥了……

算了,留下些遗产,希望能让学弟学妹们参考借鉴吧。

铁牌选手已退役。

– 2016年11月


遗产 =。=

-- ACM/ICPC Tempalte version 0.45 final  by Lunacy

0.头文件 && 读入挂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int mod = 1000000007;
const int inf = 0x3f3f3f3f;
#define rep(i, n) for(int i=0;i<int(n);++i)
#define clr(a) memset(a, 0, sizeof(a))
#define dbg(x) cout << #x << " : "<< (x) <<endl
template <class T> inline void in(T &x) {
T f = 1; char c; while ((c = getchar()) < '0' || c > '9') if (c == '-') f = -1;
x = c - '0';
while ((c = getchar()) >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0'; x *= f;
}
/* ........................................................................... */
const int N = 1e6 + 5;
int n, m, x, t, T;
int a[N];
int main(int argc, char const *argv[])
{
ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
}

1.基础算法

离散化

num[]表示离散化之前的数组,下标从1~n
a[]表示离散化之后的数组,下标从1~n
lsh[]表示离散化的缓存数组,下标为1~cnt
函数返回值为cnt

使用方法

  1. 输入n
  2. for(i: 1->n) scanf(num[i]);
  3. ll cnt = discrete()

模版版本一

说明: 此方法为完整的离散化,表示不考虑重复数字num[i]为第a[i]大的数
num数组: 1 5 5 5 100
a数组: 1 2 2 2 3

1
2
3
4
5
6
7
8
9
ll num[maxn],lsh[maxn],a[maxn],n;
ll discrete(){
memcpy(lsh, num, sizeof(lsh));
stable_sort(lsh+1, lsh+1+n);
ll cnt=unique(lsh+1, lsh+1+n)-lsh-1;
for (ll i=1; i<=n; i++)
a[i]=lower_bound(lsh+1, lsh+cnt+1, num[i])-lsh;
return cnt;
}

版本二

说明: 此方法为特殊的离散化,表示考虑重复数字num[i]为第a[i]大的数
num数组: 1 100 5 5 5
a数组: 1 5 2 2 2

1
2
3
4
5
6
7
8
9
ll num[maxn],lsh[maxn],a[maxn],n;
ll discrete(){
memcpy(lsh, num, sizeof(lsh));
stable_sort(lsh+1, lsh+1+n);
ll cnt=n;
for (ll i=1; i<=n; i++)
a[i]=lower_bound(lsh+1, lsh+cnt+1, num[i])-lsh;
return cnt;
}

归并排序

时间复杂度:O(nlogn)
参数说明:
A[]为原数组,归并排序对[l, r)区间进行排序,
T[]为辅助数组,需要定义这样一个数组,作为参数传进去

使用方法

  1. 定义辅助数组T[],求逆序数的话需要将cnt置零
  2. merge_sort(A, l, r, T); l为区间左端点,r为区间右端点的下一个点
  3. 数组A中[l, r)已排好

    Tipss

  • 注意边界范围是[l, r)
  • 如果求解逆序数,需要保证cnt初始化为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//归并排序
void merge_sort(int *A,int l,int r,int *T){
if (r-l>1) {
int m=l+(r-l)/2; //划分
int p=l,q=m,i=l;
merge_sort(A, l, m, T); //递归求解
merge_sort(A, m, r, T); //递归求解
while (p<m||q<r)
if (q>=r||(p<m&&A[p]<=A[q]))
T[i++]=A[p++]; //从左半数组复制到临时空间
else
T[i++]=A[q++]; //从右半数组复制到临时空间
for (i=l; i<r; i++)
A[i]=T[i]; //从辅助空间复制回A数组
}
}
```
```C++
//归并排序求逆序数
void merge_sort_inversion(int *A,int l,int r,int *T,int *cnt){
if (r-l>1) {
int m=l+(r-l)/2; //划分
int p=l,q=m,i=l;
merge_sort_inversion(A, l, m, T,cnt); //递归求解
merge_sort_inversion(A, m, r, T,cnt); //递归求解
while (p<m||q<r)
if (q>=r||(p<m&&A[p]<=A[q]))
T[i++]=A[p++]; //从左半数组复制到临时空间
else{
T[i++]=A[q++]; //从右半数组复制到临时空间
*cnt += m-p;
}
for (i=l; i<r; i++)
A[i]=T[i]; //从辅助空间复制回A数组
}
}

二分计算

使用lower_bound()以及upper_bound()

三分计算

用于计算区间内极值

使用方法

  1. 定义f()函数
  2. Ternary_Calculate(l, r);其中l, r为对应区间的左右端点值

Tips

  • 该模版用于求极大值,求极小值请改符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
double f(double theta){
return blablabla;
}
const double eps=1e-8;
double Ternary_Calculate(double l, double r){
double mid,midmid,ans;
while (fabs(r-l)>eps) {
mid=(l+r)/2;
midmid=(mid+r)/2;
if(f(mid)<f(midmid)) //求极大值
l=mid;
else
r=midmid;
}
ans=f(l);
return ans;
}

2.博弈

bash博弈

有一堆石子,石子个数为n,两人轮流从石子中取石子出来,最少取一个,最多取m个。最后不能取的人输,问你最后的输赢情况。
结论:当n%(m+1)==0时,先手输,否则先手赢

SG函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
int i,j;
memset(SG,0,sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for(i = 1; i &lt;= n; i++){
//每一次都要将上一状态 的 后继集合 重置
memset(S,0,sizeof(S));
for(j = 0; f[j] <= i && j <= N; j++)
S[SG[i-f[j]]] = 1; //将后继状态的SG函数值进行标记
for(j = 0;; j++) if(!S[j]){ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}

Wythoff博弈

有两堆石子,石子数目分别为n和m,现在两个人轮流从两堆石子中拿石子,每次拿时可以从一堆石子中拿走若干个,也可以从两中拿走相同数量的石子,拿走最后一刻石子的人赢。

结论:如果当前局势未奇异局势则先手必败,否则先手必胜。奇异局势的判断公式为:ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…n 方括号表示取整函数)

可以发现,前面几组奇异局势为(a,b) :(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)

这其中第k组的a为前面没有出现过的最小非负整数,而b = a + k

两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…n 方括号表示取整函数)

使用方法

int ans = wzf(n, m); n和m分别为两堆石子的个数,如果是奇异局势(必败态)则返回0,否则返回1

1
2
3
4
5
6
7
8
9
10
11
int wzf(int n, int m)
{
if(n > m)
swap(n, m);
int k = m-n;
int a = (k * (1.0 + sqrt(5.0))/2.0);
if(a == n)
return 0;
else
return 1;
}

3.数学

奇怪的数学公式系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
1.欧拉筛:
int prime[MAX_N], tot = 0;
bool check[MAX_N];
void getPrime() {
memset(check, 0, sizeof(check));
for (int i = 2; i < MAX_N; i++) {
if (check[i])continue;
prime[tot++] = i;
for (int j = 2 * i; j < MAX_N; j += i)
check[j] = 1;
}
}
//
int prime[MAX_N], tot = 0;
bool check[MAX_N];
void Euler() {
memset(check, 0, sizeof(check));
for (int i = 2; i < MAX_N; i++) {
if (!check[i])prime[tot++] = i;
for (int j = 0; j < tot; j++) {
if (prime[j]*i >= MAX_N)break;
check[prime[j]*i] = 1;
if (i % prime[j] == 0)break;
}
}
}
//
2.快速幂:
long long quickpow(long long m, long long n, long long k) //返回m^n%k
{
long long b = 1;
while (n > 0)
{
if (n & 1)
b = (b * m) % k;
n = n >> 1 ;
m = (m * m) % k;
}
return b;
}
3.快速加:
long long quickplus(long long m, long long n, long long k) //返回m*n%k
{
long long b = 1;
while (n > 0)
{
if (n & 1)
b = (b + m) % k;
n = n >> 1 ;
m = (m + m) % k;
}
return b;
}
4.质因数分解: sqrt(n)
int cnt[maxn];//存储质因子是什么
int num[maxn];//该质因子的个数
int tot = 0;//质因子的数量
void factorization(int x)//输入x,返回cnt数组和num数组
{
for (int i = 2; i * i <= x; i++)
{
if (x % i == 0)
{
cnt[tot] = i;
num[tot] = 0;
while (x % i == 0)
{
x /= i;
num[tot]++;
}
tot++;
}
}
if (x != 1)
{
cnt[tot] = x;
num[tot] = 1;
tot++;
}
}
5.约数和定理
int sum_of_factor(int cnt[], int num[], int mod) //返回因子和 % mod
{
int ans = 1;
for (int i = 0; i < tot; i++)
ans = (ans * Sum_of_geometric_progression(cnt[i], B * num[i], mod)) % mod;
return ans;
}
6.欧拉函数:
在数论,对正整数n,欧拉函数是小于n的数中与n互质的数的数目。如φ(8) = 4,因为1, 3, 5, 7均和8互质。 从欧拉函数引伸出来在环论方面的事实和拉格朗日定理构成了欧拉定理的证明。
φ函数的值:通式:φ(x) = x(1 - 1 / p1)(1 - 1 / p2)(1 - 1 / p3)(1 - 1 / p4)…..(1 - 1 / pn), 其中p1, p2……pn为x的所有质因数,x是不为0的整数。φ(1) = 1(唯一和1互质的数(小于等于1)就是1本身)。 (注意:每种质因数只一个。比如12 = 2 * 2 * 3那么φ(12) = 12 * (1 - 1 / 2) * (1 - 1 / 3) = 4
若n是质数p的k次幂,φ(n) = p ^ k - p ^ (k - 1) = (p - 1)p ^ (k - 1),因为除了p的倍数外,其他数都跟n互质。
设n为正整数,以 φ(n)表示不超过n且与n互素的正整数的个数,称为n的欧拉函数值,这里函数
φ:N→N,n→φ(n)称为欧拉函数。
欧拉函数是积性函数——若m, n互质,φ(mn) = φ(m)φ(n)。
特殊性质:当n为奇数时,φ(2n) = φ(n), 证明与上述类似。
若n为质数则φ(n) = n - 1
long long phi[maxn];
void phi1()
{
memset(phi, 0, sizeof(phi));
phi[1] = 1;
for (long long i = 2; i <= n; i++)
{
if (!phi[i])
{
for (long long j = i; j <= n; j += i)
{
if (!phi[j]) phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
}
}
}
}
另外一种:
long long phi(long long n)
{
long long tmp = n;
for (long long i = 2; i * i <= n; i++)
if (n % i == 0)
{
tmp /= i; tmp *= i - 1;
while (n % i == 0)n /= i;
}
if (n != 1)tmp /= n, tmp *= n - 1;
return tmp;
}
7.排列组合:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 7;
const int mod = 1e9 + 7;
long long fac[maxn];
long long qpow(long long a, long long b)
{
long long ans = 1; a %= mod;
for (long long i = b; i; i >>= 1, a = a * a % mod)
if (i & 1)ans = ans * a % mod;
return ans;
}
long long C(long long n, long long m)
{
if (m > n || m < 0)return 0;
long long s1 = fac[n], s2 = fac[n - m] * fac[m] % mod;
return s1 * qpow(s2, mod - 2) % mod;
}
int main()
{
fac[0] = 1;
for (int i = 1; i < maxn; i++)
fac[i] = fac[i - 1] * i % mod;
}
组合数打表模版:
const int maxn=20;
long long c[maxn][maxn]= {};
void cinit()
{
for(int i=0; i<maxn; i++)
{
c[i][0]=c[i][i]=1;
for(int j=1; j<i; j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
8.逆元 3种方式:
int gcd(int a, int b, int& x, int& y) {
if (!a) {
x = 0, y = 1;
return b;
}
int xx, yy, g = gcd(b % a, a, xx, yy);
x = yy - b / a * xx;
y = xx;
return g;
}
inline int normal(int n) {
n %= mod;
(n < 0) && (n += mod);
return n;
}
inline int inv(int a) {
int x, y;
assert(gcd(a, mod, x, y) == 1);
return normal(x);
}
long long inv(long long n) {
long long ans = 1;
int c = MOD - 2;
while (c) {
if (c & 1) ans = ans * n % MOD;
n = n * n % MOD;
c >>= 1;
}
return ans;
}
mod为素数建议用这两个:
inv[1] = 1;
for (int i = 2; i <= n; i++)
inv[i] = (p - p / a) * inv[p % a] % p;
long long inv(long long k){
if(k == 1) return 1;
return (mod - mod / k) * inv(mod % k) % mod;
}
9.统计二进制有多少个1
__builtin_popcount()
10.论文题:n内素数个数
#include <bits/stdc++.h>
using namespace std;
#define MAXN 100
#define MAXM 50010
#define MAXP 166666
#define MAX 1000010
#define clr(ar) memset(ar, 0, sizeof(ar))
#define chkbit(ar, i) (((ar[(i) >> 6]) & (1 << (((i) >> 1) & 31))))
#define setbit(ar, i) (((ar[(i) >> 6]) |= (1 << (((i) >> 1) & 31))))
#define isprime(x) (( (x) && ((x)&1) && (!chkbit(ar, (x)))) || ((x) == 2))
using namespace std;
namespace pcf{
long long dp[MAXN][MAXM];
unsigned int ar[(MAX >> 6) + 5] = {0};
int len = 0, primes[MAXP], counter[MAX];
void Sieve() {
setbit(ar, 0), setbit(ar, 1);
for (int i = 3; (i * i) < MAX; i++, i++) {
if (!chkbit(ar, i)) {
int k = i << 1;
for (int j = (i * i); j < MAX; j += k) setbit(ar, j);
}
}
for (int i = 1; i < MAX; i++) {
counter[i] = counter[i - 1];
if (isprime(i)) primes[len++] = i, counter[i]++;
}
}
void init() {
Sieve();
for (int n = 0; n < MAXN; n++) {
for (int m = 0; m < MAXM; m++) {
if (!n) dp[n][m] = m;
else dp[n][m] = dp[n - 1][m] - dp[n - 1][m / primes[n - 1]];
}
}
}
long long phi(long long m, int n) {
if (n == 0) return m;
if (primes[n - 1] >= m) return 1;
if (m < MAXM && n < MAXN) return dp[n][m];
return phi(m, n - 1) - phi(m / primes[n - 1], n - 1);
}
long long Lehmer(long long m) {
if (m < MAX) return counter[m];
long long w, res = 0;
int i, a, s, c, x, y;
s = sqrt(0.9 + m), y = c = cbrt(0.9 + m);
a = counter[y], res = phi(m, a) + a - 1;
for (i = a; primes[i] <= s; i++) res = res - Lehmer(m / primes[i]) + Lehmer(primes[i]) - 1;
return res;
}
}
int main()
{
pcf::init();
long long n;
while (cin >> n)
cout << pcf::Lehmer(n) << endl;
}
11.L-U,区间筛素数:
const int maxn = 1000010;
int PrimeList[maxn];
int PrimeNum;
bool IsNotPrime[maxn]; // IsNotPrime[i] = 1表示i + L这个数是素数.
void SegmentPrime(int L, int U)
{ // 求区间[L, U]中的素数.
int i, j;
int SU = sqrt(1.0 * U);
int d = U - L + 1;
for (i = 0; i < d; i++) IsNotPrime[i] = 0; // 一开始全是素数.
for (i = (L % 2 != 0); i < d; i += 2) IsNotPrime[i] = 1; // 把偶数的直接去掉.
for (i = 3; i <= SU; i += 2)
{
if (i > L && IsNotPrime[i - L]) continue; // IsNotPrime[i - L] == 1说明i不是素数.
j = (L / i) * i; // j为i的倍数,且最接近L的数.
if (j < L) j += i;
if (j == i) j += i; // i为素数,j = i说明j也是素数,所以直接 + i.
j = j - L;
for (; j < d; j += i) IsNotPrime[j] = 1; // 说明j不是素数(IsNotPrime[j - L] = 1).
}
if (L <= 1) IsNotPrime[1 - L] = 1;
if (L <= 2) IsNotPrime[2 - L] = 0;
PrimeNum = 0;
for (i = 0; i < d; i++) if (!IsNotPrime[i]) PrimeList[PrimeNum++] = i + L;
}
/* [1,n]与a互素个数 O(sqrt n)
先对a分解质因数
然后用容斥原理 */
int fac[50];
int solve (int n, int a){
int i,j,up,t,cnt=0,sum=0,flag;
for(i=2;i*i<=a;i++)
if(a%i==0){
fac[cnt++]=i;
while(a%i==0)a/=i;
}
if(a>1)fac[cnt++]=a;
up=1<<cnt;
for(i=1;i<up;i++){ //容斥原理,二进制枚举
flag=0,t=1;
for(j=0;j<cnt;j++){
if(i&(1<<j)){
flag^=1;
t*=fac[j];
}
}
sum+=flag?n/t:-(n/t);
}
return n-sum;
}
12.降幂公式
A^x = A^(x % Phi(C) + Phi(C)) (mod C),其中x≥Phi(C) 这个降幂公式适用于C不是素数的情况
A ^ X % C = A ^ ( X % ( C - 1 ) ) 这个降幂公式只适用于C是素数的情况

组合数学

公式

  1. C(n, 0) = C(n, n) = 1
  2. C(n, k) = C(n, n-k)
  3. C(n, k) + C(n, k+1) = C(n+1, k+1)
  4. C(n, k+1)= C(n, k)*(n-k)/(k+1)

特殊的数列

名称 数列
斐波那契数列 1 1 2 3 5 8
卡特兰数 1 2 5 14 42 132 429 1430 4862 16796 ($(4n-6)/n*f(n-1)$)

斯特林数stirling

  • 第一类 stirling数 s(n, k)

n个人分成k组,组内再按特定顺序围圈

也就是分成了k组,组内就像是项链颜色一样,

  1. ( {A, B}, {C, D} )
  2. ( {B, A}, {C, D} )

属于一组

  1. ({A}, {B, C, D})
  2. ({A}, {B, D, C})

不属于一组

公式:

给定 $s(n,0)=0,s(1,1)=1$,有递归关系$s(n,k)=s(n-1,k-1) + (n-1) s(n-1,k)$

第二类 stirling数

S(n, k) 是把p元素集合划分到k个不可区分的盒子里且没有空盒的划分个数。

公式:

$$ S(n, n) = 1 (n >= 0) $$
$$ S(n, 0) = 0 (n >= 1) $$
$$ S(n,k)=k*S(n-1,k)+S(n-1,k-1),\text (1<=k<=n-1) $$

错排问题


考虑一个有n个元素的排列,
若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。

D[n] = (n-1)(D[n-1]+D[n-2])

D[n] = floor(n!/e + 0.5)

中国剩余定理

中国剩余定理用于求模方程组 方程组形如 x%divisor[i] = rest[i]

使用方法

  1. 将n个方程的模域和余数存入divisor[]和rest[]中,下标从0~n-1
  2. x = CRT1(n)返回值即为一个符合条件的解,其它符合条件的解为 x + Π(divisor[i])

Tips

  1. 考虑数据范围,如果求模域的时候可能爆long long请使用快速乘
  2. 模域是否两两互质下面CRT1CRT2对号入座
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const int maxn=1000+5;
typedef long long ll;
ll mult_mod(ll a,ll b,ll mod){
return (a*b-(ll)(a/(long double)mod*b+1e-3)*mod+mod)%mod;
}
ll ex_gcd(ll a,ll b,ll &x,ll &y){
if (b==0) {
x=1,y=0;
return a;
}
else{
ll res=ex_gcd(b,a%b,y,x);
y-=x*(a/b);
return res;
}
}
// 中国剩余定理 模域两两互质 x%(divisor[i])=rest[i]
ll CRT1(ll divisor[],ll rest[], int n){ //n表示有n个方程,0~n-1
ll gcd,tmp,product=1,res=0,x,y;
for (int i=0; i<n; i++)
product*=divisor[i];
for (int i=0; i<n; i++) {
tmp=product/divisor[i];
gcd=ex_gcd(divisor[i], tmp, x, y);
res = (res + mult_mod(mult_mod(y, tmp, product), rest[i], product))%product;//防爆
// res=(res+y*tmp%product*rest[i]%product)%product;
}
return (res+ product)%product;//返回值为符合模方程组的解
}
// 中国剩余定理非互质版 有解返回解,无解返回-1 x%(divisor[i])=rest[i]
ll CRT2(ll divisor[], ll rest[], int n) { //n表示有n个方程,0~n-1
if (n == 1) {
if (divisor[0] > rest[0]) return rest[0];
else return -1;
}
ll x, y, d;
for (int i = 1; i < n; i++) {
if (divisor[i] <= rest[i]) return -1;
d = ex_gcd(divisor[0], divisor[i], x, y);
if ((rest[i] - rest[0]) % d != 0) return -1;
ll t = divisor[i] / d;
x = ((rest[i] - rest[0]) / d * x % t + t) % t;
rest[0] = x * divisor[0] + rest[0];
divisor[0] = divisor[0] * divisor[i] / d;
rest[0] = (rest[0] % divisor[0] + divisor[0]) % divisor[0];
}
return rest[0];
}

单变元模线性方程

说明

已知a,b,n, 求x, 使得 ax ≡ b(mod n)

输入a,b,n 输出所有[0, n)中的解(个数为gcd(a,n))

要注意在处理过程中,如果 b 是负数,需要转换:b = ((b % n) + n) % n;

复杂度:O(logN)

使用方法

  • vector<ll> ans = line_mod_equation(a, b, n);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ll ex_gcd(ll a,ll b,ll &x,ll &y){
if (b==0) {
x=1,y=0;
return a;
}
else{
ll r=ex_gcd(b,a%b,y,x);
y-=x*(a/b);
return r;
}
}
vector<ll> line_mod_equation(ll a, ll b, ll n) {
ll x, y;
ll d = extend_gcd(a, n, x, y);
vector<ll> ans;
ans.clear();
if (b % d == 0) {
x = (x % n + n) % n;
x %= (n / d);
ans.push_back(x * (b / d) % (n / d));
for (ll i = 1; i < d; i++) ans.push_back((ans[0] + i * n / d) % n);
}
return ans;
}

矩阵快速幂

//汤姆猫大法好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 2147493647;
const int inf = 0x3f3f3f3f;
#define rep(i, n) for(int i=0;i<int(n);++i)
#define clr(a) memset(a, 0, sizeof(a))
#define dbg(x) cout << #x << " : "<< (x) <<endl
template <class T> inline void in(T &x) {
char c; while ((c = getchar()) < '0' || c > '9');
x = c - '0';
while ((c = getchar()) >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0';
}
/* ........................................................................... */
const int N = 10005;
typedef vector<ll> vec;
typedef vector<vec> mat;
mat mul(mat &A, mat &B) {
mat C(A.size(), vec(B[0].size()));
for (int i = 0; i < A.size(); i++) {
for (int k = 0; k < B.size(); k++) {
for (int j = 0; j < B[0].size(); j++) {
C[i][j] = (C[i][j] + A[i][k] * B[k][j] + mod) % mod;
}
}
}
return C;
}
mat pow(mat A, ll n) {
mat B(A.size(), vec(A.size()));
for (int i = 0; i < A.size(); i++) B[i][i] = 1;
while (n > 0) {
if (n & 1) B = mul(B, A);
A = mul(A, A);
n >>= 1;
}
return B;
}
ll n, T, a, b;
mat A = {
{1, 1, 1, 1, 1, 1, 0},
{0, 1, 2, 3, 4, 4, 0},
{0, 0, 1, 3, 6, 6, 0},
{0, 0, 0, 1, 4, 4, 0},
{0, 0, 0, 0, 1, 1, 0},
{0, 0, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 2, 0}
};
int main(int argc, char const *argv[])
{
ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
cin >> T;
while (T--) {
mat A = {
{1, 1, 1, 1, 1, 1, 0},
{0, 1, 2, 3, 4, 4, 0},
{0, 0, 1, 3, 6, 6, 0},
{0, 0, 0, 1, 4, 4, 0},
{0, 0, 0, 0, 1, 1, 0},
{0, 0, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 2, 0}
};
cin >> n >> a >> b;
if (n == 1) cout << a << endl;
else if (n == 2) cout << b << endl;
else {
A = pow(A, n - 2);
ll ans = 0;
ans = (ans + A[6][5] * a % mod) % mod;
ans = (ans + A[5][5] * b % mod) % mod;
ans = (ans + A[4][5] * 16 % mod) % mod;
ans = (ans + A[3][5] * 8 % mod) % mod;
ans = (ans + A[2][5] * 4 % mod) % mod;
ans = (ans + A[1][5] * 2 % mod) % mod;
ans = (ans + A[0][5]) % mod;
cout << ans << endl;
}
}
}

自适应Simpson求积分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
求某一段的积分
*/
double F(double x)
{
///积分函数
}
/// 3-points simpson
double simpson(double a,double b)
{
double c=(a+b)/2;
return (F(a)+4*F(c)+F(b))*(b-a)/6.;
}
double asr(double a,double b,double eps,double A)
{
double c=(a+b)/2;
double L=simpson(a,c),R=simpson(c,b);
if(fabs(L+R-A)<=15*eps) return L+R+(L+R-A)/15.0;
return asr(a,c,eps/2,L)+asr(c,b,eps/2,R);
}
double ASR(double a,double b,double eps)
{
return asr(a,b,eps,simpson(a,b));
}

FFT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const double PI = acos(-1.0);
//复数结构体
struct Complex
{
double r,i;
Complex(double _r = 0.0,double _i = 0.0)
{
r = _r; i = _i;
}
Complex operator +(const Complex &b)
{
return Complex(r+b.r,i+b.i);
}
Complex operator -(const Complex &b)
{
return Complex(r-b.r,i-b.i);
}
Complex operator *(const Complex &b)
{
return Complex(r*b.r-i*b.i,r*b.i+i*b.r);
}
};
/*
* 进行FFT和IFFT前的反转变换。
* 位置i和 (i二进制反转后位置)互换
* len必须去2的幂
*/
void change(Complex y[],int len)
{
int i,j,k;
for(i = 1, j = len/2;i < len-1; i++)
{
if(i < j)swap(y[i],y[j]);
//交换互为小标反转的元素,i<j保证交换一次
//i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
k = len/2;
while( j >= k)
{
j -= k;
k /= 2;
}
if(j < k) j += k;
}
}
/*
* 做FFT
* len必须为2^k形式,
* on==1时是DFT,on==-1时是IDFT
*/
void fft(Complex y[],int len,int on)
{
change(y,len);
for(int h = 2; h <= len; h <<= 1)
{
Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
for(int j = 0;j < len;j+=h)
{
Complex w(1,0);
for(int k = j;k < j+h/2;k++)
{
Complex u = y[k];
Complex t = w*y[k+h/2];
y[k] = u+t;
y[k+h/2] = u-t;
w = w*wn;
}
}
}
if(on == -1)
for(int i = 0;i < len;i++)
y[i].r /= len;
}

NTT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
typedef long long ll;
const ll Mod=998244353; // 模数
const ll SpMul=3; // 原根
ll qpow(ll a,ll k)
{
ll res=1LL;
while(k>0)
{
if(k&1)res=res*a%Mod;
a=a*a%Mod;
k>>=1;
}
return res;
}
void Change(ll y[],int len)
{
for(int i=1,j=len/2;i<len-1;i++)
{
if(i<j)swap(y[i],y[j]);
int k=len/2;
while(j>=k)
{
j-=k;
k/=2;
}
if(j<k)j+=k;
}
}
void NTT(ll y[],int len,int on)
{
Change(y,len);
for(int h=2;h<=len;h<<=1)
{
ll wn=qpow(SpMul,(Mod-1)/h);
if(on==-1)wn=qpow(wn,Mod-2);
for(int j=0;j<len;j+=h)
{
ll w=1LL;
for(int k=j;k<j+h/2;k++)
{
ll u=y[k];
ll t=w*y[k+h/2]%Mod;
y[k]=(u+t)%Mod;
y[k+h/2]=(u-t+Mod)%Mod;
w=w*wn%Mod;
}
}
}
if(on==-1)
{
ll t=qpow(len,Mod-2);
for(int i=0;i<len;i++)
y[i]=y[i]*t%Mod;
}
}

高斯消元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* gauss_elimination O(n^3)
return 1,有解,return 0,无解。。
n个方程n个变元
要求系数矩阵可逆
A[][]是增广矩阵,即A[i][n]是第i个方程右边的常数bi
运行结束后A[i][n]是第i个未知数的值 */
double A[N][N];
int gauss(int n)
{
int i,j,k,r;
for(i=0;i<n;i++){
//选一行与r与第i行交换,提高数据值的稳定性
r=i;
for(j=i+1;j<n;j++)
if(fabs(A[j][i]) > fabs(A[r][i]))r=j;
if(r!=i)for(j=0;j<=n;j++)swap(A[r][j],A[i][j]);
//i行与i+1~n行消元
/* for(k=i+1;k<n;k++){ //从小到大消元,中间变量f会有损失
double f=A[k][i]/A[i][i];
for(j=i;j<=n;j++)A[k][j]-=f*A[i][j];
}*/
for(j=n;j>=i;j--){ //从大到小消元,精度更高
for(k=i+1;k<n;k++)
A[k][j]-=A[k][i]/A[i][i]*A[i][j];
}
}
//判断方程时候有解
for(i=0;i<n;i++)if(sign(A[i][i])==0)return 0;
//回代过程
for(i=n-1;i>=0;i--){
for(j=i+1;j<n;j++)
A[i][n]-=A[j][n]*A[i][j];
A[i][n]/=A[i][i];
}
}
/* 整数矩阵 O( n^3 )
高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var. */
int a[N][N];//增广矩阵
int x[N];//解集
bool free_x[N];//标记是否是不确定的变元
int n,m,k;
//
void Debug(int equ,int var)
{
int i, j;
for (i = 0; i < equ; i++)
{
for (j = 0; j < var + 1; j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
//
inline int gcd(int a,int b)
{
int t;
while(b!=0)
{
t=b;
b=a%b;
a=t;
}
return a;
}
inline int lcm(int a,int b)
{
return a/gcd(a,b)*b;//先除后乘防溢出
}
// 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
//-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
//有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var.
int Gauss(int equ,int var)
{
int i,j,k;
int max_r; // 当前这列绝对值最大的行.
int col; //当前处理的列
int ta,tb;
int LCM;
int temp;
int free_x_num;
int free_index;
for(int i=0;i<=var;i++)
{
x[i]=0;
free_x[i]=true;
}
//转换为阶梯阵.
col=0; // 当前处理的列
for(k = 0;k < equ && col < var;k++,col++)
{// 枚举当前处理的行.
// 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
max_r=k;
for(i=k+1;i<equ;i++)
{
if(abs(a[i][col])>abs(a[max_r][col])) max_r=i;
}
if(max_r!=k)
{// 与第k行交换.
for(j=k;j<var+1;j++) swap(a[k][j],a[max_r][j]);
}
if(a[k][col]==0)
{// 说明该col列第k行以下全是0了,则处理当前行的下一列.
k--;
continue;
}
for(i=k+1;i<equ;i++) // i=0高斯约当消元,才能在多解的情况下判断变元是否确定
{// 枚举要删去的行.
if(a[i][col]!=0 && i!=k)
{
LCM = lcm(abs(a[i][col]),abs(a[k][col]));
ta = LCM/abs(a[i][col]);
tb = LCM/abs(a[k][col]);
if(a[i][col]*a[k][col]<0)tb=-tb;//异号的情况是相加
for(j=0;j<var+1;j++)
{
a[i][j] = a[i][j]*ta - a[k][j]*tb;
}
}
}
}
// Debug(equ,var);
// 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
for (i = k; i < equ; i++)
{
if (a[i][col] != 0) return -1;
}
// 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
// 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
// 且出现的行数即为自由变元的个数.
if (k < var)return 1;
{
// 首先,自由变元有var - k个,即不确定的变元至少有var - k个.
for (i = k - 1; i >= 0; i--)
{
// 第i行一定不会是(0, 0, ..., 0)的情况,因为这样的行是在第k行到第equ行.
// 同样,第i行一定不会是(0, 0, ..., a), a != 0的情况,这样的无解的.
free_x_num = 0; // 用于判断该行中的不确定的变元的个数,如果超过1个,则无法求解,它们仍然为不确定的变元.
for (j = 0; j < var; j++)
{
if (a[i][j] != 0 && free_x[j]) free_x_num++, free_index = j;
}
if (free_x_num > 1) continue; // 无法求解出确定的变元.
// 说明就只有一个不确定的变元free_index,那么可以求解出该变元,且该变元是确定的.
temp = a[i][var];
for (j = 0; j < var; j++)
{
if (a[i][j] != 0 && j != free_index) temp -= a[i][j] * x[j];
}
x[free_index] = temp / a[i][free_index]; // 求出该变元.
free_x[free_index] = 0; // 该变元是确定的.
}
return var - k; // 自由变元有var - k个.
}
// 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
// 计算出Xn-1, Xn-2 ... X0.
for (i = var - 1; i >= 0; i--)
{
temp = a[i][var];
for (j = i + 1; j < var; j++)
{
if (a[i][j] != 0) temp -= a[i][j] * x[j];
}
if (temp % a[i][i] != 0) return -2; // 说明有浮点数解,但无整数解.
x[i] = temp / a[i][i];
}
return 0;
}
/* 异或矩阵 O( n^3 )
高斯-约当消元
如果无解return -1,如果有唯一解,返回变化个数
如果方程有多个解,则二进制枚举自由变元O(2^n),返回最小变化个数!,
n个方程n个变元
A[][]增广矩阵
B[]结果矩阵
is_free[]在有多解的情况下,判断元素解是否唯一 */
int A[N][N],B[N],is_free[N],num[N];
void getA(int n)
{
/* 得到增光矩阵A[][] */
}
int gauss(int n)
{
int i,j,k,cnt,row,ok,ret,up,cnt_free;
for(i=row=0;i<n;i++){
if(!A[row][i]){
for(j=row+1;j<n;j++){
if(A[j][i]){
for(k=i;k<=n;k++)swap(A[row][k],A[j][k]);
break;
}
}
}
if(A[row][i]!=1)continue; //保证为严格的阶梯矩阵
for(j=0;j<n;j++){ //从0开始,高斯约当消元
if(j!=row && A[j][i]){
for(k=i;k<=n;k++)
A[j][k]^=A[row][k];
}
}
row++;
}
for(i=n-1;i>=row;i--)
if(A[i][n])return -1; //无解
if(row==n){ //唯一解
for(i=ret=0;i<n;i++)if(A[i][n])ret++;
return ret;
}
mem(is_free,0);
for(i=k=j=0;i<n;i++,j++){
while(!A[i][j] && j<n){
is_free[j]=1; //判断元素是否解唯一
num[k++]=j++;
}
}
ret=INF;cnt_free=n-row; //自由变元个数
up=1<<cnt_free;
for(k=0;k<up;k++){ //枚举最小的变换个数
for(i=0;i<cnt_free;i++)B[num[i]]=(k&(1<<i))?1:0;
for(i=n-1;i>=0;i--){
if(is_free[i])continue;
B[i]=0;
for(j=row;j<n;j++)B[i]^=B[j]*A[i][j];
B[i]^=A[i][n];
}
for(i=cnt=0;i<n;i++)if(B[i])cnt++;
ret=Min(ret,cnt);
}
return ret; //返回最小的变换个数
}
//(高精度)
const int MatrixSize = 50;
const double eps = 1e-8;
typedef double Matrix[MatrixSize][MatrixSize];
void Gauss(Matrix A, int n)
{
int i, j, k, r;
for(i = 0; i < n; ++ i)
{
r = i;
for(j = i+1; j < n; ++ j)
if (fabs(A[j][i]) > fabs(A[r][i])) r = j;
if(fabs(A[r][i]) < eps) continue;
if(r != i) for(j = 0; j <= n; j++) swap(A[r][j], A[i][j]);
for(k = 0; k < n; k++) if(k != i)
for(j = n; j >= i; j--) A[k][j] -= A[k][i]/A[i][i] * A[i][j];
}
for(i = 0 ; i < n ; ++ i)
{
if(fabs(A[i][n]) < eps) A[i][n] = 0;
else A[i][n] /= A[i][i];
}
}

莫比乌斯反演

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int normal[maxn];
int mu[maxn];
int prime[maxn];
int pcnt;
void Init()
{
memset(normal,0,sizeof(normal));
mu[1] = 1;
pcnt = 0;
for(int i=2; i<maxn; i++)
{
if(!normal[i])
{
prime[pcnt++] = i;
mu[i] = -1;
}
for(int j=0; j<pcnt&&i*prime[j]<maxn; j++)
{
normal[i*prime[j]] = 1;
if(i%prime[j]) mu[i*prime[j]] = -mu[i];
else
{
mu[i*prime[j]] = 0;
break;
}
}
}
}

4.字符串

KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
next[]的含义是 x[i-next[i]……i-1]=next[0……next[i]-1]
next[i]满足x[i-z……i-1]=x[0……z-1]的最大z值(就是x的自身匹配)
*/
void kmp_pre(char x[],int m,int next[])
{
int i,j;
j=next[0]=-1;
i=0;
while(i<m)
{
while(-1!=j&&x[i]!=x[j])j=next[j];
next[++i]=++j;
}
}
/*
返回x在y中出现的次数,可以重叠
*/
int next[maxn];
int Kmp_count(char x[],int m,char y[],int n)
{
//x是模式串,y是主串
int i,j;
int ans=0;
//preKmp(x,m,next);
kmp_pre(x,m,next);
i=j=0;
while(i<n)
{
while(-1!=j&&y[i]!=x[j])j=next[j];
i++;j++;
if(j>=m)
{
ans++;
j=next[j];
}
}
return ans;
}

AC自动机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <bits/stdc++.h>
using namespace std;
#define N 100005
#define M 26
//AC自动机模板
struct AC {
int nexta[N][M];
int faila[N];
int enda[N];
int L;
int root;
int newnode() {
for (int i = 0; i < M; i++) {
nexta[L][i] = -1;
}
enda[L++] = 0;
return L - 1;
}
void init() {
L = 0;
root = newnode();
}
void insert(char word[]) {
int now = root;
for (int i = 0; word[i] != '\0'; i++) {
int tempch = word[i] - 'a';
if (nexta[now][tempch] == -1) {
nexta[now][tempch] = newnode();
}
now = nexta[now][tempch];
}
enda[now]++;
}
void build() {
queue<int> q;
for (int i = 0; i < M; i++) {
if (nexta[root][i] == -1) {
nexta[root][i] = root;
}
else {
faila[nexta[root][i]] = root;
q.push(nexta[root][i]);
}
}
while (!q.empty()) {
int temp = q.front();
q.pop();
for (int i = 0; i < M; i++) {
if (nexta[temp][i] == -1) {
nexta[temp][i] = nexta[faila[temp]][i];
}
else {
faila[nexta[temp][i]] = nexta[faila[temp]][i];
q.push(nexta[temp][i]);
}
}
}
}
long long int query(char sentence[]) {
int now = root;
long long int ans = 0;
for (int i = 0; sentence[i] != '\0'; i++) {
now = nexta[now][sentence[i] - 'a'];
int temp = now;
while (temp != root) {
ans += (long long int)enda[temp];
temp = faila[temp];
}
}
return ans;
}
} ac;
char buf[100005];
char buff[10005][100005];
int main()
{
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
int T, n, m;
scanf("%d", &T);
while ( T-- ) {
scanf("%d %d", &n, &m);
ac.init();
for (int i = 0; i < n; i++) scanf("%s", buff[i]);
for (int i = 0; i < m; i++) {
scanf("%s", buf);
ac.insert(buf);
}
ac.build();
for (int i = 0; i < n; i++) cout << ac.query(buff[i]) << endl;
}
}

Manacher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <cstring>
using namespace std;
char str[2000005];
int fast(char *p) {
int ans = 1;
for (int i = 0; p[i]; ++i) {
int s = i, e = i, t;
while (p[e + 1] == p[i]) ++e;
i = e;
while (p[s - 1] == p[e + 1]) --s, ++e;
if ((t = e - s + 1) > ans) ans = t;
}
return ans;
}
int main(int argc, char const *argv[])
{
int T; cin >> T;
while (T--) {
cin >> (str + 1);
str[0] = '$';
cout << fast(str) << endl;
}
}

Trie树

用于查找对于若干个字符串,查找某字符串作为前缀出现的次数

  • maxNode :最大节点的数量
  • sigma_size:每个节点的子节点个数
  • sz :表示字典树中下一个节点的下标,根节点编号为0,不指向任何字符
  • val[] :到当前位置的前缀串出现的次数

使用方法

  1. init();初始化
  2. insert()插入所有字符串
  3. query()查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
const int maxNode=1005*25;
const int sigma_size=26;
int c[maxNode][sigma_size];
// int val[maxNode]; //val[i]用于记录以下标以i结尾的辅助信息,如权值
int cnt[maxNode];
int sz;
void init()
{
sz=1;
memset(c[0],0,sizeof(c[0]));
memset(cnt,0,sizeof(cnt));
}
int idx(char ch){
return ch - 'a';
}
void insert(char s[],int v=0) //v表示该字符串的辅助信息,如权值等,使用时需要取消对应注释
{
int u=0;
for (int i=0; s[i]; i++)
{
char ch = idx(s[i]);
if (!c[u][ch])
{
memset(c[sz],0,sizeof(c[sz]));
// val[sz]=0;
c[u][ch]=sz++;
}
u=c[u][ch];
cnt[u]++;
}
// val[u]=v;
}
int query(char s[])
{
int u=0,n=strlen(s);
for (int i=0; i<n; i++)
{
char ch = idx(s[i]);
if (!c[u][ch]||u!=0&&cnt[u]<=1)
return i; //查询最大匹配长度
u=c[u][ch];
}
return n; //查询最大匹配长度
// return cnt[u]; /*查询该字符串出现的次数*/
}
//可持久化字典树:
// Made by xiper
// updata time : 2015 / 12 / 8
// test status: √
// 使用前调用初始化函数 init() 同时 root[0] = 0;
struct Trie_Persistent
{
const static int LetterSize = 5; // 字符集大小
const static int TrieSize = 20 * ( 1e5 + 50); // 可能的所有节点总数量
int tot; // 节点总数
//节点类型
struct node
{
int ptr[LetterSize]; // trie_node_ptr[]
int cnt[LetterSize]; // 维护字符集数目
} tree[TrieSize];
// 获取字符集哈希编号 , 必须在 [0 , LetterSize) 之内
inline int GetLetterIdx(int c) {return c - 'a';}
// 插入字符串 str , 上一个副本是 f
int insert(const char * str , int f) {
int len = strlen( str );
int res = tot++; // 建立虚拟根结点
tree[res] = tree[f]; // 初始化
int cur = res; // 当前指针
for (int i = 0 ; i < len ; ++ i) {
int idx = GetLetterIdx( str[i] ); // 获取字符编号
int p = tot ++ ; // 创建下一个虚拟节点
tree[cur].cnt[idx] ++ ;
tree[cur].ptr[idx] = p;
f = tree[f].ptr[idx]; // 上一个副本的指针更新
tree[p] = tree[f]; // updata information;
cur = tree[cur].ptr[idx]; // updata ptr
}
return res;
}
// 在 [l ,r] 副本中查找字符串str
// 参数带入( str , root[l-1] , root[r])
int find(const char * str , int l , int r) {
int len = strlen(str);
for (int i = 0 ; i < len ; ++ i) {
int idx = GetLetterIdx ( str[i] ); // 获取字符Hash
int cnt = tree[r].cnt[idx] - tree[l].cnt[idx];
if (!cnt) return 0;
l = tree[l].ptr[idx];
r = tree[r].ptr[idx];
}
return 1;
}
void init() {tot = 1; for (int i = 0 ; i < LetterSize ; ++ i) tree[0].ptr[i] = 0 , tree[0].cnt[i] = 0; } // 虚拟节点
} trie;

字符串哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
// BKDR Hash Function
unsigned int BKDRHash(char *str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}

后缀数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* suffix array
倍增算法 O(n*lgn)
build_sa(num,n+1,m) 注意n+1,每个字符的值为0~m-1
getHeight(num,n)
rmq_init(height) 初始化rmq,传递height数组
rmq(a+1,b) 求排名分别为为a和b的最长公共前缀
lcp(a,b) 求后缀a和后缀b的最长公共前缀
n = 8 ;
num[] = { 1, 1, 2, 1, 1, 1, 1, 2, $ }. 注意num数组最后一位值为0,其它位须大于0!
rank[] = { 4, 6, 8, 1, 2, 3, 5, 7, 0 }. (rank[0~n-1]为有效值)
sa[] = { 8, 3, 4, 5, 0, 6, 1, 7, 2 }. (sa[1~n]为有效值)
height[] = { 0, 0, 3, 2, 3, 1, 2, 0, 1 }. (height[2~n]为有效值) */
char s[N];
int d[N][20];
int num[N];
int sa[N],t1[N],t2[N],c[N],rank[N],height[N];
int n,m;
void build_sa(int s[],int n,int m)
{
int i,k,p,*x=t1,*y=t2;
//第一轮基数排序
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=s[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(k=1;k<=n;k<<=1){
p=0;
//直接利用sa数组排序第二关键字
for(i=n-k;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
//基数排序第一关键字
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
//根据sa和x数组计算新的x数组
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
if(p>=n)break; //已经排好序,直接退出
m=p; //下次基数排序的最大值
}
}
void getHeight(int s[],int n)
{
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(s[i+k]==s[j+k])k++;
height[rank[i]]=k;
}
}
void rmq_init(int a[])
{
int i,j;
for(i=1;i<=n;i++)d[i][0]=a[i];
for(j=1;(1<<j)<=n;j++){
for(i=1;i+(1<<j)-1<=n;i++){
d[i][j]=Min(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
}
}
int rmq(int l,int r)
{
int k=0;
while((1<<(k+1))<=r-l+1)k++;
return Min(d[l][k],d[r-(1<<k)+1][k]);
}
int lcp(int a,int b)
{
if(a==b)return n-a; //a和b为同一后缀,直接输出,字串串长度为n
int ra=rank[a],rb=rank[b];
if(ra>rb)swap(ra,rb);
return rmq(ra+1,rb);
}

字符串最小表示法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//字符串呈环状,每一位置都可以作为首位,找出以哪个位置为
// 开头,可以使得这个字符串的字典序最小(或最大
int getmin(char s[])
{
int i=0,j=1,k=0;//i和j是两个进行比较的起始匹配位点,k是匹配长度
int len=strlen(s);
while(i<len&&j<len&&k<len)
{
int t=s[(i+k)%len]-s[(j+k)%len];//比较两个串的大小关系
if(t==0)k++;//如果相同,匹配长度增大,比较位置向移
else //如果不同,则字典序大的位置肯定不会是答案,改变那个匹配位点
{
if(t>0)i+=k+1;
else j+=k+1;
if(i==j)j++;//i和j一定要错开
k=0;//匹配长度要重置为0
}
}
return i<j?i:j;//因为字典序大的位置被后移了,所以较小的位置就是答案
}

5.数据结构

并查集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int pre[maxn];
void init(int n) {
for (int i = 0; i < n; i++) pre[i] = i;
}
int find(int x) {
if (pre[x] == x) return x;
else return pre[x] = find(pre[x]);
//int t = pre[x]; pre[x] = find(pre[x]); sum[x] += sum[t];
}
void unite(int x, int y) {
x = find(x), y = find(y);
if(x != y) pre[y] = x;
}
bool same(int x, int y) {
return find(x) == find(y);
}

splay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
struct SplayTree {
const static int maxn = 1e5 + 15;
int ch[maxn][2] , key[maxn] , s[maxn] , tot , root , fa[maxn];
void init( int x , int val = 0 , int par = 0 ) {
ch[x][0] = ch[x][1] = 0 , fa[x] = par , key[x] = val , s[x] = 1;
}
void init() {
init( 0 , 0 , 0 ); s[0] = 0;
tot = root = 0 ;
}
inline void up(int x) {
s[x] = s[ch[x][0]] + s[ch[x][1]] + 1;
}
void rotate( int x, int d ) {
int y = fa[x];
ch[y][d ^ 1] = ch[x][d];
if ( ch[x][d]) fa[ch[x][d]] = y;
fa[x] = fa[y];
if (fa[y]) {
if (y == ch[fa[y]][d]) ch[fa[y]][d] = x;
else ch[fa[y]][d ^ 1] = x;
}
ch[x][d] = y , fa[y] = x;
up( y ) , up( x );
}
// Splay x to target's son
void Splay( int x , int target ) {
while ( fa[x] != target ) {
int y = fa[x];
if ( x == ch[y][0] ) {
if ( fa[y] != target && y == ch[fa[y]][0])
rotate( y , 1 );
rotate( x , 1 );
} else {
if ( fa[y] != target && y == ch[fa[y]][1])
rotate( y , 0 );
rotate( x , 0 );
}
}
if ( !target ) root = x;
}
void Insert( int & t , int val , int par = 0 ) {
if ( t == 0 ) {
t = ++ tot;
init( t , val , par );
Splay( tot , 0 );
} else {
int cur = t;
if ( val < key[t] ) Insert( ch[t][0] , val , cur );
else Insert( ch[t][1] , val , cur );
up( cur );
}
}
// Return point
int find( int t , int v ) {
if ( t == 0 ) return 0;
else if ( key[t] == v ) {
Splay( t , 0 );
return t;
}
else if ( v < key[t] ) return find( ch[t][0] , v );
return find( ch[t][1] , v );
}
// Delete Root
void Delete() {
if ( !ch[root][0] ) {
fa[ ch[root][1] ] = 0 ;
root = ch[root][1];
} else {
int cur = ch[root][0];
while ( ch[cur][1] ) cur = ch[cur][1];
Splay( cur , root );
ch[cur][1] = ch[root][1];
root = cur , fa[cur] = 0 , fa[ch[root][1]] = root;
up( root );
}
}
int size() {
return s[root];
}
// 查第 k 小 , 必须保证合法
int kth( int x , int k ) {
if ( k == s[ch[x][0]] + 1 ) {
Splay( x , 0 );
return key[x];
}
else if ( k <= s[ch[x][0]] ) return kth( ch[x][0] , k );
else return kth( ch[x][1] , k - s[ch[x][0]] - 1 );
}
//找前驱
int pred( int t , int v ) {
if ( t == 0 ) return v;
else {
if ( v <= key[t] ) return pred( ch[t][0] , v );
else {
int ans = pred( ch[t][1] , v );
if ( ans == v ) {
ans = key[t];
Splay( t , 0 );
}
return ans;
}
}
}
/*int less( int t , int v ){
if( t == 0 ) return 0;
int rs = 0;
if( v <= key[t] ) rs = less( ch[t][0] , v );
else rs = s[ch[t][0]] + 1 + less( ch[t][1] , v );
if( Tl ){
Splay( t , 0 );
Tl = 0;
}
return rs;
}*/
//找后继
int succ( int t , int v ) {
if ( t == 0 ) return v;
else {
if ( v >= key[t] ) return succ( ch[t][1] , v );
else {
int ans = succ( ch[t][0] , v );
if ( ans == v ) {
ans = key[t];
Splay( t , 0 );
}
return ans;
}
}
}
void Preorder( int t ) {
if ( !t ) return;
Preorder( ch[t][0] );
printf("%d " , key[t] );
Preorder( ch[t][1] );
}
} splay;

DLX 覆盖问题

DLX{  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    const static int maxn=20010;  
    #define FF(i,A,s) for(int i = A[s];i != s;i = A[i])  
    int L[maxn],R[maxn],U[maxn],D[maxn];  
    int size,col[maxn],row[maxn],s[maxn],H[maxn];  
    bool vis[70];  
    int ans[maxn],cnt;  
    void init(int m){  
        for(int i=0;i<=m;i++){  
            L[i]=i-1;R[i]=i+1;U[i]=D[i]=i;s[i]=0;  
        }  
        memset(H,-1,sizeof(H));  
        L[0]=m;R[m]=0;size=m+1;  
    }  
    void link(int r,int c){  
         U[size]=c;D[size]=D[c];U[D[c]]=size;D[c]=size;  
         if(H[r]<0)H[r]=L[size]=R[size]=size;  
         else {  
             L[size]=H[r];R[size]=R[H[r]];  
             L[R[H[r]]]=size;R[H[r]]=size;  
         }  
         s[c]++;col[size]=c;row[size]=r;size++;  
     }  
    void del(int c){//精确覆盖  
        L[R[c]]=L[c];R[L[c]]=R[c];    
        FF(i,D,c)FF(j,R,i)U[D[j]]=U[j],D[U[j]]=D[j],--s[col[j]];    
    }    
    void add(int c){  //精确覆盖  
        R[L[c]]=L[R[c]]=c;    
        FF(i,U,c)FF(j,L,i)++s[col[U[D[j]]=D[U[j]]=j]];    
    }    
    bool dfs(int k){//精确覆盖  
        if(!R[0]){    
            cnt=k;return 1;    
        }    
        int c=R[0];FF(i,R,0)if(s[c]>s[i])c=i;    
        del(c);    
        FF(i,D,c){    
            FF(j,R,i)del(col[j]);    
            ans[k]=row[i];if(dfs(k+1))return true;    
            FF(j,L,i)add(col[j]);    
        }    
        add(c);    
        return 0;  
    }    
    void remove(int c){//重复覆盖  
        FF(i,D,c)L[R[i]]=L[i],R[L[i]]=R[i];  
    }  
     void resume(int c){//重复覆盖  
         FF(i,U,c)L[R[i]]=R[L[i]]=i;  
     }  
    int A(){//估价函数  
        int res=0;  
        memset(vis,0,sizeof(vis));  
        FF(i,R,0)if(!vis[i]){  
                res++;vis[i]=1;  
                FF(j,D,i)FF(k,R,j)vis[col[k]]=1;  
            }  
        return res;  
    }  
    void dfs(int now,int &ans){//重复覆盖  
        if(R[0]==0)ans=min(ans,now);  
        else if(now+A()<ans){  
            int temp=INF,c;  
            FF(i,R,0)if(temp>s[i])temp=s[i],c=i;  
            FF(i,D,c){  
                remove(i);FF(j,R,i)remove(j);  
                dfs(now+1,ans);  
                FF(j,L,i)resume(j);resume(i);  
            }  
        }  
    }  
}dlx;

主席树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//区间第k大
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#define w(i) T[(i)].w
#define ls(i) T[(i)].ls
#define rs(i) T[(i)].rs
using namespace std;
const int N = 100000 + 10;
struct node {
int ls, rs, w;
node() {ls = rs = w = 0;}
} T[N * 20];
int a[N], b[N], p[N], root[N], sz, n, m;
int cmp(int i, int j) {
return a[i] < a[j];
}
void ins(int &i, int l, int r, int x) {
T[++sz] = T[i]; i = sz;
w(i)++;
if (l == r) return;
int m = (l + r) >> 1;
if (x <= m) ins(ls(i), l, m, x);
else ins(rs(i), m + 1, r, x);
}
int query(int i, int j, int l, int r, int k) {
if (l == r) return l;
int t = w(ls(j)) - w(ls(i));
int m = (l + r) >> 1;
if (t >= k) return query(ls(i), ls(j), l, m, k);
else return query(rs(i), rs(j), m + 1, r, k - t);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]); p[i] = i;
}
sort(p + 1, p + 1 + n, cmp); //间接排序,p[i]表示第i小的值在a[]中的下标;
for (int i = 1; i <= n; i++) b[p[i]] = i; //离散化
sz = 0;
for (int i = 1; i <= n; i++) {
root[i] = root[i - 1];
ins(root[i], 1, n, b[i]);
}
for (int i = 0; i < m; i++) {
int x, y, k; scanf("%d%d%d", &x, &y, &k);
int t = query(root[x - 1], root[y], 1, n, k);
printf("%d\n", a[p[t]]);
}
return 0;
}
//[l, r]区间的数 里面找小于等于x的个数
#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <queue>
#include <cmath>
#include <vector>
using namespace std;
#define mnx 100050
#define ll long long
#define mod 1000000007
#define inf 223372036854775807
#define lson l, m, rt << 1
#define rson m+1, r, rt << 1 | 1
ll a[mnx], b[mnx];
int root[mnx], tot;
struct node {
int ls, rs, sum;
} p[mnx * 20];
int build( int l, int r ) {
int rt = tot++;
p[rt].sum = 0;
if ( l == r ) return rt;
int m = ( l + r ) >> 1;
p[rt].ls = build( l, m );
p[rt].rs = build( m + 1, r );
return rt;
}
int update( int x, int v, int i, int l, int r ) {
int rt = tot++;
p[rt] = p[i];
p[rt].sum += v;
if ( l == r ) return rt;
int m = ( l + r ) >> 1;
if ( x <= m ) p[rt].ls = update( x, v, p[i].ls, l, m );
else p[rt].rs = update( x, v, p[i].rs, m + 1, r );
return rt;
}
int query( int i, int j, int k, int l, int r ) {
if ( l == r ) return p[i].sum - p[j].sum;
int ret = 0, m = ( l + r ) >> 1;
if ( k > m ) {
ret += ( p[p[i].ls].sum - p[p[j].ls].sum );
ret += query( p[i].rs, p[j].rs, k, m + 1, r );
}
else ret += query( p[i].ls, p[j].ls, k, l, m );
return ret;
}
int main() {
int cas, cnt = 1;
scanf( "%d", &cas );
while ( cas-- ) {
tot = 0;
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%I64d", &a[i]);
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
int tmp = unique( b + 1, b + 1 + n ) - b - 1;
tmp++, b[tmp] = inf;
root[0] = build( 1, tmp );
for (int i = 1; i <= n; i++) {
int k = lower_bound( b + 1, b + 1 + tmp, a[i] ) - b;
root[i] = update( k, 1, root[i - 1], 1, tmp );
}
printf( "Case %d:\n", cnt++ );
while ( m-- ) {
int l, r; ll v;
scanf("%d %d %I64d", &l, &r, &v);
l++, r++;
int k = upper_bound( b + 1, b + 1 + tmp, v ) - b - 1;
int ans = 0;
if (k > 0) ans = query( root[r], root[l - 1], k, 1, tmp );
printf( "%d\n", ans );
}
}
}

LCA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* DFS+ST O( n*logn )
DFS处理:
T=<V,E>,其中V={A,B,C,D,E,F,G},E={AB,AC,BD,BE,EF,EG},且A为树根。
则图T的DFS结果为:A->B->D->B->E->F->E->G->E->B->A->C->A
ST处理d[]数组
dis为深度遍历计数
d[]为树遍历节点的深度
E[]为树遍历的节点的标号
R[i]表示E数组中第一个值为i的元素下标
f[i][j]为深度遍历[i,j]区间的最小深度的深度遍历编号
如果有n个节点,那么E[]和R[]下标范围为2*n-1
rmq下标范围为[1,n]
注意他们的最近公共祖先为他们本身的情况!!! */
struct Edge{
int u,v;
}e[N];
int first[N],next[N],f[N][20],d[N],E[N],R[N],vis[N];
int T,n,m,mt,dis,root;
void adde(int a,int b)
{
e[mt].u=a;e[mt].v=b;
next[mt]=first[a],first[a]=mt++;
}
int Minele(int i,int j) //比较距离来获取下标
{
return d[i]<d[j]?i:j;
}
void rmq_init(int n)
{
int i,j;
for(i=1;i<=n;i++)f[i][0]=i;
for(j=1;(1<<j)<=n;j++){
for(i=1;i+(1<<j)-1<=n;i++){
f[i][j]=Minele(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int rmq(int l,int r)
{
int k=0;
while((1<<(k+1))<=r-l+1)k++;
return Minele(f[l][k],f[r-(1<<k)+1][k]);
}
void dfs(int u,int deep)
{
d[dis]=deep;
E[dis]=u;
R[u]=dis++;
int i;
for(i=first[u];i!=-1;i=next[i]){
dfs(e[i].v,deep+1);
d[dis]=deep;
E[dis++]=u;
}
}
void LCA_Init()
{
dis=1; //初始化为1
dfs(root,0); //传递根节点和深度
rmq_init(2*n-1); //传递E[]和R[]的长度
}
int LCA(int a,int b) //返回a和b节点的最近祖先节点标号,注意他们的最近公共祖先为他们本身的情况
{
int left=R[a],right=R[b];
if(left>right)swap(left,right);
return E[rmq(left,right)];
}

划分树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* O(n*logn)
s[]是数列的前缀和
val[]是输入的数列,然后sort排序
num[u+1][]存的是u层划分后的数字 (类似快排Partation (d-1)次后的结果)
cnt[u][i]是统计在当前区间第i个数之前(包括第i个数)放到左区间的数的个数
sum[u][i]是求在当前区间第i个数之前(包括第i个数)放到左区间的数和
注意:每次询问前初始化 ksum=0 */
int val[N],num[20][N],cnt[20][N];
LL sum[20][N],s[N];
int knum;
LL ksum;
void build(int u,int l,int r)
{
if(l==r){
sum[u][l]=num[u][l];
return;
}
int i,mid,midnum,kl,kr,lsame;
LL s=0;
mid=(l+r)>>1;
kl=l;kr=mid+1;lsame=mid-l+1;
midnum=val[mid];
for(i=l;i<=mid;i++) //注意这里需要统计等于中位数放在左儿子区间的个数
if(val[i]<midnum)lsame--;
for(i=l;i<=r;i++){
if(num[u][i]<midnum || (num[u][i]==midnum && lsame)){ //注意等于中位数情况
if(num[u][i]==midnum)lsame--;
num[u+1][kl++]=num[u][i];
sum[u][i]=s+(LL)num[u][i];
s=sum[u][i];
}
else {
num[u+1][kr++]=num[u][i];
sum[u][i]=s;
}
cnt[u][i]=kl-l;
}
build(u+1,l,mid);
build(u+1,mid+1,r);
}
void query(int u,int l,int r,int a,int b,int k)
{
if(a==b){ //注意这里可能l<r啦
knum=num[u][a];
ksum+=num[u][a];
return;
}
int i,t,mid,cnta;
mid=(l+r)>>1;
cnta=(a>l?cnt[u][a-1]:0); //注意l==a
t=cnt[u][b]-cnta;
if(k<=t){
query(u+1,l,mid,l+cnta,l+cnt[u][b]-1,k);
}
else{
ksum+=sum[u][b]-(a>l?sum[u][a-1]:0);
query(u+1,mid+1,r,mid+a-l-cnta+1,mid+b-l-cnt[u][b]+1,k-t);
}
}

kd tree insert版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <cstdio>
#include <cstring>
#include <climits>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 500010
#define M 500010
#define INF 0x3f3f3f3f
int n, m, sign, res;
struct Point {
int x, y;
Point(int _x = 0, int _y = 0): x(_x), y(_y) {}
void set(int _, int __) {
x = _, y = __;
}
} P[N + M];
int Dis(const Point &A, const Point &B) {
return abs(A.x - B.x) + abs(A.y - B.y);
}
inline bool cmp(const Point &A, const Point &B) {
if (sign) return A.x < B.x || (A.x == B.x && A.y < B.y);
else return A.y < B.y || (A.y == B.y && A.x < B.x);
}
struct Node {
Node *l, *r;
int x[2], y[2];
Point p;
void SetP(const Point &P) {
p = P;
x[0] = x[1] = P.x;
y[0] = y[1] = P.y;
}
int Dis(const Point &p) const {
int res = 0;
if (p.x < x[0] || p.x > x[1]) res += (p.x < x[0]) ? x[0] - p.x : p.x - x[1];
if (p.y < y[0] || p.y > y[1]) res += (p.y < y[0]) ? y[0] - p.y : p.y - y[1];
return res;
}
void up(Node *B) {
x[0] = min(x[0], B->x[0]);
x[1] = mac(x[1], B->x[1]);
y[0] = min(y[0], B->y[0]);
y[1] = mac(y[1], B->y[1]);
}
} mem[N + M], *C = mem, Tnull, *null = &Tnull;
Node *Build(int tl, int tr, bool d) {
if (tl > tr) return null;
int mid = (tl + tr) >> 1;
sign = d;
std::nth_element(P + tl + 1, P + mid + 1, P + tr + 1, cmp);
Node *q = C++;
q->SetP(P[mid]);
q->l = Build(tl, mid - 1, d ^ 1);
q->r = Build(mid + 1, tr, d ^ 1);
if (q->l != null) q->up(q->l);
if (q->r != null) q->up(q->r);
return q;
}
void Ask(Node *q, const Point &p) {
res = min(res, Dis(q->p, p));
int DisL = q->l != null ? q->l->Dis(p) : INF;
int DisR = q->r != null ? q->r->Dis(p) : INF;
if (DisL < DisR) {
if (q->l != null) Ask(q->l, p);
if (DisR < res && q->r != null) Ask(q->r, p);
}
else {
if (q->r != null) Ask(q->r, p);
if (DisL < res && q->l != null) Ask(q->l, p);
}
}
void Insert(Node *root, const Point &p) {
Node *q = C++;
q->l = q->r = null;
q->SetP(p);
sign = 0;
while (1) {
root->up(q);
if (cmp(q->p, root->p)) {
if (root->l == null) {
root->l = q;
break;
}
else root = root->l;
}
else {
if (root->r == null) {
root->r = q;
break;
}
else root = root->r;
}
sign ^= 1;
}
}
int main(int argc, char const *argv[])
{
scanf("%d%d", &n, &m);
int ope, x, y;
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &x, &y);
P[i] = Point(x, y);
}
Node* root = Build(1, n, 0);
while (m--) {
scanf("%d%d%d", &ope, &x, &y);
if (ope == 1) Insert(root, Point(x, y));
else {
res = INF;
Ask(root, Point(x, y));
printf("%d\n", res);
}
}
}

kd tree no insert版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <iostream>
#define MAX 100005
#define INF 1e18
using namespace std;
struct Point {
int x, y;
} p[MAX], p2[MAX];
bool dv[MAX];
bool cmpx (const Point& p1, const Point& p2) {
return p1.x < p2.x;
}
bool cmpy (const Point& p1, const Point& p2) {
return p1.y < p2.y;
}
long long Dis(Point p1, Point p2) {
return (long long)(p1.x - p2.x) * (p1.x - p2.x) + (long long)(p1.y - p2.y) * (p1.y - p2.y);
}
void build (int l, int r)
{
if (l == r) return;
int mid = l + r >> 1;
int minx = min_element ( p + l, p + r, cmpx )->x, miny = min_element ( p + l, p + r, cmpy )->y;
int maxx = max_element ( p + l, p + r, cmpx )->x, maxy = max_element ( p + l, p + r, cmpy )->y;
dv[mid] = maxx - minx >= maxy - miny;
nth_element (p + l, p + mid, p + r, dv[mid] ? cmpx : cmpy);
build (l, mid);
build (mid + 1, r);
}
long long res, t, n;
void find (int l, int r, Point a) {
if (l == r) return;
int mid = l + r >> 1;
long long dis = Dis(a, p[mid]);
if (dis > 0 ) res = min (res , dis);
long long d = dv[mid] ? (a.x - p[mid].x) : (a.y - p[mid].y);
int l1 = l, l2 = mid + 1, r1 = mid, r2 = r;
if (d > 0) swap(l1, l2), swap(r1, r2);
find(l1, r1, a);
if (d * d < res) find(l2, r2, a);
}
int main(int argc, char const *argv[])
{
while (cin >> n >> m) {
for (int i = 0; i < n; i++) {
scanf ("%d%d", &p[i].x, &p[i].y);
}
build (0, n);
for (int i = 0; i < m; i++) {
if ()
res = INF;
find (0, n, p2[i]);
printf ("%lld\n", res);
}
}
}

LCT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200010;
struct Node {
Node *ch[2], *p; int size, value;
bool rev;
Node(int t = 0);
inline bool dir(void) {return p->ch[1] == this;}
inline void SetC(Node *x, bool d) {
ch[d] = x; x->p = this;
}
inline void Rev(void) {
swap(ch[0], ch[1]); rev ^= 1;
}
inline void Push(void) {
if (rev) {
ch[0]->Rev();
ch[1]->Rev();
rev = 0;
}
}
inline void Update(void) {
size = ch[0]->size + ch[1]->size + 1;
}
} Tnull, *null = &Tnull, *fim[MAXN];
// 要记得额外更新null的信息
Node::Node(int _value) {ch[0] = ch[1] = p = null; rev = 0;}
inline bool isRoot(Node *x) {return x->p == null || (x != x->p->ch[0] && x != x->p->ch[1]);}
inline void rotate(Node *x) {
Node *p = x->p; bool d = x->dir();
p->Push(); x->Push();
if (!isRoot(p)) p->p->SetC(x, p->dir()); else x->p = p->p;
p->SetC(x->ch[!d], d);
x->SetC(p, !d);
p->Update();
}
inline void splay(Node *x) {
x->Push();
while (!isRoot(x)) {
if (isRoot(x->p)) rotate(x);
else {
if (x->dir() == x->p->dir()) {rotate(x->p); rotate(x);}
else {rotate(x); rotate(x);}
}
}
x->Update();
}
inline Node* Access(Node *x) {
Node *t = x, *q = null;
for (; x != null; x = x->p) {
splay(x); x->ch[1] = q; q = x;
}
splay(t); //info will be updated in the splay;
return q;
}
inline void Evert(Node *x) {
Access(x); x->Rev();
}
inline void link(Node *x, Node *y) {
Evert(x); x->p = y;
}
inline Node* getRoot(Node *x) {
Node *tmp = x;
Access(x);
while (tmp->Push(), tmp->ch[0] != null) tmp = tmp->ch[0];
splay(tmp);
return tmp;
}
// 一定要确定x和y之间有边
inline void cut(Node *x, Node *y) {
Access(x); splay(y);
if (y->p != x) swap(x, y);
Access(x); splay(y);
y->p = null;
}
inline Node* getPath(Node *x, Node *y) {
Evert(x); Access(y);
return y;
}
inline void clear(void) {
null->rev = 0; null->size = 0; null->value = 0;
}
int judge(Node *x, Node *y) {
while (x->p != null)x = x->p;
while (y->p != null)y = y->p;
if (x != y)return 0;
return 1;
}
int main() {
clear();
int n, u, v; string c;
cin >> n;
for (int i = 1; i <= n; i++) fim[i] = new Node();
while (1) {
cin >> c;
if (c[0] == 'E')break;
cin >> u >> v;
if (c[0] == 'T') {
if (judge(fim[u], fim[v])) cout << "YES" << endl;
else cout << "NO" << endl;
}
else if (c[0] == 'C') link(fim[u], fim[v]);
else cut(fim[u], fim[v]);
}
}

树状数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Bit
{
vector<int> a;
int sz;
void init(int n)
{
sz = n;
for (int i = 1; i <= n + 5; i++)
a.push_back(0);
}
int lowbit(int x)
{
return x & (-x);
}
int query(int x)
{
int ans = 0;
for (; x; x -= lowbit(x))ans += a[x];
return ans;
}
void update(int x, int v)
{
for (; x < sz; x += lowbit(x))
a[x] += v;
}
} bit;

线段树完全版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
hdu1166 敌兵布阵
题意:O(-1)
思路:O(-1)
线段树功能:update:单点增减 query:区间求和
#include <cstdio>
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 55555;
int sum[maxn<<2];
void PushUP(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l,int r,int rt) {
if (l == r) {
scanf("%d",&sum[rt]);
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUP(rt);
}
void update(int p,int add,int l,int r,int rt) {
if (l == r) {
sum[rt] += add;
return ;
}
int m = (l + r) >> 1;
if (p <= m) update(p , add , lson);
else update(p , add , rson);
PushUP(rt);
}
int query(int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret += query(L , R , lson);
if (R > m) ret += query(L , R , rson);
return ret;
}
int main() {
int T , n;
scanf("%d",&T);
for (int cas = 1 ; cas <= T ; cas ++) {
printf("Case %d:\n",cas);
scanf("%d",&n);
build(1 , n , 1);
char op[10];
while (scanf("%s",op)) {
if (op[0] == 'E') break;
int a , b;
scanf("%d%d",&a,&b);
if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1));
else if (op[0] == 'S') update(a , -b , 1 , n , 1);
else update(a , b , 1 , n , 1);
}
}
return 0;
}
hdu1754 I Hate It
题意:O(-1)
思路:O(-1)
线段树功能:update:单点替换 query:区间最值
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 222222;
int MAX[maxn<<2];
void PushUP(int rt) {
MAX[rt] = max(MAX[rt<<1] , MAX[rt<<1|1]);
}
void build(int l,int r,int rt) {
if (l == r) {
scanf("%d",&MAX[rt]);
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUP(rt);
}
void update(int p,int sc,int l,int r,int rt) {
if (l == r) {
MAX[rt] = sc;
return ;
}
int m = (l + r) >> 1;
if (p <= m) update(p , sc , lson);
else update(p , sc , rson);
PushUP(rt);
}
int query(int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
return MAX[rt];
}
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret = max(ret , query(L , R , lson));
if (R > m) ret = max(ret , query(L , R , rson));
return ret;
}
int main() {
int n , m;
while (~scanf("%d%d",&n,&m)) {
build(1 , n , 1);
while (m --) {
char op[2];
int a , b;
scanf("%s%d%d",op,&a,&b);
if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1));
else update(a , b , 1 , n , 1);
}
}
return 0;
}
hdu1394 Minimum Inversion Number
题意:求Inversion后的最小逆序数
思路:用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解
线段树功能:update:单点增减 query:区间求和
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 5555;
int sum[maxn<<2];
void PushUP(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l,int r,int rt) {
sum[rt] = 0;
if (l == r) return ;
int m = (l + r) >> 1;
build(lson);
build(rson);
}
void update(int p,int l,int r,int rt) {
if (l == r) {
sum[rt] ++;
return ;
}
int m = (l + r) >> 1;
if (p <= m) update(p , lson);
else update(p , rson);
PushUP(rt);
}
int query(int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret += query(L , R , lson);
if (R > m) ret += query(L , R , rson);
return ret;
}
int x[maxn];
int main() {
int n;
while (~scanf("%d",&n)) {
build(0 , n - 1 , 1);
int sum = 0;
for (int i = 0 ; i < n ; i ++) {
scanf("%d",&x[i]);
sum += query(x[i] , n - 1 , 0 , n - 1 , 1);
update(x[i] , 0 , n - 1 , 1);
}
int ret = sum;
for (int i = 0 ; i < n ; i ++) {
sum += n - x[i] - x[i] - 1;
ret = min(ret , sum);
}
printf("%d\n",ret);
}
return 0;
}
hdu2795 Billboard
题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子
思路:每次找到最大值的位子,然后减去L
线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 222222;
int h , w , n;
int MAX[maxn<<2];
void PushUP(int rt) {
MAX[rt] = max(MAX[rt<<1] , MAX[rt<<1|1]);
}
void build(int l,int r,int rt) {
MAX[rt] = w;
if (l == r) return ;
int m = (l + r) >> 1;
build(lson);
build(rson);
}
int query(int x,int l,int r,int rt) {
if (l == r) {
MAX[rt] -= x;
return l;
}
int m = (l + r) >> 1;
int ret = (MAX[rt<<1] >= x) ? query(x , lson) : query(x , rson);
PushUP(rt);
return ret;
}
int main() {
while (~scanf("%d%d%d",&h,&w,&n)) {
if (h > n) h = n;
build(1 , h , 1);
while (n --) {
int x;
scanf("%d",&x);
if (MAX[1] < x) puts("-1");
else printf("%d\n",query(x , 1 , h , 1));
}
}
return 0;
}
练习:
poj2828 Buy Tickets
poj2886 Who Gets the Most Candies?
成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候
hdu1698 Just a Hook
题意:O(-1)
思路:O(-1)
线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 111111;
int h , w , n;
int col[maxn<<2];
int sum[maxn<<2];
void PushUp(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void PushDown(int rt,int m) {
if (col[rt]) {
col[rt<<1] = col[rt<<1|1] = col[rt];
sum[rt<<1] = (m - (m >> 1)) * col[rt];
sum[rt<<1|1] = (m >> 1) * col[rt];
col[rt] = 0;
}
}
void build(int l,int r,int rt) {
col[rt] = 0;
sum[rt] = 1;
if (l == r) return ;
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
col[rt] = c;
sum[rt] = c * (r - l + 1);
return ;
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (R > m) update(L , R , c , rson);
PushUp(rt);
}
int main() {
int T , n , m;
scanf("%d",&T);
for (int cas = 1 ; cas <= T ; cas ++) {
scanf("%d%d",&n,&m);
build(1 , n , 1);
while (m --) {
int a , b , c;
scanf("%d%d%d",&a,&b,&c);
update(a , b , c , 1 , n , 1);
}
printf("Case %d: The total value of the hook is %d.\n",cas , sum[1]);
}
return 0;
}
poj3468 A Simple Problem with Integers
题意:O(-1)
思路:O(-1)
线段树功能:update:成段增减 query:区间求和
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define LL long long
const int maxn = 111111;
LL add[maxn<<2];
LL sum[maxn<<2];
void PushUp(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void PushDown(int rt,int m) {
if (add[rt]) {
add[rt<<1] += add[rt];
add[rt<<1|1] += add[rt];
sum[rt<<1] += add[rt] * (m - (m >> 1));
sum[rt<<1|1] += add[rt] * (m >> 1);
add[rt] = 0;
}
}
void build(int l,int r,int rt) {
add[rt] = 0;
if (l == r) {
scanf("%lld",&sum[rt]);
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
add[rt] += c;
sum[rt] += (LL)c * (r - l + 1);
return ;
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
PushUp(rt);
}
LL query(int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
LL ret = 0;
if (L <= m) ret += query(L , R , lson);
if (m < R) ret += query(L , R , rson);
return ret;
}
int main() {
int N , Q;
scanf("%d%d",&N,&Q);
build(1 , N , 1);
while (Q --) {
char op[2];
int a , b , c;
scanf("%s",op);
if (op[0] == 'Q') {
scanf("%d%d",&a,&b);
printf("%lld\n",query(a , b , 1 , N , 1));
} else {
scanf("%d%d%d",&a,&b,&c);
update(a , b , c , 1 , N , 1);
}
}
return 0;
}
poj2528 Mayor’s posters
题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报
思路:这题数据范围很大,直接搞超时+超内存,需要离散化:
离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了
所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多
而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)
给出下面两个简单的例子应该能体现普通离散化的缺陷:
1-10 1-4 5-10
1-10 1-4 6-10
为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.
线段树功能:update:成段替换 query:简单hash
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 11111;
bool hash[maxn];
int li[maxn] , ri[maxn];
int X[maxn*3];
int col[maxn<<4];
int cnt;
void PushDown(int rt) {
if (col[rt] != -1) {
col[rt<<1] = col[rt<<1|1] = col[rt];
col[rt] = -1;
}
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
col[rt] = c;
return ;
}
PushDown(rt);
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
}
void query(int l,int r,int rt) {
if (col[rt] != -1) {
if (!hash[col[rt]]) cnt ++;
hash[ col[rt] ] = true;
return ;
}
if (l == r) return ;
int m = (l + r) >> 1;
query(lson);
query(rson);
}
int Bin(int key,int n,int X[]) {
int l = 0 , r = n - 1;
while (l <= r) {
int m = (l + r) >> 1;
if (X[m] == key) return m;
if (X[m] < key) l = m + 1;
else r = m - 1;
}
return -1;
}
int main() {
int T , n;
scanf("%d",&T);
while (T --) {
scanf("%d",&n);
int nn = 0;
for (int i = 0 ; i < n ; i ++) {
scanf("%d%d",&li[i] , &ri[i]);
X[nn++] = li[i];
X[nn++] = ri[i];
}
sort(X , X + nn);
int m = 1;
for (int i = 1 ; i < nn; i ++) {
if (X[i] != X[i-1]) X[m ++] = X[i];
}
for (int i = m - 1 ; i > 0 ; i --) {
if (X[i] != X[i-1] + 1) X[m ++] = X[i] + 1;
}
sort(X , X + m);
memset(col , -1 , sizeof(col));
for (int i = 0 ; i < n ; i ++) {
int l = Bin(li[i] , m , X);
int r = Bin(ri[i] , m , X);
update(l , r , i , 0 , m , 1);
}
cnt = 0;
memset(hash , false , sizeof(hash));
query(0 , m , 1);
printf("%d\n",cnt);
}
return 0;
}
poj3225 Help with Intervals
题意:区间操作,交,并,补等
思路:
我们一个一个操作来分析:(用01表示是否包含区间,-1表示该区间内既有包含又有不包含)
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换
成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作
很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了
所以当一个节点得到覆盖标记时把异或标记清空
而当一个节点得到异或标记的时候,先判断覆盖标记,如果是01,直接改变一下覆盖标记,不然的话改变异或标记
开区间闭区间只要数字乘以2就可以处理(偶数表示端点,奇数表示两端点间的区间)
线段树功能:update:成段替换,区间异或 query:简单hash
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 131072;
bool hash[maxn];
int cover[maxn<<2];
int XOR[maxn<<2];
void FXOR(int rt) {
if (cover[rt] != -1) cover[rt] ^= 1;
else XOR[rt] ^= 1;
}
void PushDown(int rt) {
if (cover[rt] != -1) {
cover[rt<<1] = cover[rt<<1|1] = cover[rt];
XOR[rt<<1] = XOR[rt<<1|1] = 0;
cover[rt] = -1;
}
if (XOR[rt]) {
FXOR(rt<<1);
FXOR(rt<<1|1);
XOR[rt] = 0;
}
}
void update(char op,int L,int R,int l,int r,int rt) {
if (L <= l && r <= R) {
if (op == 'U') {
cover[rt] = 1;
XOR[rt] = 0;
} else if (op == 'D') {
cover[rt] = 0;
XOR[rt] = 0;
} else if (op == 'C' || op == 'S') {
FXOR(rt);
}
return ;
}
PushDown(rt);
int m = (l + r) >> 1;
if (L <= m) update(op , L , R , lson);
else if (op == 'I' || op == 'C') {
XOR[rt<<1] = cover[rt<<1] = 0;
}
if (m < R) update(op , L , R , rson);
else if (op == 'I' || op == 'C') {
XOR[rt<<1|1] = cover[rt<<1|1] = 0;
}
}
void query(int l,int r,int rt) {
if (cover[rt] == 1) {
for (int it = l ; it <= r ; it ++) {
hash[it] = true;
}
return ;
} else if (cover[rt] == 0) return ;
if (l == r) return ;
PushDown(rt);
int m = (l + r) >> 1;
query(lson);
query(rson);
}
int main() {
cover[1] = XOR[1] = 0;
char op , l , r;
int a , b;
while ( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r) ) {
a <<= 1 , b <<= 1;
if (l == '(') a ++;
if (r == ')') b --;
if (a > b) {
if (op == 'C' || op == 'I') {
cover[1] = XOR[1] = 0;
}
} else update(op , a , b , 0 , maxn , 1);
}
query(0 , maxn , 1);
bool flag = false;
int s = -1 , e;
for (int i = 0 ; i <= maxn ; i ++) {
if (hash[i]) {
if (s == -1) s = i;
e = i;
} else {
if (s != -1) {
if (flag) printf(" ");
flag = true;
printf("%c%d,%d%c",s&1?'(':'[' , s>>1 , (e+1)>>1 , e&1?')':']');
s = -1;
}
}
}
if (!flag) printf("empty set");
puts("");
return 0;
}
练习:
poj1436 Horizontally Visible Segments
poj2991 Crane
Another LCIS
Bracket Sequence
区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并
poj3667 Hotel
题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边
2 a b:将[a,a+b-1]的房间清空
思路:记录区间中最长的空房间
线段树操作:update:区间替换 query:询问满足条件的最左断点
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 55555;
int lsum[maxn<<2] , rsum[maxn<<2] , msum[maxn<<2];
int cover[maxn<<2];
void PushDown(int rt,int m) {
if (cover[rt] != -1) {
cover[rt<<1] = cover[rt<<1|1] = cover[rt];
msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = cover[rt] ? 0 : m - (m >> 1);
msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = cover[rt] ? 0 : (m >> 1);
cover[rt] = -1;
}
}
void PushUp(int rt,int m) {
lsum[rt] = lsum[rt<<1];
rsum[rt] = rsum[rt<<1|1];
if (lsum[rt] == m - (m >> 1)) lsum[rt] += lsum[rt<<1|1];
if (rsum[rt] == (m >> 1)) rsum[rt] += rsum[rt<<1];
msum[rt] = max(lsum[rt<<1|1] + rsum[rt<<1] , max(msum[rt<<1] , msum[rt<<1|1]));
}
void build(int l,int r,int rt) {
msum[rt] = lsum[rt] = rsum[rt] = r - l + 1;
cover[rt] = -1;
if (l == r) return ;
int m = (l + r) >> 1;
build(lson);
build(rson);
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1;
cover[rt] = c;
return ;
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
PushUp(rt , r - l + 1);
}
int query(int w,int l,int r,int rt) {
if (l == r) return l;
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (msum[rt<<1] >= w) return query(w , lson);
else if (rsum[rt<<1] + lsum[rt<<1|1] >= w) return m - rsum[rt<<1] + 1;
return query(w , rson);
}
int main() {
int n , m;
scanf("%d%d",&n,&m);
build(1 , n , 1);
while (m --) {
int op , a , b;
scanf("%d",&op);
if (op == 1) {
scanf("%d",&a);
if (msum[1] < a) puts("0");
else {
int p = query(a , 1 , n , 1);
printf("%d\n",p);
update(p , p + a - 1 , 1 , 1 , n , 1);
}
} else {
scanf("%d%d",&a,&b);
update(a , a + b - 1 , 0 , 1 , n , 1);
}
}
return 0;
}
练习:
hdu3308 LCIS
hdu3397 Sequence operation
hdu2871 Memory Control
hdu1540 Tunnel Warfare
CF46-D Parking Lot
扫描线
这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
最典型的就是矩形面积并,周长并等题
hdu1542 Atlantis
题意:矩形面积并
思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个
线段树操作:update:区间增减 query:直接取根节点的值
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 2222;
int cnt[maxn << 2];
double sum[maxn << 2];
double X[maxn];
struct Seg {
double h , l , r;
int s;
Seg(){}
Seg(double a,double b,double c,int d) : l(a) , r(b) , h(c) , s(d) {}
bool operator < (const Seg &cmp) const {
return h < cmp.h;
}
}ss[maxn];
void PushUp(int rt,int l,int r) {
if (cnt[rt]) sum[rt] = X[r+1] - X[l];
else if (l == r) sum[rt] = 0;
else sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
cnt[rt] += c;
PushUp(rt , l , r);
return ;
}
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
PushUp(rt , l , r);
}
int Bin(double key,int n,double X[]) {
int l = 0 , r = n - 1;
while (l <= r) {
int m = (l + r) >> 1;
if (X[m] == key) return m;
if (X[m] < key) l = m + 1;
else r = m - 1;
}
return -1;
}
int main() {
int n , cas = 1;
while (~scanf("%d",&n) && n) {
int m = 0;
while (n --) {
double a , b , c , d;
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
X[m] = a;
ss[m++] = Seg(a , c , b , 1);
X[m] = c;
ss[m++] = Seg(a , c , d , -1);
}
sort(X , X + m);
sort(ss , ss + m);
int k = 1;
for (int i = 1 ; i < m ; i ++) {
if (X[i] != X[i-1]) X[k++] = X[i];
}
memset(cnt , 0 , sizeof(cnt));
memset(sum , 0 , sizeof(sum));
double ret = 0;
for (int i = 0 ; i < m - 1 ; i ++) {
int l = Bin(ss[i].l , k , X);
int r = Bin(ss[i].r , k , X) - 1;
if (l <= r) update(l , r , ss[i].s , 0 , k - 1, 1);
ret += sum[1] * (ss[i+1].h - ss[i].h);
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ret);
}
return 0;
}
hdu1828 Picture
题意:矩形周长并
思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)
线段树操作:update:区间增减 query:直接取根节点的值
?[Copy to clipboard]View Code CPP1
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 22222;
struct Seg{
int l , r , h , s;
Seg() {}
Seg(int a,int b,int c,int d):l(a) , r(b) , h(c) , s(d) {}
bool operator < (const Seg &cmp) const {
return h < cmp.h;
}
}ss[maxn];
bool lbd[maxn<<2] , rbd[maxn<<2];
int numseg[maxn<<2];
int cnt[maxn<<2];
int len[maxn<<2];
void PushUP(int rt,int l,int r) {
if (cnt[rt]) {
lbd[rt] = rbd[rt] = 1;
len[rt] = r - l + 1;
numseg[rt] = 2;
} else if (l == r) {
len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0;
} else {
lbd[rt] = lbd[rt<<1];
rbd[rt] = rbd[rt<<1|1];
len[rt] = len[rt<<1] + len[rt<<1|1];
numseg[rt] = numseg[rt<<1] + numseg[rt<<1|1];
if (lbd[rt<<1|1] && rbd[rt<<1]) numseg[rt] -= 2;//两条线重合
}
}
void update(int L,int R,int c,int l,int r,int rt) {
if (L <= l && r <= R) {
cnt[rt] += c;
PushUP(rt , l , r);
return ;
}
int m = (l + r) >> 1;
if (L <= m) update(L , R , c , lson);
if (m < R) update(L , R , c , rson);
PushUP(rt , l , r);
}
int main() {
int n;
while (~scanf("%d",&n)) {
int m = 0;
int lbd = 10000, rbd = -10000;
for (int i = 0 ; i < n ; i ++) {
int a , b , c , d;
scanf("%d%d%d%d",&a,&b,&c,&d);
lbd = min(lbd , a);
rbd = max(rbd , c);
ss[m++] = Seg(a , c , b , 1);
ss[m++] = Seg(a , c , d , -1);
}
sort(ss , ss + m);
int ret = 0 , last = 0;
for (int i = 0 ; i < m ; i ++) {
if (ss[i].l < ss[i].r) update(ss[i].l , ss[i].r - 1 , ss[i].s , lbd , rbd - 1 , 1);
ret += numseg[1] * (ss[i+1].h - ss[i].h);
ret += abs(len[1] - last);
last = len[1];
}
printf("%d\n",ret);
}
return 0;
}

莫队

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int a[maxn],pos[maxn],c[maxn],Ans[maxn];
int ans,n,m;
struct query
{
int l,r,id;
}Q[maxn];
bool cmp(query a,query b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
void Update(int x)
{
c[x]++;
if(c[x]==2)ans++;
}
void Delete(int x)
{
c[x]--;
if(c[x]==1)ans--;
}
int main()
{
n=read(),m=read(),m=read();
int sz =ceil(sqrt(1.0*n));
for(int i=1;i<=n;i++)
{
a[i]=read();
pos[i]=(i-1)/sz;
}
for(int i=1;i<=m;i++)
{
Q[i].l=read();
Q[i].r=read();
Q[i].id = i;
}
sort(Q+1,Q+1+m,cmp);
int L=1,R=0;ans=0;
for(int i=1;i<=m;i++)
{
int id = Q[i].id;
while(R<Q[i].r)R++,Update(a[R]);
while(L>Q[i].l)L--,Update(a[L]);
while(R>Q[i].r)Delete(a[R]),R--;
while(L<Q[i].l)Delete(a[L]),L++;
Ans[id]=ans;
}
for(int i=1;i<=m;i++)
printf("%d\n",Ans[i]);
}

树链剖分

树链剖分解决在树上进行任意两点间的一类路径更新和路径查询的问题。大体分为点权型和边权型两种。分清楚边权型题目和点权型题目的建图区别,线段树方面要注意是使用RMQ/区间更新/单点更新的板子。当边权型题目最后是找son[u]v

使用方法

  1. work(n)初始化,注意输入点权和输入树的先后关系,默认点下标从1开始,树根为1
  2. find(x,y) 求x,y路径上的点权和(或最值,可通过修改线段树得到)
  3. update(w[x],y,1,nodenum) 将x节点的权值更新为y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// 点权型 单点更新 区间查询
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int Vmax = 5*1e4 + 5;//点的数量
const int Emax =2*1e5+5;//边的数量 小于Vmax的两倍
namespace segment_tree{
int sum[Vmax<<2],add[Vmax<<2];
inline void pushup(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int L,int c,int l,int r,int rt=1){
if (L == l && l == r)
{
sum[rt] = c;
return ;
}
int m = (l + r) >> 1;
if (L <= m) update(L , c , lson);
else update(L , c , rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt=1){
if (L <= l && r <= R)
return sum[rt];
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret+=query(L , R , lson);
if (m < R) ret+=query(L , R , rson);
return ret;
}
}
namespace poufen{
using namespace segment_tree;
int siz[Vmax],son[Vmax],fa[Vmax],dep[Vmax],top[Vmax],w[Vmax];
int nodenum;
struct edge{
int v,next;
}e[Emax];
int pre[Vmax],ecnt;
inline void init(){
memset(pre, -1, sizeof(pre));
ecnt=0;
}
inline void add_(int u,int v){
e[ecnt].v=v;
e[ecnt].next=pre[u];
pre[u]=ecnt++;
}
void dfs(int u){
siz[u]=1;son[u]=0;//下标从1开始,son[0]初始为0
for(int i=pre[u];~i;i=e[i].next)
{
int v=e[i].v;
if(fa[u]!=v)
{
fa[v]=u;
dep[v]=dep[u]+1;//初始根节点dep!!
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
}
void build_tree(int u,int tp){
top[u]=tp,w[u]=++nodenum;
if(son[u])build_tree(son[u],tp);
for(int i=pre[u];~i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=son[u])
build_tree(e[i].v,e[i].v);
}
inline int find1(int u,int v){
int ret=0;
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
swap(f1,f2),swap(u,v);
ret+=query(w[f1],w[u],1,nodenum);
u=fa[f1];
f1=top[u];
}
if(dep[u]>dep[v])swap(u,v);
ret+=query(w[u],w[v],1,nodenum);
return ret;
}
int a[Vmax],b[Vmax];
int val[Vmax];//
void work1(int n)
{
memset(siz, 0, sizeof(siz));
memset(sum, 0, sizeof(sum));
init();
int root=1;
fa[root]=nodenum=dep[root]=0;
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a[i],&b[i]);
add_(a[i],b[i]);
add_(b[i],a[i]);
}
dfs(root);
build_tree(root,root);
for(int i=1;i<=n;i++)
update(w[i],val[i],1,nodenum);
}
}
using namespace poufen;

如果是更新边,find函数替换为以下函数。

边权型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline int find2(int u,int v){
int f1=top[u],f2=top[v],ret=0;
while(f1!=f2)
{
if(dep[f1]<dep[f2])
swap(f1,f2),swap(u,v);
ret+=query(w[f1],w[u],1,nodenum);
u=fa[f1];
f1=top[u];
}
if(u==v)return ret;
if(dep[u]>dep[v])swap(u,v);
return ret+=query(w[son[u]],w[v],1,nodenum);
}

如果是边权型,work函数替换为以下函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void work2(int n){
memset(siz, 0, sizeof(siz));
memset(sum, 0, sizeof(sum));
init();
int root=1;
fa[root]=nodenum=dep[root]=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
add_(a[i],b[i]);
add_(b[i],a[i]);
}
dfs(root);
build_tree(root,root);
for(int i=1;i<n;i++){
int u = a[i];
int v = b[i];
if (dep[u] > dep[v]) { //保证v是被指向的点,也就是深度较大的点
swap(u, v);
swap(a[i], b[i]);
}
update(w[v],c[i],1,nodenum);
}
}

区间更新

点更新

如果是区间更新,update为upd(u,v,c)意为将u,v路径上的点权都+=c,注意将线段树模板修改成区间更新模板,直接改为c的操作可以修改线段树模板

1
2
3
4
5
6
7
8
9
10
11
12
13
inline void upd1(int u,int v,int c){
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
swap(f1,f2),swap(u,v);
update(w[f1],w[u],c,1,nodenum);
u=fa[f1];
f1=top[u];
}
if(dep[u]>dep[v])swap(u,v);
update(w[u],w[v],c,1,nodenum);
}
边更新

区间更新:upd2(u,v,c)意为将u,v路径上的边权+=c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline void upd(int u,int v,int c){
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
swap(f1,f2),swap(u,v);
update(w[f1],w[u],c,1,nodenum);
u=fa[f1];
f1=top[u];
}
if(u==v)return;
if(dep[u]>dep[v])swap(u,v);
update(w[son[u]],w[v],c,1,nodenum);
}

Treap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
struct Treap
{
int size;
int key,fix;
Treap *ch[2];
Treap(int key)
{
size=1;
fix=rand();
this->key=key;
ch[0]=ch[1]=NULL;
}
int compare(int x) const
{
if(x==key) return -1;
return x<key? 0:1;
}
void Maintain()
{
size=1;
if(ch[0]!=NULL) size+=ch[0]->size;
if(ch[1]!=NULL) size+=ch[1]->size;
}
};
void Rotate(Treap* &t,int d)
{
Treap *k=t->ch[d^1];
t->ch[d^1]=k->ch[d];
k->ch[d]=t;
t->Maintain(); //必须先维护t,再维护k,因为此时t是k的子节点
k->Maintain();
t=k;
}
void Insert(Treap* &t,int x)
{
if(t==NULL) t=new Treap(x);
else
{
//int d=t->compare(x); //如果值相等的元素只插入一个
int d=x < t->key ? 0:1; //如果值相等的元素都插入
Insert(t->ch[d],x);
if(t->ch[d]->fix > t->fix)
Rotate(t,d^1);
}
t->Maintain();
}
//一般来说,在调用删除函数之前要先用Find()函数判断该元素是否存在
void Delete(Treap* &t,int x)
{
int d=t->compare(x);
if(d==-1)
{
Treap *tmp=t;
if(t->ch[0]==NULL)
{
t=t->ch[1];
delete tmp;
tmp=NULL;
}
else if(t->ch[1]==NULL)
{
t=t->ch[0];
delete tmp;
tmp=NULL;
}
else
{
int k=t->ch[0]->fix > t->ch[1]->fix ? 1:0;
Rotate(t,k);
Delete(t->ch[k],x);
}
}
else Delete(t->ch[d],x);
if(t!=NULL) t->Maintain();
}
bool Find(Treap *t,int x)
{
while(t!=NULL)
{
int d=t->compare(x);
if(d==-1) return true;
t=t->ch[d];
}
return false;
}
int Kth(Treap *t,int k)
{
if(t==NULL||k<=0||k>t->size)
return -1;
if(t->ch[0]==NULL&&k==1)
return t->key;
if(t->ch[0]==NULL)
return Kth(t->ch[1],k-1);
if(t->ch[0]->size>=k)
return Kth(t->ch[0],k);
if(t->ch[0]->size+1==k)
return t->key;
return Kth(t->ch[1],k-1-t->ch[0]->size);
}
int Rank(Treap *t,int x)
{
int r;
if(t->ch[0]==NULL) r=0;
else r=t->ch[0]->size;
if(x==t->key) return r+1;
if(x<t->key)
return Rank(t->ch[0],x);
return r+1+Rank(t->ch[1],x);
}
void DeleteTreap(Treap* &t)
{
if(t==NULL) return;
if(t->ch[0]!=NULL) DeleteTreap(t->ch[0]);
if(t->ch[1]!=NULL) DeleteTreap(t->ch[1]);
delete t;
t=NULL;
}
void Print(Treap *t)
{
if(t==NULL) return;
Print(t->ch[0]);
cout<<t->key<<endl;
Print(t->ch[1]);
}
int val[1000005];
int main()
{
int n,x,m;
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=n; i++)
scanf("%d",&val[i]);
int index=1;
Treap *root=NULL;
for(int i=1; i<=m; i++)
{
scanf("%d",&x);
for(int j=index; j<=x; j++)
Insert(root,val[j]);
index=x+1;
printf("%d\n",Kth(root,i));
}
DeleteTreap(root);
}
return 0;
}

6.动态规划

背包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <cstring>
using namespace std;
int a[6];
// int found = 0;
int sum = 0, num, total = 0;
int d[200005], V;
//c:费用 w:价值 n:个数
void bag01(int c, int w) {
for (int i = V; i >= c; i--)
d[i] = max(d[i], d[i - c] + w);
}
void allbag(int c, int w) {
for (int i = c; i <= V; i++)
d[i] = max(d[i], d[i - c] + w);
}
void multbag(int c, int w, int n) {
if (c * n >= V) allbag(c, w);
int k = 1;
while (k <= n) {
bag01(k * c, k * w);
n = n - k;
k = k * 2;
}
bag01(n * c, n * w);
}

数位DP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//求1到n中含有49的数有多少
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
using namespace std;
int bit[40];
long long f[40][4];
long long dp(int pos, int st, bool flag)
//表示所在位; pos 为状态, st st = 0 没有, 49st = 1 前一位为, 4st, 已经出现过; = 249, 表示高位是否是与原数不同的flag
{
if (pos == 0) return st == 2; //所有位都已扩展完,如果st==2 返回,否则返回10
if (flag && f[pos][st] != - 1) return f[pos][st]; //如果高位与原数不同且已经算过,直接返回
long long ans = 0;
int x = flag ? 9 : bit[pos];
//维护当前位最多是多少
for (int i = 0; i <= x; i++) {
if ((st == 2) || (st == 1 && i == 9)) //如果已经出现49 或者上一位为,当前位为49
ans += dp(pos - 1, 2, flag || i < x);
//算从当前状态扩展过去的下一位
else if (i == 4)ans += dp(pos - 1, 1, flag || i < x);
//当前位是,将向下扩展的4改成st1
else ans += dp(pos - 1, 0, flag || i < x);
//维持st0
}
if (flag) f[pos][st] = ans; //记忆化
return ans;
}
long long calc(long long x){
intlen = 0;
while (x){
bit[++len] = x % 10;
x = x / 10;
}
//拆位
dp(len, 0, 0);
//记忆化搜索进行计算
}
int main(){
int t;
scanf("%d", &t);
memset(f, -1, sizeof(f));
for (inti = 1; i <= t; i++){
long long n;
cin >> n;
cout << calc(n) << endl; //算小于的符合条件的数n
}
return 0;
}

7.图论

Floyed

1
2
3
4
5
6
7
8
9
10
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
if (i == k) continue;
for (int j = 1; j <= n; j++) {
if (i == j || k == j) continue;
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}

SPFA

最短路不用SPFA的都是异教徒!在玄学复杂度面前,只要你相信,它就是O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
vector<pair<int, int>> E[maxn];
int n, m;
int d[maxn], inq[maxn];
int main(int argc, char const *argv[])
{
while (cin >> n >> m) {
for (int i = 0; i < maxn; i++) E[i].clear();
for (int i = 0; i < maxn; i++) inq[i] = 0;
for (int i = 0; i < maxn; i++) d[i] = 1e9;
for (int i = 0; i < m; i++) {
int x, y, z; cin >> x >> y >> z;
E[x].push_back(make_pair(y, z));
E[y].push_back(make_pair(x, z));
}
int s, t; cin >> s >> t;
queue<int> Q;
Q.push(s), d[s] = 0, inq[s] = 1;
while (!Q.empty()) {
int now = Q.front();
Q.pop(); inq[now] = 0;
for (int i = 0; i < E[now].size(); i++) {
int v = E[now][i].first;
if (d[v] > d[now] + E[now][i].second) {
d[v] = d[now] + E[now][i].second;
if (inq[v] == 1) continue;
inq[v] = 1;
Q.push(v);
}
}
}
if (d[t] == 1e9) cout << "-1" << endl;
else cout << d[t] << endl;
}
}

次短路与第K短路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
HRBUST 1050 Hot Pursuit II
//求次短路:Dijkstra的dist数组和vis数组再加一维,松弛的时候讨论当前的路
//小于最短路,或者大于最短路但小于次短路这两种情况,就能维护一个次短路了
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1000 + 5;
const int INF = 0x3f3f3f3f;
struct Node {
int v, c, flag;
Node (int _v = 0, int _c = 0, int _flag = 0) : v(_v), c(_c), flag(_flag) {}
bool operator < (const Node &rhs) const {
return c > rhs.c;
}
};
struct Edge {
int v, cost;
Edge (int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}
};
vector<Edge>E[maxn];
bool vis[maxn][2];
int dist[maxn][2];
void Dijkstra(int n, int s) {
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) {
dist[i][0] = INF;
dist[i][1] = INF;
}
priority_queue<Node>que;
dist[s][0] = 0;
que.push(Node(s, 0, 0));
while (!que.empty()) {
Node tep = que.top(); que.pop();
int u = tep.v;
int flag = tep.flag;
if (vis[u][flag]) continue;
vis[u][flag] = true;
for (int i = 0; i < (int)E[u].size(); i++) {
int v = E[u][i].v;
int cost = E[u][i].cost;
if (!vis[v][0] && dist[v][0] > dist[u][flag] + cost) {
dist[v][1] = dist[v][0];
dist[v][0] = dist[u][flag] + cost;
que.push(Node(v, dist[v][0], 0));
que.push(Node(v, dist[v][1], 1));
} else if (!vis[v][1] && dist[v][1] > dist[u][flag] + cost) {
dist[v][1] = dist[u][flag] + cost;
que.push(Node(v, dist[v][1], 1));
}
}
}
}
void addedge(int u, int v, int w) {
E[u].push_back(Edge(v, w));
}
int main() {
//freopen("in.txt", "r", stdin);
int n, m, v, w;
while (scanf("%d", &n) != EOF) {
for (int i = 0; i <= n; i++) E[i].clear();
for (int u = 1; u <= n; u++) {
scanf("%d", &m);
for (int j = 0; j < m; j++) {
scanf("%d%d", &v, &w);
addedge(u, v, w);
}
}
Dijkstra(n, 1);
printf("%d\n", dist[n][1]);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//POJ 2449 Remmarguts' Date
//求S点到T点的第K短路的长度
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1000 + 5;
const int INF = 0x3f3f3f3f;
int s, t, k;
bool vis[maxn];
int dist[maxn];
struct Node {
int v, c;
Node (int _v = 0, int _c = 0) : v(_v), c(_c) {}
bool operator < (const Node &rhs) const {
return c + dist[v] > rhs.c + dist[rhs.v];
}
};
struct Edge {
int v, cost;
Edge (int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}
};
vector<Edge>E[maxn], revE[maxn];
void Dijkstra(int n, int s) {
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) dist[i] = INF;
priority_queue<Node>que;
dist[s] = 0;
que.push(Node(s, 0));
while (!que.empty()) {
Node tep = que.top(); que.pop();
int u = tep.v;
if (vis[u]) continue;
vis[u] = true;
for (int i = 0; i < (int)E[u].size(); i++) {
int v = E[u][i].v;
int cost = E[u][i].cost;
if (!vis[v] && dist[v] > dist[u] + cost) {
dist[v] = dist[u] + cost;
que.push(Node(v, dist[v]));
}
}
}
}
int astar(int s) {
priority_queue<Node> que;
que.push(Node(s, 0)); k--;
while (!que.empty()) {
Node pre = que.top(); que.pop();
int u = pre.v;
if (u == t) {
if (k) k--;
else return pre.c;
}
for (int i = 0; i < (int)revE[u].size(); i++) {
int v = revE[u][i].v;
int c = revE[u][i].cost;
que.push(Node(v, pre.c + c));
}
}
return -1;
}
void addedge(int u, int v, int w) {
revE[u].push_back(Edge(v, w));
E[v].push_back(Edge(u, w));
}
int main() {
//freopen("in.txt", "r", stdin);
int n, m, u, v, w;
while (scanf("%d%d", &n, &m) != EOF) {
for (int i = 0; i <= n; i++) {
E[i].clear();
revE[i].clear();
}
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
}
scanf("%d%d%d", &s, &t, &k);
Dijkstra(n, t);
if (dist[s] == INF) {
puts("-1");
continue;
}
if (s == t) k++;
printf("%d\n", astar(s));
}
return 0;
}

Tarjan无向图最小权值割边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#define N 1005
struct Edge {int v, next;} e[N * N];
struct Bridge {int u, v;} bri[N];
int val[N][N];
int bnt, n, m;
int Head[N], cnt;
int dfn[N], low[N], f[N], Index, judge[N][N];
void Init(){
cnt = bnt = Index = 0;
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(f, 0, sizeof(f));
memset(val, 0, sizeof(val));
memset(judge, 0, sizeof(judge));
for (int i = 0; i <= n; i++){
Head[i] = -1;
dfn[i] = 0;
}
}
void AddEdge(int u, int v){
e[cnt].v = v;
e[cnt].next = Head[u];
Head[u] = cnt++;
}
void TarJan(int u, int fa){
f[u] = fa;
low[u] = dfn[u] = ++Index;
for (int j = Head[u]; j != -1; j = e[j].next){
int v = e[j].v;
if (!dfn[v]){
TarJan(v, u);
low[u] = min(low[u], low[v]);
}
else if (v != fa)
low[u] = min(low[u], dfn[v]);
}
}
int main(){
while (scanf("%d%d", &n, &m) && n && m){
Init();
int a, b, c, i;
for (i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
AddEdge(a - 1, b - 1);
AddEdge(b - 1, a - 1);
val[a - 1][b - 1] = val[b - 1][a - 1] = c;
judge[a - 1][b - 1]++;
judge[b - 1][a - 1]++;
}
TarJan(0, 0);
bool flag = true;
for (int i = 0; i < n; ++i){
if (!dfn[i]) flag = false;
}
if (!flag){
cout << 0 << endl;
continue;
}
for (i = 0; i < n; i++){
int u = f[i];
if (low[i] > dfn[u]){
bri[bnt].u = min(u, i);
bri[bnt++].v = max(u, i);
}
}
int ans = 20000;
if (bnt == 0) ans = -1;
else {
for (i = 0; i < bnt; i++){
if (judge[bri[i].u][bri[i].v] > 1) continue;
ans = min(ans, val[bri[i].u][bri[i].v]);
}
}
if (ans == 20000) ans = -1;
else if (ans == 0) ans = 1;
printf("%d\n", ans);
}
}

二分图匹配以及交叉染色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
概念
最大独立集:求一个二分图中最大的一个点集,该点集内的点互不相连。
最小顶点覆盖数:在二分图中,用最少的点,让所有的边至少和一个点有关联。换句话说,假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。
最小路径覆盖:找出最小的路径条数,使这些路径覆盖图中所有点。
计算方法
最大独立集 = 顶点数 - 最大匹配数 = vN + uN - hungary()
最小顶点覆盖数 = 最大匹配数 = hungary()
最小路径覆盖=|G|-最大匹配数
//匈牙利算法
bool dfs(int u) {
for (int v = 0; v < V; v++)
if (g[u][v] && !used[v]) {
used[v] = 1;
if (m[v] == -1 || dfs(m[v])) {
m[v] = u;
return 1;
}
}
return 0;
}
int hungary() {
int res = 0; clr(m, -1);
for (int u = 0; u < U; u++) {
clr(used, 0);
if (dfs(u)) res++;
}
return res;
}
//KM算法 O(n^4)完美匹配
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 305;
const int INF = 0x3f3f3f3f;
int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度
int ex_girl[MAXN]; // 每个妹子的期望值
int ex_boy[MAXN]; // 每个男生的期望值
bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生
bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生
int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1
int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值
int N;
bool dfs(int girl)
{
vis_girl[girl] = true;
for (int boy = 0; boy < N; ++boy) {
if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
if (gap == 0) { // 如果符合要求
vis_boy[boy] = true;
if (match[boy] == -1 || dfs( match[boy] )) { // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
match[boy] = girl;
return true;
}
} else {
slack[boy] = min(slack[boy], gap); // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸
}
}
return false;
}
int KM()
{
memset(match, -1, sizeof match); // 初始每个男生都没有匹配的女生
memset(ex_boy, 0, sizeof ex_boy); // 初始每个男生的期望值为0
// 每个女生的初始期望值是与她相连的男生最大的好感度
for (int i = 0; i < N; ++i) {
ex_girl[i] = love[i][0];
for (int j = 1; j < N; ++j) {
ex_girl[i] = max(ex_girl[i], love[i][j]);
}
}
// 尝试为每一个女生解决归宿问题
for (int i = 0; i < N; ++i) {
fill(slack, slack + N, INF); // 因为要取最小值 初始化为无穷大
while (1) {
// 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
// 记录每轮匹配中男生女生是否被尝试匹配过
memset(vis_girl, false, sizeof vis_girl);
memset(vis_boy, false, sizeof vis_boy);
if (dfs(i)) break; // 找到归宿 退出
// 如果不能找到 就降低期望值
// 最小可降低的期望值
int d = INF;
for (int j = 0; j < N; ++j)
if (!vis_boy[j]) d = min(d, slack[j]);
for (int j = 0; j < N; ++j) {
// 所有访问过的女生降低期望值
if (vis_girl[j]) ex_girl[j] -= d;
// 所有访问过的男生增加期望值
if (vis_boy[j]) ex_boy[j] += d;
// 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
else slack[j] -= d;
}
}
}
// 匹配完成 求出所有配对的好感度的和
int res = 0;
for (int i = 0; i < N; ++i)
res += love[ match[i] ][i];
return res;
}
int main()
{
while (~scanf("%d", &N)) {
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
scanf("%d", &love[i][j]);
printf("%d\n", KM());
}
return 0;
}
//无向图的二分图判断
const int maxn=1000+5;
int n;//图节点数
vector<int> G[maxn];//G[i]表示i节点邻接的点
int color[maxn];//color[i]=0,1,2 表i节点 不涂颜色 涂白色 涂黑色
//判断无向图是否可二分
bool bipartite(int u)
{
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(color[v]==color[u]) return false;
if(!color[v])
{
color[v]=3-color[u];
if(!bipartite(v)) return false;
}
}
return true;
}

拓扑排序

用于处理有向图顺序的问题,比如在到达一个顶点之前必须要先到达一个顶点。

BFS说明

将所有入度为0的点全都push进队列,然后对队列中的每一个点u执行两个操作。

  1. 将其排入topo数组之中
  2. 找对应的临接点v,并将这条边删除(表现为将v的入度减一),如果此时v的入度为0了,则将v点Push进队列。当队列为空时,topo数组即为排好序的序列。

使用方法

  1. init()初始化
  2. head[u].push_back(v); indegree[v]++; 顶点下标从1~n,表示完成v之前必须要完成u
  3. bool ok = toposort(); //返回值为false表示有环
  4. 得到topo[]即为排好序的数组
  • 下标必须从1开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//下标从1~n
const maxn=1000;
int n,m;//n个点 m条边
vector<int>head[maxn];
int indegree[maxn];//记录入度
int topo[maxn];
void init(){
for (int i=0; i<=n; i++) {
head[i].clear();
indegree[i]=0;
topo[i]=0;
}
}
bool toposort(){
int index=1,cnt=0;
priority_queue<int>q;
for (int i=1; i<=n; i++) {
if (indegree[i]==0) {
q.push(i);
}
}
if(q.empty()) return false;
while (!q.empty()) {
cnt++;
int u=q.top();
q.pop();
topo[index++]=u;
int sz=head[u].size();
for (int i=0; i<sz; i++) {
int v=head[u][i];
if (--indegree[v]==0) {
q.push(v);
}
}
}
if(cnt==n) return true;
else return false;
}
void showAns(){
for (int i=1; i<=n; i++) {
printf("%d%c",topo[i],i==n?'\n':' ');
}
}

DFS说明

总共n个点(0~n-1),m组关系。

t:topo数组反向记录时的位置下标。

G数组:邻接矩阵 G[u][v]=1表示 u到v单向连通。

c数组:0表示未访问,1表示已找过,-1表示在DFS递归中。

topo数组:记录拓扑排序后的结果。

使用举例:UVa 10305 Ordering Tasks

使用方法

  1. memset(G,0,sizeof(G)); 清空G数组
  2. adde(u, v);加一条从u指向v的边,顶点下标从1~n,但是在G中存是从0~n-1
  3. toposort();调用该函数进行排序,返回值为false表示有环,否则表示没有环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//下标1~n
const maxn=1000;
int G[maxn][maxn];//邻接矩阵
int n,m;
int c[maxn];
int topo[maxn],t;
void adde(int u, int v){
G[u-1][v-1]=1;
}
bool dfs(int u){
c[u]=-1;//访问标志
for (int v=0; v<n; v++)
if (G[u][v])
if (c[v]<0) return false;
else if (!c[v]&&!dfs(v)) return false;
c[u]=1;
topo[--t]=u;
return true;
}
bool toposort(){
t=n;
memset(c,0,sizeof(c));
for (int u=0; u<n; u++)
if (!c[u]&&!dfs(u)) return false;
return true;
}
void showAns(){
for (int i=0; i<n; i++) {
printf("%d%c",topo[i]+1,i==n-1?'\n':' ');
}
}

无向图的割顶和割桥

应用

删除一个无向图中的点,能使得原图增加几个连通分量?

  • 如果该点是一个孤立的点,那么增加-1个。
  • 如果该点不是割点,那么增加0个。
  • 如果该点是割点且非根节点,那么增加该点在dfs树中(无反向边连回早期祖先的)的儿子数。
  • 如果该点是割点且是一个dfs树的根节点,那么增加该点在dfs树中(无反向边连回早期祖先的)的儿子数-1的数目,也就是增加了以该dfs树的儿子数目-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//求无向图的割顶和桥
const int maxn=100000+10; //顶点数
int n,m;//n个点 m条边 顶点下标0~n-1
int dfs_clock;//时钟,每访问一个节点增1
vector<int> G[maxn];//G[i]表示i节点邻接的所有节点
int pre[maxn];//pre[i]表示i节点被第一次访问到的时间戳,若pre[i]==0表示i还未被访问
int low[maxn];//low[i]表示i节点及其后代能通过反向边连回的最早的祖先的pre值
bool iscut[maxn];//标记i节点是不是一个割点
int cut[maxn];//cut[i]表示割i点的时图中联通分量的增加量
vector<pair<int, int> >Bridge;
int dfs(int u,int fa=-1)//求出以u为根节点(u在DFS树中的父节点是fa)的树的所有割顶和桥
{
if (fa == -1) cut[u]--; //如果是根的话,割时增加的量为“连出去的量减一”
int lowu=pre[u]=++dfs_clock;
int child=0; //子节点数目
for(int i=0; i<G[u].size(); i++)
{
int v=G[u][i];
if(!pre[v]){
child++;//未访问过的节点才能算是u的孩子
int lowv=dfs(v,u);
lowu=min(lowu,lowv);
if(lowv>=pre[u]){
cut[u]++;
iscut[u]=true; //u点是割顶
if(lowv>pre[u]) //割桥判定
Bridge.push_back(make_pair(u, v));
}
}
else if(pre[v]<pre[u] && v!=fa){//v!=fa确保了(u,v)是从u到v的反向边
lowu=min(lowu,pre[v]);
}
}
if(fa<0 && child==1 )
iscut[u]=false;//u若是根且孩子数<=1,那u就不是割顶
return low[u]=lowu;
}
void work(){
// input & initialize
scanf("%d%d",&n,&m);
dfs_clock=0;//初始化时钟
memset(pre,0,sizeof(pre));
memset(iscut,0,sizeof(iscut));
memset(cut, 0, sizeof(cut));
memset(low, 0, sizeof(low));
Bridge.clear();
for(int i=1;i<=n;i++) G[i].clear();
for(int i=0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);//下标1~n
G[u].push_back(v);
G[v].push_back(u);
}
//Apllication
int k = 0;
for (int i=1; i<=n; i++) {
if (!pre[i]) {
k++;
dfs(i);//每次遍历一个连通块
}
}
// output
printf("共有 %d 个连通量\n",k);
for(int i=1;i<=n;i++)
if(iscut[i]==true)
printf("割顶是:%d\n",i);
for (int i=0; i<Bridge.size(); i++) {
printf("割桥是:%d %d\n",Bridge[i].first,Bridge[i].second);
}
for (int i=1; i<=n; i++) {
printf("当删除 %d点 时,会增加 %d个联通量\n",i,cut[i] );
}
}

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
Hdu 3394.Railway
在一个有n个点,m条边的无向图中,如果某条边不在任何一个回路中,
则称这条边是无用的如果某条边被多个回路利用,则称这条边是冲突的,
求这个图中的冲突的和无用的边的条数
很明显,图中无用的边就是桥,而冲突的边呢?在每个块中,
如果边数大于点数,那么这个块中的每条边都是冲突边。
*/
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
#define MEM(a) memset(a, 0, sizeof(a))
#define pb push_back
const int maxv = 10000;
const int maxe = 100000;
struct Edge {
int u, v;
Edge () {}
Edge (int a, int b ) {
u = a;
v = b;
}
};
int pre[maxv], low[maxv], iscut[maxv], bccno[maxv];
int dfs_clock, bcc_cnt, qiao, ans2;
vector<int> G[maxe], bcc[maxv];
stack<Edge> S;
void dfs(int u, int fa) {
low[u] = pre[u] = ++dfs_clock;
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
S.push(Edge(u,v));
dfs(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= pre[u]) {
if (low[v] > pre[u]) qiao++;
bcc_cnt++; bcc[bcc_cnt].clear();
for(;;) {
Edge x = S.top(); S.pop();
if (bccno[x.u] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
if (bccno[x.v] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (x.u == u && x.v == v) break;
}
int cnt2 = 0;
for (int j = 0; j < (int)bcc[bcc_cnt].size(); j++) {
int k = bcc[bcc_cnt][j];
for (int h = 0; h < (int)G[k].size(); h++) {
int vv = G[k][h];
if (bccno[vv] == bcc_cnt) cnt2++;
}
}
if (cnt2/2 > (int)bcc[bcc_cnt].size()) ans2 += cnt2/2;
cnt2 = 0;
}
} else if (pre[v] < pre[u] && v != fa) {
S.push(Edge(u, v));
low[u] = min(low[u], pre[v]);
}
}
}
void find_bcc(int n) {
MEM(pre); MEM(iscut); MEM(bccno); MEM(low);
dfs_clock = bcc_cnt = qiao = ans2 = 0;
for (int i = 0; i < n; i++)
if (!pre[i]) dfs(i, -1);
for (int i = 0; i <= bcc_cnt; i++)
bcc[i].clear();
}
int main() {
//freopen("in.txt", "r", stdin);
int n, m, u, v;
while (scanf("%d%d", &n, &m) != EOF) {
if (!n && !m) break;
for (int i = 0; i < n; i++) G[i].clear();
for (int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
G[u].pb(v); G[v].pb(u);
}
find_bcc(n);
printf("%d %d\n", qiao, ans2);
}
return 0;
}

双连通分量

无向图中的双连通分量有两种:点双连通分量和边的连联通分量,如果去掉任意一个点之后,这个图还是连通的,就说这个图是点双连通的。如果去掉任意一条边之后这个图还是连通的,就说明这个图是边双连通的。

割桥将每一个边-双连通分量分开,low[i]的意义就是low[i]所连的块能返回的最早的祖先,也就是说,low[i]相同的点即为一个边连通分量。

Tips

  • 点的下标是从0~n-1
  • 使用该算法需要保证无重边
  • 跑完模版之后,所有low[i]值相同的点的集合分别为一个边-双连通分量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
//点-双连通分量
const int maxn=5000+10;//点数
struct Edge{
int u,v;
Edge(int _u,int _v){
u = _u, v = _v;
}
};
int pre[maxn]; //第一次访问的dfs_clock时间戳
int low[maxn];
int iscut[maxn]; //割点判断
int bccno[maxn]; // bccno[i]表示i所在最早访问的点-双联通分量的下标 即bcc[bccno[i]]这个连通分量集合中含有i这个点 对于割顶来讲没有意义,因为他属于多个点-双联通分量
vector<int> belong[maxn];//belong[i]表示i所在的双连通分量的下标的集合
int dfs_clock;
int bcc_cnt; // 双连通分量个数
vector<int>G[maxn]; // 顶点,下标0~n-1
vector<int>bcc[maxn]; //点双连通分量存储结果 下标1~bcnt
stack<Edge>S;
int dfs(int u,int fa){
int lowu = pre[u] = ++dfs_clock;
int child = 0;
for (int i=0; i<G[u].size(); i++) {
int v = G[u][i];
Edge e = Edge(u, v);
if (!pre[v]) {
S.push(e);
child++;
int lowv = dfs(v, u);
lowu = min(lowu, lowv);
if (lowv >= pre[u]) {
iscut[u] = true;
bcc_cnt++;
bcc[bcc_cnt].clear();
while (true) {
Edge x = S.top();
S.pop();
if (bccno[x.u]!=bcc_cnt) {
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
belong[x.u].push_back(bcc_cnt);
}
if (bccno[x.v] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
belong[x.v].push_back(bcc_cnt);
}
if (x.u == u && x.v ==v) {
break;
}
}
}
}
else if(pre[v] < pre[u] && v != fa){
S.push(e);
lowu = min(lowu, pre[v]);
}
}
if (fa < 0 && child == 1) {
iscut[u] = 0;
}
return low[u]=lowu;
}
int find_bcc(int n){//n个顶点
//栈无需清空,每次跑完必然为空
//bcc[]无需清空,组建连通分量时已清空
memset(pre, 0, sizeof(pre));
memset(iscut, 0, sizeof(iscut));
memset(bccno, 0, sizeof(bccno));
memset(low, 0, sizeof(low));
dfs_clock = bcc_cnt = 0;
int cnt = 0;
for (int i=1; i<=n; i++) {
if (!pre[i]) {
dfs(i, -1);
cnt++;
}
}
return cnt;
}
void work(int n,int m){// n个点 m条边
// input and initialize
for (int i=1; i<=n; i++) {
G[i].clear();
belong[i].clear();
}
for (int i=0; i<m; i++) {
int u,v;
scanf("%d%d",&u,&v);//index range: 1~n
G[u].push_back(v);
G[v].push_back(u);
}
// find biconnected component
int cnt = find_bcc(n);
// output
printf("共计%d个连通块\n",cnt);
printf("共计%d个点-双连通分量\n",bcc_cnt);
for (int i=1; i<=bcc_cnt; i++) {
printf("第%d个点-双连通分量所含的点有: ",i);
for (int j = 0; j<bcc[i].size(); j++) {
printf("%d ",bcc[i][j]);
}
printf("\n");
}
}
int main(){
int n,m; //n个点 m条边
while (scanf("%d%d",&n,&m)!=EOF) {
work(n, m);
}
}
/*
POJ 3352 Road Construction
给出一个没有重边的无向图,求至少加入几条边使整个图成为一个边双连通分量
把图中所有的边双连通分量缩成一个点,原图就缩成了一棵树,
要加的边数就是(所有度为1的点的个数 + 1)/2
*/
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define MEM(a) memset(a, 0, sizeof(a))
#define pb push_back
const int maxv = 1010;
int pre[maxv], low[maxv], deg[maxv], stakk[maxv];
int dfs_clock, top;
vector<int> G[maxv];
void dfs(int u, int fa) {
low[u] = pre[u] = ++dfs_clock;
stakk[top++] = u;
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v, u);
low[u] = min(low[u], low[v]);
} else if (pre[v] < pre[u] && v != fa) {
low[u] = min(low[u], pre[v]);
}
}
if (pre[u] == low[u]) {
while (top > 0 && stakk[top] != u) {
low[stakk[--top]] = low[u];
}
}
}
void find_bcc(int n) {
MEM(pre); MEM(low);MEM(deg);
dfs_clock = top = 0;
for (int i = 1; i <= n; i++)
if (!pre[i]) dfs(i, -1);
}
int main() {
int n, m, u, v;
while (scanf("%d%d", &n, &m) != EOF) {
for (int i = 1; i <= n; i++) G[i].clear();
for (int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
G[u].pb(v); G[v].pb(u);
}
find_bcc(n);
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < (int)G[i].size(); j++) {
if (low[i] != low[G[i][j]]) {
deg[low[i]]++;
deg[low[G[i][j]]]++;
}
}
}
for (int i = 1; i <= n; i++)
if (deg[i]/2 == 1) ans++;
printf("%d\n", (ans+1)/2);
}
return 0;
}
/*
LA 3523 Knights of the Round Table
亚瑟王要给一些骑士开会,但是这些骑士中有一些相互憎恨,
所以他们不能在圆桌中相邻,为了投票不出现支持的人数和反对的人数相等的情况,
每个圆桌中的骑士的个数必须为奇数个,
问有多少个骑士一定不能参加任何一个会议。
因为可能要开好几桌会议,所以要求出图中所有的连通分量,
又因为要求骑士的个数为奇数个,所以求每个联通分量中不在奇圈上的点的个数的和
*/
#include <cstdio>
#include <stack>
#include <cstring>
#include <vector>
using namespace std;
#define MEM(a) memset(a, 0, sizeof(a))
const int maxv = 1100;
const int maxe = maxv*maxv;
vector<int>G[maxv], bcc[maxv];
int dfs_clock, bcc_cnt, pre[maxv], low[maxv], bccno[maxv];
int color[maxv], odd[maxv], mapp[maxv][maxv];
struct Edge{
int u, v;
Edge(){}
Edge(int a, int b) {
u = a;
v = b;
}
};
stack<Edge> S;
bool bipartite (int u, int b) {
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (bccno[v] != b) continue;
if (color[v] == color[u]) return false;
if (!color[v]) {
color[v] = 3 - color[u];
if (!bipartite(v, b)) return false;
}
}
return true;
}
void tarjan(int u, int fa) {
pre[u] = low[u] = ++dfs_clock;
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
S.push(Edge(u, v));
tarjan(v, u);
low[u] = min(pre[v], low[u]);
if (low[v] >= pre[u]) {
bcc_cnt++;
bcc[bcc_cnt].clear();
for(;;) {
Edge x = S.top(); S.pop();
if (bccno[x.u] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
if (bccno[x.v] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (x.u == u && x.v == v) break;
}
}
}
else if (pre[v] < pre[u] && v != fa) {
S.push(Edge(u, v));
low[u] = min(low[u], pre[v]);
}
}
}
void find_bcc(int n) {
dfs_clock = bcc_cnt = 0;
MEM(pre); MEM(low);
for (int i = 1; i <= n; i++)
if (!pre[i]) tarjan(i, -1);
}
int main() {
int n, m, u, v;
while (scanf("%d%d", &n, &m) != EOF &&m && n) {
MEM(mapp);
for (int i = 1; i <= n; i++) G[i].clear();
for (int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
mapp[u][v] = mapp[u][v] = 1;
}
for (int u = 1; u <= n; u++) {
for (int v = u+1; v <= n; v++) {
if (!mapp[u][v]) {
G[u].push_back(v);
G[v].push_back(u);
}
}
}
find_bcc(n);
MEM(odd);
for (int i = 1; i <= bcc_cnt; i++) { //对于图中的每个点双连通分量
MEM(color);
for (int j = 0; j < (int)bcc[i].size(); j++) bccno[bcc[i][j]] = i;
//给双连通分量里的每个点标号
int u = bcc[i][0]; // 找到代表的点 (是割点?)
color[u] = 1;
if (!bipartite(u, i)) //判断如果这个双连通分量不是二分图,那么这个连通分量里的点都合格
for (int j = 0; j < (int)bcc[i].size(); j++) odd[bcc[i][j]] = 1;
}
int ans = n;
for (int i = 1; i <= n; i++) if (odd[i]) ans--;
printf("%d\n", ans);
}
return 0;
}

强连通分量

有向图强连通分量:在有向图中有一些点,这些点中如果能从A到B,那么一定从B能到达A,这些点组成的点数最多的子图是原图的一个强连通分量。 运用场合:有向图、有两两可达这种条件、往往通过把每个强连通分量缩点把原图化简成一棵树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const int maxn=5000+10;
int dfs_clock;//时钟
int scc_cnt;//强连通分量总数
vector<int> G[maxn];//G[i]表示i节点指向的所有点
vector<int> belong[maxn];//belong[i]表示第i个强联通分量包含的所有元素 下标1~scc_cnt
int pre[maxn]; //时间戳
int low[maxn]; //u以及u的子孙能到达的祖先pre值
int sccno[maxn];//sccno[i]==j表示i节点属于j连通分量 sccno[i]的区间为1~scc_cnt
int cnt[maxn];
stack<int> S;
void dfs(int u){
pre[u]=low[u]=++dfs_clock;
S.push(u);
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(!pre[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(!sccno[v]){
low[u]=min(low[u],pre[v]);
}
}
if(low[u] == pre[u]){//u为当前强连通分量的入口
scc_cnt++;
belong[scc_cnt].clear();
while(true){
int x=S.top(); S.pop();
sccno[x]=scc_cnt;
cnt[scc_cnt]++;
belong[scc_cnt].push_back(x);
if(x==u) break;
}
}
}
//求出有向图所有连通分量
void find_scc(int n){
scc_cnt=dfs_clock=0;
memset(sccno,0,sizeof(sccno));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)
if(!pre[i]) dfs(i);
}
void work(int n,int m){
for(int i=1;i<=n;i++) G[i].clear();
while(m--){
int u,v;
scanf("%d%d",&u,&v);//index range: 1~n
G[u].push_back(v);
}
find_scc(n);
for(int i=1;i<=n;i++)
printf("%d号点属于%d分量\n",i,sccno[i]);
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
work(n,m);
}
}
```
```cpp
/*
POJ 3180 The Cow Prom
有N头奶牛,和M个绳索。每个绳索从一头奶牛处发出,射向另一头奶牛。
问最后这M个绳索连出了几个集合?
*/
#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
using namespace std;
#define MEM(a) memset(a, 0, sizeof(a))
const int maxv = 21000;
vector<int>G[maxv];
int pre[maxv], lowlink[maxv], sccno[maxv], ans[maxv], dfs_clock, scc_cnt;
stack<int>S;
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
if (lowlink[u] > lowlink[v]) lowlink[u] = lowlink[v];
} else if (!sccno[v]) {
if (lowlink[u] > pre[v]) lowlink[u] = pre[v];
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
for(;;) {
int x = S.top(); S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
MEM(sccno); MEM(pre); MEM(ans);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
int main() {
int n, m, u, v;
while (scanf("%d%d", &n, &m) != EOF) {
for (int i = 1; i <= n; i++) G[i].clear();
for (int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
}
find_scc(n);
for (int u = 1; u <= n; u++) {
ans[sccno[u]]++;
}
int cnt = 0;
for (int i = 1; i <= scc_cnt; i++)
if (ans[i] > 1) cnt++;
printf("%d\n", cnt);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
HDU 3816 The King’s Problem
有一个n个点m条边的有向图,把这个图分成几个区域,
使得每个区域中的任意两点u, v要么u能到v,要么v能到u,
求最少要分成几个区域
缩点得到有向的树,即求这棵树的最少路径覆盖(点数 – 二分图的最大匹配)
*/
#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
using namespace std;
#define MEM(a) memset(a, 0, sizeof(a))
const int maxv = 15100;
vector<int>G[maxv], H[maxv];
int pre[maxv], lowlink[maxv], sccno[maxv], left[maxv], dfs_clock, scc_cnt;
bool vis[maxv];
stack<int>S;
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
if (lowlink[u] > lowlink[v]) lowlink[u] = lowlink[v];
} else if (!sccno[v]) {
if (lowlink[u] > pre[v]) lowlink[u] = pre[v];
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
for(;;) {
int x = S.top(); S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
MEM(sccno); MEM(pre);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
bool match(int u) {
for (int i = 0; i <(int)H[u].size(); i++) {
int v = H[u][i];
if (!vis[v]) {
vis[v] = 1;
if (left[v] == -1 || match(left[v])) {
left[v] = u;
return true;
}
}
}
return false;
}
int main() {
int n, m, u, v, t;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
G[i].clear();
H[i].clear();
}
for (int i = 0; i < m; i++) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
}
find_scc(n);
for (int u = 1; u <= n; u++) {
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (sccno[u] != sccno[v])
H[sccno[u]].push_back(sccno[v]);
}
}
int sum = 0;
memset(left, -1, sizeof(left));
for (int i = 1; i <= scc_cnt; i++) {
MEM(vis);
if (match(i)) sum++;
}
printf("%d\n", scc_cnt-sum);
}
return 0;
}

最小生成树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 205;
int pre[maxn];
void init(int n) {
for (int i = 0; i < n; i++) pre[i] = i;
}
int find(int x) {
if (pre[x] == x) return x;
else return pre[x] = find(pre[x]);
}
void unite(int x, int y) {
x = find(x), y = find(y);
if (x != y) pre[y] = x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
struct edge {
int u, v;
long long cost;
} es[maxn * maxn];
bool cmp(const edge &e1, const edge &e2) {
return e1.cost < e2.cost;
}
int V, E;
long long kruskal() {
sort(es, es + E, cmp);
init(V);
long long res = 0, cnt = 0;
for (int i = 0; i < E; i++) {
edge e = es[i];
if (!same(e.u, e.v)) {
cnt++;
unite(e.u, e.v);
res += e.cost;
}
}
if(cnt < V - 1) return -1;
return res;
}
int main(int argc, char const *argv[])
{
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
while (cin >> E >> V, E) {
memset(pre, -1, sizeof(pre));
init(V);
for (int i = 0; i < E; i++) scanf("%d%d%lld",&es[i].u,&es[i].v,&es[i].cost);
//cin >> es[i].u >> es[i].v >> es[i].cost;
long long res = kruskal();
if (res == -1) cout << "?" << endl;
else cout << res << endl;
}
}

最小树形图

最小树形图就是处理一个确定起点有向有权图的最小生成树问题

使用方法

  1. 修改mytype为边权的类型
  2. init(m)表示初始化m条边
  3. ans = Directed_MST(1, n, m);起点为1n个点,m条边
  4. bool ok = judge(ans);ok为true表示存在该树,false表示不存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
typedef int mytype;
const int Vmax=105;
const int Emax=Vmax*Vmax;
struct edge//有向边
{
int u,v; //起点 终点
mytype l; //权值
} e[Emax];
int pre[Vmax],ID[Vmax],vis[Vmax];
mytype In[Vmax];
void init(int m)
{
for(int i=1; i<=m; i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].l);
}
}
mytype Directed_MST(int root,int NV,int NE)
{
// memset(pre,0,sizeof(pre));
mytype ret = 0;
while(1)
{
//1.找最小入边
for(int i=1; i<=NV; i++)
In[i] = INF;
for(int i=1; i<=NE; i++)
{
int u = e[i].u;
int v = e[i].v;
if(e[i].l < In[v] && u != v)
{
pre[v] = u;
In[v] = e[i].l;
}
}
for(int i=1; i<=NV; i++)
{
if(i == root)
continue;
if(fabs(In[i]-INF)<eps)
return -1;//除了跟以外有点没有入边,则根无法到达它
}
//2.找环
int cntnode = 0;
memset(ID,-1,sizeof(ID));
memset(vis,-1,sizeof(vis));
In[root] = 0;
for(int i=1; i<=NV; i++) //标记每个环
{
ret += In[i];
int v = i;
while(vis[v] != i && ID[v] == -1 && v != root)
{
vis[v] = i;
v = pre[v];
}
if(v != root && ID[v] == -1)
{
ID[v] = ++cntnode;
for(int u = pre[v] ; u != v ; u = pre[u])
ID[u] = cntnode;
}
}
if(cntnode == 0)
break;//无环
for(int i=1; i<=NV; i++)
if(ID[i] == -1)
ID[i] = ++cntnode;
//3.缩点,重新标记
for(int i=1; i<=NE; i++)
{
int v = e[i].v;
e[i].u = ID[e[i].u];
e[i].v = ID[e[i].v];
if(e[i].u != e[i].v)
{
e[i].l -= In[v];
}
}
NV = cntnode;
root = ID[root];
}
return ret;
}
bool judge(mytype ans) //判断能否成树
{
return fabs(ans+1)>eps;
}

次小生成树

说明

求最小生成树时,用数组maxd[i][j]来表示MST中i到j最大边权,求完后,直接枚举所有不在MST中的边,替换掉最大边权的边,更新答案

使用方法

  1. SMST::init(n);
  2. SMST::adde(u, v, w); 初始化顶点个数,起始点位置
  3. int status = SMST::smst(); 返回-1表示无最小生成树,返回-2表示有最小生成树无次小生成树,否则返回次小生成树的权

Tips

  • SMST::weight直接记录了最小生成树的权重
  • 对于平行边/自环要进行特殊处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// 顶点下标1~n
const int Vmax = 105;
namespace SMST{
struct Edge{
int u,v,next,w,vis;
}e[405];
int ecnt;
int he[Vmax];
// MST
int n;//n个顶点
int s;//起点 树的根
int weight; //MST的重量
int dis[Vmax]; //dis[i]表示指向i点的最短的边
bool vis[Vmax]; //标记该点是否在树上
int pre[Vmax];//记录前驱
//SMST
int subweight; //SMST的重量
int id[Vmax];
int maxd[Vmax][Vmax]; //maxd[u][v]表示 u-v路径上最大的边权
void init(int Vsz,int source=1){//默认起点为1
memset(he, -1, sizeof(he));
ecnt = 0;
memset(dis, INF, sizeof(dis));
memset(maxd, 0, sizeof(maxd));
memset(vis, false, sizeof(vis));
n=Vsz;
weight=0;
dis[source] = 0;
pre[source] = source;
s = source;
}
//1~n的邻接矩阵
void adde(int u,int v,int w){
e[ecnt].vis = 0;
e[ecnt].u = u;
e[ecnt].v = v;
e[ecnt].w = w;
e[ecnt].next = he[u];
he[u] = ecnt ++;
}
int prim(){
/*
返回值说明:
-1: 无生成树
weight: 最小生成树的重量
*/
for(int i=1;i<=n;i++){
int pos=0;
int minc = INF;
for(int j=1;j<=n;j++){
if(!vis[j]&&dis[j]<minc){
pos=j;
minc = dis[j];
}
}
if(minc == INF) return -1; //n个点不联通 无生成树
weight+=dis[pos];
vis[pos]=true;
if (i!=s) {
e[id[pos]].vis = 1;
e[id[pos]^1].vis = 1;
}
for(int j=1;j<=n;j++){
if (j == pos) continue;
if(vis[j]) {
maxd[j][pos] = maxd[pos][j] = max(dis[pos], maxd[j][pre[pos]]);
}
}
for (int j = he[pos]; j!=-1; j = e[j].next) {
int v = e[j].v;
if (!vis[v] && e[j].w < dis[v]) {
dis[v] = e[j].w;
pre[v] = pos;
id[v] = j;
}
}
}
return weight;
}
int smst(){
/*
返回值说明:
-1: 无生成树
-2: 有最小生成树 无次小生成树 (比如给出的图即为一棵树)
subweight: 次小生成树的重量
*/
if(prim() == -1) return -1; //无生成树
subweight = INF;
for (int i=0; i<ecnt; i+=2) {
int u = e[i].u;
int v = e[i].v;
if (e[i].vis == 0) {
subweight = min(subweight, weight + e[i].w - maxd[u][v]);
}
}
if(subweight == INF) return -2; //只有唯一生成树 也就是说只有最小生成树 没有次小生成树
return subweight;
}
}

网络流:ISAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3fffffff;
template <int N, int M>
struct Isap
{
int top;
int d[N], pre[N], cur[N], gap[N];
struct Vertex {
int head;
} V[N];
struct Edge {
int v, next;
int c, f;
} E[M];
void init() {
memset(V, -1, sizeof(V));
top = 0;
}
void add_edge(int u, int v, int c) {
E[top].v = v;
E[top].c = c;
E[top].f = 0;
E[top].next = V[u].head;
V[u].head = top++;
}
void add(int u, int v, int c) {
add_edge(u, v, c);
add_edge(v, u, 0);
}
void set_d(int t) {
queue<int> Q;
memset(d, -1, sizeof(d));
memset(gap, 0, sizeof(gap));
d[t] = 0;
Q.push(t);
while (!Q.empty()) {
int v = Q.front(); Q.pop();
++gap[d[v]];
for (int i = V[v].head; ~i; i = E[i].next) {
int u = E[i].v;
if (d[u] == -1) {
d[u] = d[v] + 1;
Q.push(u);
}
}
}
}
int sap(int s, int t, int num) {
set_d(t);
int ans = 0, u = s;
int flow = inf;
memcpy(cur, V, sizeof(V));
while (d[s] < num) {
int &i = cur[u];
for (; ~i; i = E[i].next) {
int v = E[i].v;
if (E[i].c > E[i].f && d[u] == d[v] + 1) {
u = v;
pre[v] = i;
flow = min(flow, E[i].c - E[i].f);
if (u == t) {
while (u != s) {
int j = pre[u];
E[j].f += flow;
E[j ^ 1].f -= flow;
u = E[j ^ 1].v;
}
ans += flow;
flow = inf;
}
break;
}
}
if (i == -1) {
if (--gap[d[u]] == 0)
break;
int dmin = num - 1;
cur[u] = V[u].head;
for (int j = V[u].head; ~j; j = E[j].next)
if (E[j].c > E[j].f)
dmin = min(dmin, d[E[j].v]);
d[u] = dmin + 1;
++gap[d[u]];
if (u != s)
u = E[pre[u] ^ 1].v;
}
}
return ans;
}
};
Isap<100005, 200005> Sap;
int T, n, m, s, t, x, y, z, xx, yy, a[100005], b[100005];
int main(int argc, char const *argv[])
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
cin >> T;
while (T--) {
cin >> n >> m;
Sap.init();
for (int i = 1; i <= n; i++) cin >> x >> y, a[i] = x, b[i] = y;
xx = *min_element(a, a + n + 1) , yy = *max_element(b, b + n + 1) ;
//cout << xx << " " << yy << endl;
while (m--) cin >> x >> y >> z, Sap.add(x, y, z), Sap.add(y, x, z);
cout << Sap.sap(xx, yy, n) << endl;
}
}

全局最小割

最小割的模板同最大流,但是如果只要求割成两部分,并不在意隔开了哪些点,这是全图最小割问题,需要使用Stoer_Wanger算法来解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//HDU 3691 Nubulsa Expo
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define mem(a) memset(a, 0, sizeof(a))
const int maxv = 500;
const int inf = 0x3f3f3f3f;
int v[maxv], d[maxv];
int G[maxv][maxv];
bool vis[maxv];
int Stoer_Wanger(int n) {
int res = inf;
for (int i = 1; i <= n; i++) v[i] = i;
while (n > 1) {
int k = 1, pre = 1;
mem(vis); mem(d);
for (int i = 2; i <= n; i++) {
k = -1;
for (int j = 2; j <= n; j++) {
if ( !vis[ v[j] ] ) {
d[ v[j] ] += G[ v[pre] ][ v[j] ];
if (k == -1 || d[ v[k] ] < d[ v[j] ] ) {
k = j;
}
}
}
vis[ v[k] ] = true;
if (i == n) {
res = min(res, d[ v[k] ]);
for (int j = 1; j <= n; j++) {
G[ v[pre] ][ v[j] ] += G[ v[j] ][ v[k] ];
G[ v[j] ][ v[pre] ] += G[ v[j] ][ v[k] ];
}
v[ k ] = v[ n-- ];
}
pre = k;
}
}
return res;
}
int main() {
int n, m, s, u, v, w;
while (scanf("%d%d%d", &n, &m, &s) != EOF && s) {
mem(G);
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u][v] += w;
G[v][u] += w;
}
printf("%d\n", Stoer_Wanger(n));
}
return 0;
}

最小费用最大流

使用方法

  1. MCMF::init(n) n个点
  2. MCMF::adde(u,v,cap,cost); 加边
  3. long long minCost, maxFlow;
  4. maxFlow = MCMF::MincostMaxflow(st, ed, minCost); 执行完的结果maxFlow为最大流 minCost为最小费用

Tips

  • 需要保证初始网络中没有负权圈,不过可以有负权边
  • 求最大费用最大流:将所有边的费用都加个负号,最后结果再加回来一个负号即可
  • n个点的下标可以为0~n-1也可以为1~n
  • 拆点的话要把顶点数加倍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const int Vmax=2005; //需要拆点的话记得加倍
namespace MCMF{
struct Edge{
int from,to,cap,flow,cost;
Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}
};
int n,m;
vector<Edge>edges;
vector<int>G[Vmax];
int inq[Vmax]; //是否在队列中
int d[Vmax]; //Bellman-Ford
int p[Vmax]; //上一条弧
int a[Vmax]; //可改进量
void init(int _Vsz){
n=_Vsz;
for(int i=0;i<=n;i++) G[i].clear();
edges.clear();
}
void adde(int from,int to,int cap,int cost){
edges.push_back(Edge(from, to, cap, 0, cost));
edges.push_back(Edge(to, from, 0, 0, -cost));
m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool SPFA(int s,int t,int& flow,long long& cost){
for(int i=0;i<=n;i++) d[i]=INF;
memset(inq, 0, sizeof(inq));
d[s]=0;
inq[s]=1;
p[s]=0;
a[s]=INF;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
inq[u]=0;
for(int i=0;i<G[u].size();i++){
Edge& e=edges[G[u][i]];
if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
d[e.to]=d[u]+e.cost; //松弛操作
p[e.to]=G[u][i]; //记录上一条边信息
a[e.to]=min(a[u], e.cap-e.flow);
if(!inq[e.to]){
q.push(e.to);
inq[e.to]=1;
}
}
}
}
if(d[t]==INF) return false; //s-t 不联通,失败退出
flow+=a[t];
cost+=(long long)d[t]*(long long)a[t];
for(int u=t;u!=s;u=edges[p[u]].from){
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
}
return true;
}
int MincostMaxflow(int s,int t,long long& cost){
int flow=0;
cost=0;
while(SPFA(s, t, flow, cost));
return flow;
}
}

网络流上下界的问题

无源汇点上下界可行流

建图:

建立超级源,汇

对于一条边 u—>v low(u,v) high(u,v) 连边 u—>v high(u,v) - low(u,v)

对每个节点记录in,

1
2
if(in[i]<0) add_edge(i,n+1,-in[i]);
if(in[i]>0) add_edge(0,i,in[i]);
1
2
3
4
5
6
7
8
9
10
11
12
for(int i=0;i<m;i++)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
in[a]-=c;in[b]+=c;
low[i]=c;
add_edge(a,b,d-c);
}
for(int i=1;i<=n;i++)
{
if(in[i]<0) add_edge(i,n+1,-in[i]);
if(in[i]>0) add_edge(0,i,in[i]);
}

有源点汇点的上下界最大流

设原图 源点为 s 汇点 为 t,连一条t到s无下界上界无限大的边。。。。设两个超级源S,T,像无源汇判断可行流的问题一样,记录每个点的in,连接到相应的超级源汇点。。。对S,T跑一遍最大流,并检测S所连边是否满流。。。如果不满足连可行流都没有无解。。。否则去掉S,T点(但总点数不要边)对s,t跑一遍最大流。得到的结果既答案。。。。。第一遍最大流保证了每个点的下界流得到满足,此时的图里还有很多自由流可以走,第二遍最大流就将这些流走满了得到的就是答案。。。

总结一下有源点汇点的上下界最大流 步骤为:
1:连接 t–>s INF,并增加S,T 像无源汇可行流一样建边,第一次最大流判断可行流
2:去掉S,T(Adj变-1) 总点数不变,第二次最大流得到答案

有源汇点上下界最小流:

设原源汇点 s,t 建立超级源汇点S,T先不连接 t–>s 像无源汇点可行流判断一样的建图,对S,T跑一遍最大流,记录流量f1。。。 连接源汇点 t—>s 无下界,上界INF ….再对S,T跑一遍最大流,得到流量f2。。。

如果流量$$f1+f2=\sum_{i=1}^{n}Ini$$

则存在最小流,最小流流量既 t—>s 的后悔边的流量。
否则无解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Super S n+2 Super T n+3
for(int i=0;i<n+2;i++)
{
if(in[i]>0)
{
sum+=in[i];
addedge(n+2,i,in[i]);
}
if(in[i]<0) addedge(i,n+3,-in[i]);
}
int MaxFlow1=sap(n+2,n+3,n+4);
addedge(n+1,0,INF);
int MaxFlow2=sap(n+2,n+3,n+4);
if(MaxFlow1+MaxFlow2==sum)
{
printf("%d\n",edge[Size-2].flow);
}

2-SAT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int maxn=10000+10;
struct TwoSAT
{
int n;//原始图的节点数(未翻倍)
vector<int> G[maxn*2];//G[i]==j表示如果mark[i]=true,那么mark[j]也要=true
bool mark[maxn*2];//标记
int S[maxn*2],c;//S和c用来记录一次dfs遍历的所有节点编号
void init(int n)
{
this->n=n;
for(int i=0;i<2*n;i++) G[i].clear();
memset(mark,0,sizeof(mark));
}
//加入(x,xval)或(y,yval)条件
//xval=0表示假,yval=1表示真
void add_clause(int x,int xval,int y,int yval)
{
x=x*2+xval;
y=y*2+yval;
G[x^1].push_back(y);
G[y^1].push_back(x);
}
//从x执行dfs遍历,途径的所有点都标记
//如果不能标记,那么返回false
bool dfs(int x)
{
if(mark[x^1]) return false;//这两句的位置不能调换
if(mark[x]) return true;
mark[x]=true;
S[c++]=x;
for(int i=0;i<G[x].size();i++)
if(!dfs(G[x][i])) return false;
return true;
}
//判断当前2-SAT问题是否有解
bool solve()
{
for(int i=0;i<2*n;i+=2)
if(!mark[i] && !mark[i+1])
{
c=0;
if(!dfs(i))
{
while(c>0) mark[S[--c]]=false;
if(!dfs(i+1)) return false;
}
}
return true;
}
};

曼哈顿距离MST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
* O(n*logn)
性质:对于某个点,以他为中心的区域分为8个象限,对于每一个象限,只会取距离最近的一个点连边。
建图方法:
我们把所有的点按照x从小到大排序:x1≤x2≤…≤xn。
建立一个抽象数据结构T。T中的每个元素对应平面上的一个点(x,y),该元素的第一关键字等于y-x,第二关键字等于y+x。
从Pn到P1逐个处理每个点。处理Pk的时候,令Pk+1, Pk+2, …, Pn都已经存入到T
中。某个点Q(x,y)如果落在Pk的R1区间内,必须满足:
1. x≥xk
2. y-x>yk-xk
要满足第一个条件,Q必须属于集合{Pk+1, Pk+2, …, Pn},即Q必然在T中。
要满足第二个条件,Q在T中的第一关键字必须大于yk-xk(定值)。
因为我们要使得|PkQ|最小,所以我们实际上就是:从T的第一关键字大于某常数的所有元素中,寻找第二关键字最小的元素。
很明显,T可以用平衡二叉树来实现。按照第一关键字有序来建立平衡树,对于平衡树每个节点都记录以其为根的子树中第二关
键字最小的是哪个元素。查询、插入的时间复杂度都是O(logn)。
平衡二叉树也可以用线段树代替。
这里的代码用的BIT维护!
坐标变化:
R1->R2:关于y=x对称,swap(x,y)
R2->R3:考虑到代码的方便性,我们考虑R2->R7,x=-x。
R7->R4:因为上面求的是R2->R7,因此这里还是关于y=x对称。 */
const int INF=0x3f3f3f3f;
struct Point{
int x,y,id;
bool operator<(const Point p)const{
return x!=p.x?x<p.x:y<p.y;
}
}p[N];
struct BIT{
int min_val,pos;
void init(){
min_val=INF;
pos=-1;
}
}bit[N];
struct Edge{
int u,v,d;
bool operator<(const Edge e)const{
return d<e.d;
}
}e[N<<2];
int T[N],hs[N];
int n,mt,pre[N];
void adde(int u,int v,int d)
{
e[mt].u=u,e[mt].v=v;
e[mt++].d=d;
}
int find(int x)
{
return pre[x]=(x==pre[x]?x:find(pre[x]));
}
int dist(int i,int j)
{
return abs(p[i].x-p[j].x)+abs(p[i].y-p[j].y);
}
inline int lowbit(int x)
{
return x&(-x);
}
void update(int x,int val,int pos)
{
for(int i=x;i>=1;i-=lowbit(i))
if(val<bit[i].min_val)
bit[i].min_val=val,bit[i].pos=pos;
}
int query(int x,int m)
{
int min_val=INF,pos=-1;
for(int i=x;i<=m;i+=lowbit(i))
if(bit[i].min_val<min_val)
min_val=bit[i].min_val,pos=bit[i].pos;
return pos;
}
int Manhattan_minimum_spanning_tree(int n,Point *p,int K)
{
int i,w,dir,fa,fb,pos,m;
//Build graph
mt=0;
for(dir=0;dir<4;dir++){
//Coordinate transform - reflect by y=x and reflect by x=0
if(dir==1||dir==3){
for(i=0;i<n;i++)
swap(p[i].x,p[i].y);
}
else if(dir==2){
for(i=0;i<n;i++){
p[i].x=-p[i].x;
}
}
//Sort points according to x-coordinate
sort(p,p+n);
//Discretize
for(i=0;i<n;i++){
T[i]=hs[i]=p[i].y-p[i].x;
}
sort(hs,hs+n);
m=unique(hs,hs+n)-hs;
//Initialize BIT
for(i=1;i<=m;i++)
bit[i].init();
//Find points and add edges
for(i=n-1;i>=0;i--){
pos=lower_bound(hs,hs+m,T[i])-hs+1; //BIT中从1开始'
w=query(pos,m);
if(w!=-1)
adde(p[i].id,p[w].id,dist(i,w));
update(pos,p[i].x+p[i].y,i);
}
}
//Kruskal - 找到第K小的边
sort(e,e+mt);
for(i=0;i<n;i++)pre[i]=i;
for(i=0;i<mt;i++){
fa=find(e[i].u),fb=find(e[i].v);
if(fa!=fb){
K--;pre[fa]=fb;
if(K==0)return e[i].d;
}
}
}

8.计算几何

目录

㈠ 点的基本运算

  1. 平面上两点之间距离 1
  2. 判断两点是否重合 1
  3. 矢量叉乘 1
  4. 矢量点乘 2
  5. 判断点是否在线段上 2
  6. 求一点饶某点旋转后的坐标 2
  7. 求矢量夹角 2

㈡ 线段及直线的基本运算

  1. 点与线段的关系 3
  2. 求点到线段所在直线垂线的垂足 4
  3. 点到线段的最近点 4
  4. 点到线段所在直线的距离 4
  5. 点到折线集的最近距离 4
  6. 判断圆是否在多边形内 5
  7. 求矢量夹角余弦 5
  8. 求线段之间的夹角 5
  9. 判断线段是否相交 6
    10.判断线段是否相交但不交在端点处 6
    11.求线段所在直线的方程 6
    12.求直线的斜率 7
    13.求直线的倾斜角 7
    14.求点关于某直线的对称点 7
    15.判断两条直线是否相交及求直线交点 7
    16.判断线段是否相交,如果相交返回交点 7

㈢ 多边形常用算法模块

  1. 判断多边形是否简单多边形 8
  2. 检查多边形顶点的凸凹性 9
  3. 判断多边形是否凸多边形 9
  4. 求多边形面积 9
  5. 判断多边形顶点的排列方向,方法一 10
  6. 判断多边形顶点的排列方向,方法二 10
  7. 射线法判断点是否在多边形内 10
  8. 判断点是否在凸多边形内 11
  9. 寻找点集的graham算法 12
    10.寻找点集凸包的卷包裹法 13
    11.判断线段是否在多边形内 14
    12.求简单多边形的重心 15
    13.求凸多边形的重心 17
    14.求肯定在给定多边形内的一个点 17
    15.求从多边形外一点出发到该多边形的切线 18
    16.判断多边形的核是否存在 19

㈣ 圆的基本运算
.点是否在圆内 20
.求不共线的三点所确定的圆 21

㈤ 矩形的基本运算
1.已知矩形三点坐标,求第4点坐标 22

㈥ 常用算法的描述 22

㈦ 补充
1.两圆关系: 24
2.判断圆是否在矩形内: 24
3.点到平面的距离: 25
4.点是否在直线同侧: 25
5.镜面反射线: 25
6.矩形包含: 26
7.两圆交点: 27
8.两圆公共面积: 28

  1. 圆和直线关系: 29
  2. 内切圆: 30
  3. 求切点: 31
  4. 线段的左右旋: 31
    13.公式: 32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
/* 需要包含的头文件 */
#include <cmath >
/* 常用的常量定义 */
const double INF = 1E200;
const double EP = 1E-10;
const int MAXV = 300;
const double PI = 3.14159265;
/* 基本几何结构 */
struct POINT
{
double x;
double y;
POINT(double a = 0, double b = 0) { x = a; y = b;} //constructor
};
struct LINESEG
{
POINT s;
POINT e;
LINESEG(POINT a, POINT b) { s = a; e = b;}
LINESEG() { }
};
struct LINE // 直线的解析方程 a*x+b*y+c=0 为统一表示,约定 a >= 0
{
double a;
double b;
double c;
LINE(double d1 = 1, double d2 = -1, double d3 = 0) {a = d1; b = d2; c = d3;}
};
/*
* *
* 点的基本运算 *
* *
*/
double dist(POINT p1, POINT p2) // 返回两点之间欧氏距离
{
return ( sqrt( (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) ) );
}
bool equal_point(POINT p1, POINT p2) // 判断两个点是否重合
{
return ( (abs(p1.x - p2.x) < EP) && (abs(p1.y - p2.y) < EP) );
}
/
r=multiply(sp,ep,op),得到(sp-op)和(ep-op)的叉积
r>0:ep在矢量opsp的逆时针方向;
r=0:opspep三点共线;
r<0:ep在矢量opsp的顺时针方向
*/
double multiply(POINT sp, POINT ep, POINT op)
{
return ((sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y));
}
/*
r=dotmultiply(p1,p2,op),得到矢量(p1-op)和(p2-op)的点积,如果两个矢量都非零矢量
r<0:两矢量夹角为钝角;
r=0:两矢量夹角为直角;
r>0:两矢量夹角为锐角
*/
double dotmultiply(POINT p1, POINT p2, POINT p0)
{
return ((p1.x - p0.x) * (p2.x - p0.x) + (p1.y - p0.y) * (p2.y - p0.y));
}
/
判断点p是否在线段l上
条件:(p在线段l所在的直线上) && (点p在以线段l为对角线的矩形内)
*/
bool online(LINESEG l, POINT p)
{
return ( (multiply(l.e, p, l.s) == 0) && ( ( (p.x - l.s.x) * (p.x - l.e.x) <= 0 ) && ( (p.y - l.s.y) * (p.y - l.e.y) <= 0 ) ) );
}
// 返回点p以点o为圆心逆时针旋转alpha(单位:弧度)后所在的位置
POINT rotate(POINT o, double alpha, POINT p)
{
POINT tp;
p.x -= o.x;
p.y -= o.y;
tp.x = p.x * cos(alpha) - p.y * sin(alpha) + o.x;
tp.y = p.y * cos(alpha) + p.x * sin(alpha) + o.y;
return tp;
}
/* 返回顶角在o点,起始边为os,终止边为oe的夹角(单位:弧度)
角度小于pi,返回正值
角度大于pi,返回负值
可以用于求线段之间的夹角
原理:
r = dotmultiply(s,e,o) / (dist(o,s)*dist(o,e))
r'= multiply(s,e,o)
r >= 1 angle = 0;
r <= -1 angle = -PI
-1<r<1 && r'>0 angle = arccos(r)
-1<r<1 && r'<=0 angle = -arccos(r)
*/
double angle(POINT o, POINT s, POINT e)
{
double cosfi, fi, norm;
double dsx = s.x - o.x;
double dsy = s.y - o.y;
double dex = e.x - o.x;
double dey = e.y - o.y;
cosfi = dsx * dex + dsy * dey;
norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey);
cosfi /= sqrt( norm );
if (cosfi >= 1.0 ) return 0;
if (cosfi <= -1.0 ) return -3.1415926;
fi = acos(cosfi);
if (dsx * dey - dsy * dex > 0) return fi; // 说明矢量os 在矢量 oe的顺时针方向
return -fi;
}
/**\
* *
* 线段及直线的基本运算 *
* *
\**/
/* 判断点与线段的关系,用途很广泛
本函数是根据下面的公式写的,P是点C到线段AB所在直线的垂足
AC dot AB
r = ---------
||AB||^2
(Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
= -------------------------------
L^2
r has the following meaning:
r=0 P = A
r=1 P = B
r<0 P is on the backward extension of AB
r>1 P is on the forward extension of AB
0<r<1 P is interior to AB
*/
double relation(POINT p, LINESEG l)
{
LINESEG tl;
tl.s = l.s;
tl.e = p;
return dotmultiply(tl.e, l.e, l.s) / (dist(l.s, l.e) * dist(l.s, l.e));
}
// 求点C到线段AB所在直线的垂足 P
POINT perpendicular(POINT p, LINESEG l)
{
double r = relation(p, l);
POINT tp;
tp.x = l.s.x + r * (l.e.x - l.s.x);
tp.y = l.s.y + r * (l.e.y - l.s.y);
return tp;
}
/* 求点p到线段l的最短距离,并返回线段上距该点最近的点np
注意:np是线段l上到点p最近的点,不一定是垂足 */
double ptolinesegdist(POINT p, LINESEG l, POINT &np)
{
double r = relation(p, l);
if (r < 0)
{
np = l.s;
return dist(p, l.s);
}
if (r > 1)
{
np = l.e;
return dist(p, l.e);
}
np = perpendicular(p, l);
return dist(p, np);
}
// 求点p到线段l所在直线的距离,请注意本函数与上个函数的区别
double ptoldist(POINT p, LINESEG l)
{
return abs(multiply(p, l.e, l.s)) / dist(l.s, l.e);
}
/* 计算点到折线集的最近距离,并返回最近点.
注意:调用的是ptolineseg()函数 */
double ptopointset(int vcount, POINT pointset[], POINT p, POINT &q)
{
int i;
double cd = double(INF), td;
LINESEG l;
POINT tq, cq;
for (i = 0; i < vcount - 1; i++)
{
l.s = pointset[i];
l.e = pointset[i + 1];
td = ptolinesegdist(p, l, tq);
if (td < cd)
{
cd = td;
cq = tq;
}
}
q = cq;
return cd;
}
/* 判断圆是否在多边形内.ptolineseg()函数的应用2 */
bool CircleInsidePolygon(int vcount, POINT center, double radius, POINT polygon[])
{
POINT q;
double d;
q.x = 0;
q.y = 0;
d = ptopointset(vcount, polygon, center, q);
if (d < radius || fabs(d - radius) < EP)
return true;
else
return false;
}
/* 返回两个矢量l1和l2的夹角的余弦(-1 --- 1)注意:如果想从余弦求夹角的话,注意反余弦函数的定义域是从 0到pi */
double cosine(LINESEG l1, LINESEG l2)
{
return (((l1.e.x - l1.s.x) * (l2.e.x - l2.s.x) +
(l1.e.y - l1.s.y) * (l2.e.y - l2.s.y)) / (dist(l1.e, l1.s) * dist(l2.e, l2.s))) );
}
// 返回线段l1与l2之间的夹角 单位:弧度 范围(-pi,pi)
double lsangle(LINESEG l1, LINESEG l2)
{
POINT o, s, e;
o.x = o.y = 0;
s.x = l1.e.x - l1.s.x;
s.y = l1.e.y - l1.s.y;
e.x = l2.e.x - l2.s.x;
e.y = l2.e.y - l2.s.y;
return angle(o, s, e);
}
// 如果线段u和v相交(包括相交在端点处)时,返回true
//
//判断P1P2跨立Q1Q2的依据是:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) >= 0。
//判断Q1Q2跨立P1P2的依据是:( Q1 - P1 ) × ( P2 - P1 ) * ( P2 - P1 ) × ( Q2 - P1 ) >= 0。
bool intersect(LINESEG u, LINESEG v)
{
return ( (max(u.s.x, u.e.x) >= min(v.s.x, v.e.x)) && //排斥实验
(max(v.s.x, v.e.x) >= min(u.s.x, u.e.x)) &&
(max(u.s.y, u.e.y) >= min(v.s.y, v.e.y)) &&
(max(v.s.y, v.e.y) >= min(u.s.y, u.e.y)) &&
(multiply(v.s, u.e, u.s) * multiply(u.e, v.e, u.s) >= 0) && //跨立实验
(multiply(u.s, v.e, v.s) * multiply(v.e, u.e, v.s) >= 0));
}
// (线段u和v相交)&&(交点不是双方的端点) 时返回true
bool intersect_A(LINESEG u, LINESEG v)
{
return ((intersect(u, v)) &&
(!online(u, v.s)) &&
(!online(u, v.e)) &&
(!online(v, u.e)) &&
(!online(v, u.s)));
}
// 线段v所在直线与线段u相交时返回true;方法:判断线段u是否跨立线段v
bool intersect_l(LINESEG u, LINESEG v)
{
return multiply(u.s, v.e, v.s) * multiply(v.e, u.e, v.s) >= 0;
}
// 根据已知两点坐标,求过这两点的直线解析方程: a*x+b*y+c = 0 (a >= 0)
LINE makeline(POINT p1, POINT p2)
{
LINE tl;
int sign = 1;
tl.a = p2.y - p1.y;
if (tl.a < 0)
{
sign = -1;
tl.a = sign * tl.a;
}
tl.b = sign * (p1.x - p2.x);
tl.c = sign * (p1.y * p2.x - p1.x * p2.y);
return tl;
}
// 根据直线解析方程返回直线的斜率k,水平线返回 0,竖直线返回 1e200
double slope(LINE l)
{
if (abs(l.a) < 1e-20)
return 0;
if (abs(l.b) < 1e-20)
return INF;
return -(l.a / l.b);
}
// 返回直线的倾斜角alpha ( 0 - pi)
double alpha(LINE l)
{
if (abs(l.a) < EP)
return 0;
if (abs(l.b) < EP)
return PI / 2;
double k = slope(l);
if (k > 0)
return atan(k);
else
return PI + atan(k);
}
// 求点p关于直线l的对称点
POINT symmetry(LINE l, POINT p)
{
POINT tp;
tp.x = ((l.b * l.b - l.a * l.a) * p.x - 2 * l.a * l.b * p.y - 2 * l.a * l.c) / (l.a * l.a + l.b * l.b);
tp.y = ((l.a * l.a - l.b * l.b) * p.y - 2 * l.a * l.b * p.x - 2 * l.b * l.c) / (l.a * l.a + l.b * l.b);
return tp;
}
// 如果两条直线 l1(a1*x+b1*y+c1 = 0), l2(a2*x+b2*y+c2 = 0)相交,返回true,且返回交点p
bool lineintersect(LINE l1, LINE l2, POINT &p) // 是 L1,L2
{
double d = l1.a * l2.b - l2.a * l1.b;
if (abs(d) < EP) // 不相交
return false;
p.x = (l2.c * l1.b - l1.c * l2.b) / d;
p.y = (l2.a * l1.c - l1.a * l2.c) / d;
return true;
}
// 如果线段l1和l2相交,返回true且交点由(inter)返回,否则返回false
bool intersection(LINESEG l1, LINESEG l2, POINT &inter)
{
LINE ll1, ll2;
ll1 = makeline(l1.s, l1.e);
ll2 = makeline(l2.s, l2.e);
if (lineintersect(ll1, ll2, inter))
return online(l1, inter) && online(l2, inter);
else
return false;
}
/\
* *
* 多边形常用算法模块 *
* *
\/
// 如果无特别说明,输入多边形顶点要求按逆时针排列
/*
返回值:输入的多边形是简单多边形,返回true
要 求:输入顶点序列按逆时针排序
说 明:简单多边形定义:
1:循环排序中相邻线段对的交是他们之间共有的单个点
2:不相邻的线段不相交
本程序默认第一个条件已经满足
*/
bool issimple(int vcount, POINT polygon[])
{
int i, cn;
LINESEG l1, l2;
for (i = 0; i < vcount; i++)
{
l1.s = polygon[i];
l1.e = polygon[(i + 1) % vcount];
cn = vcount - 3;
while (cn)
{
l2.s = polygon[(i + 2) % vcount];
l2.e = polygon[(i + 3) % vcount];
if (intersect(l1, l2))
break;
cn--;
}
if (cn)
return false;
}
return true;
}
// 返回值:按输入顺序返回多边形顶点的凸凹性判断,bc[i]=1,iff:第i个顶点是凸顶点
void checkconvex(int vcount, POINT polygon[], bool bc[])
{
int i, index = 0;
POINT tp = polygon[0];
for (i = 1; i < vcount; i++) // 寻找第一个凸顶点
{
if (polygon[i].y < tp.y || (polygon[i].y == tp.y && polygon[i].x < tp.x))
{
tp = polygon[i];
index = i;
}
}
int count = vcount - 1;
bc[index] = 1;
while (count) // 判断凸凹性
{
if (multiply(polygon[(index + 1) % vcount], polygon[(index + 2) % vcount], polygon[index]) >= 0 )
bc[(index + 1) % vcount] = 1;
else
bc[(index + 1) % vcount] = 0;
index++;
count--;
}
}
// 返回值:多边形polygon是凸多边形时,返回true
bool isconvex(int vcount, POINT polygon[])
{
bool bc[MAXV];
checkconvex(vcount, polygon, bc);
for (int i = 0; i < vcount; i++) // 逐一检查顶点,是否全部是凸顶点
if (!bc[i])
return false;
return true;
}
// 返回多边形面积(signed);输入顶点按逆时针排列时,返回正值;否则返回负值
double area_of_polygon(int vcount, POINT polygon[])
{
double s;
if (vcount < 3) return 0;
s = polygon[0].y * (polygon[vcount - 1].x - polygon[1].x);
for (int i = 1; i < vcount; i++)
s += polygon[i].y * (polygon[(i - 1)].x - polygon[(i + 1) % vcount].x);
return s / 2;
}
// 如果输入顶点按逆时针排列,返回true
bool isconterclock(int vcount, POINT polygon[])
{
return area_of_polygon(vcount, polygon) > 0;
}
// 另一种判断多边形顶点排列方向的方法
bool isccwize(int vcount, POINT polygon[])
{
int i, index;
POINT a, b, v;
v = polygon[0];
index = 0;
for (i = 1; i < vcount; i++) // 找到最低且最左顶点,肯定是凸顶点
{
if (polygon[i].y < v.y || polygon[i].y == v.y && polygon[i].x < v.x)
{
index = i;
}
}
a = polygon[(index - 1 + vcount) % vcount]; // 顶点v的前一顶点
b = polygon[(index + 1) % vcount]; // 顶点v的后一顶点
return multiply(v, b, a) > 0;
}
/**
射线法判断点q与多边形polygon的位置关系,要求polygon为简单多边形,顶点逆时针排列
如果点在多边形内: 返回0
如果点在多边形边上: 返回1
如果点在多边形外: 返回2
/
int insidepolygon(int vcount, POINT Polygon[], POINT q)
{
int c = 0, i, n;
LINESEG l1, l2;
bool bintersect_a, bonline1, bonline2, bonline3;
double r1, r2;
l1.s = q;
l1.e = q;
l1.e.x = double(INF);
n = vcount;
for (i = 0; i < vcount; i++)
{
l2.s = Polygon[i];
l2.e = Polygon[(i + 1) % n];
if (online(l2, q))
return 1; // 如果点在边上,返回1
if ( (bintersect_a = intersect_A(l1, l2)) || // 相交且不在端点
( (bonline1 = online(l1, Polygon[(i + 1) % n])) && // 第二个端点在射线上
( (!(bonline2 = online(l1, Polygon[(i + 2) % n]))) && /* 前一个端点和后一个端点在射线两侧 */
((r1 = multiply(Polygon[i], Polygon[(i + 1) % n], l1.s) * multiply(Polygon[(i + 1) % n], Polygon[(i + 2) % n], l1.s)) > 0) ||
(bonline3 = online(l1, Polygon[(i + 2) % n])) && /* 下一条边是水平线,前一个端点和后一个端点在射线两侧 */
((r2 = multiply(Polygon[i], Polygon[(i + 2) % n], l1.s) * multiply(Polygon[(i + 2) % n],
Polygon[(i + 3) % n], l1.s)) > 0)
)
)
) c++;
}
if (c % 2 == 1)
return 0;
else
return 2;
}
//点q是凸多边形polygon内时,返回true;注意:多边形polygon一定要是凸多边形
bool InsideConvexPolygon(int vcount, POINT polygon[], POINT q) // 可用于三角形!
{
POINT p;
LINESEG l;
int i;
p.x = 0; p.y = 0;
for (i = 0; i < vcount; i++) // 寻找一个肯定在多边形polygon内的点p:多边形顶点平均值
{
p.x += polygon[i].x;
p.y += polygon[i].y;
}
p.x /= vcount;
p.y /= vcount;
for (i = 0; i < vcount; i++)
{
l.s = polygon[i]; l.e = polygon[(i + 1) % vcount];
if (multiply(p, l.e, l.s)*multiply(q, l.e, l.s) < 0) /* 点p和点q在边l的两侧,说明点q肯定在多边形外 */
break;
}
return (i == vcount);
}
/*
寻找凸包的graham 扫描法
PointSet为输入的点集;
ch为输出的凸包上的点集,按照逆时针方向排列;
n为PointSet中的点的数目
len为输出的凸包上的点的个数
*/
void Graham_scan(POINT PointSet[], POINT ch[], int n, int &len)
{
int i, j, k = 0, top = 2;
POINT tmp;
// 选取PointSet中y坐标最小的点PointSet[k],如果这样的点有多个,则取最左边的一个
for (i = 1; i < n; i++)
if ( PointSet[i].y < PointSet[k].y || (PointSet[i].y == PointSet[k].y) && (PointSet[i].x < PointSet[k].x) )
k = i;
tmp = PointSet[0];
PointSet[0] = PointSet[k];
PointSet[k] = tmp; // 现在PointSet中y坐标最小的点在PointSet[0]
for (i = 1; i < n - 1; i++) /* 对顶点按照相对PointSet[0]的极角从小到大进行排序,极角相同的按照距离PointSet[0]从近到远进行排序 */
{
k = i;
for (j = i + 1; j < n; j++)
if ( multiply(PointSet[j], PointSet[k], PointSet[0]) > 0 || // 极角更小
(multiply(PointSet[j], PointSet[k], PointSet[0]) == 0) && /* 极角相等,距离更短 */
dist(PointSet[0], PointSet[j]) < dist(PointSet[0], PointSet[k])
)
k = j;
tmp = PointSet[i];
PointSet[i] = PointSet[k];
PointSet[k] = tmp;
}
ch[0] = PointSet[0];
ch[1] = PointSet[1];
ch[2] = PointSet[2];
for (i = 3; i < n; i++)
{
while (multiply(PointSet[i], ch[top], ch[top - 1]) >= 0)
top--;
ch[++top] = PointSet[i];
}
len = top + 1;
}
// 卷包裹法求点集凸壳,参数说明同graham算法
void ConvexClosure(POINT PointSet[], POINT ch[], int n, int &len)
{
int top = 0, i, index, first;
double curmax, curcos, curdis;
POINT tmp;
LINESEG l1, l2;
bool use[MAXV];
tmp = PointSet[0];
index = 0;
// 选取y最小点,如果多于一个,则选取最左点
for (i = 1; i < n; i++)
{
if (PointSet[i].y < tmp.y || PointSet[i].y == tmp.y && PointSet[i].x < tmp.x)
{
index = i;
}
use[i] = false;
}
tmp = PointSet[index];
first = index;
use[index] = true;
index = -1;
ch[top++] = tmp;
tmp.x -= 100;
l1.s = tmp;
l1.e = ch[0];
l2.s = ch[0];
while (index != first)
{
curmax = -100;
curdis = 0;
// 选取与最后一条确定边夹角最小的点,即余弦值最大者
for (i = 0; i < n; i++)
{
if (use[i])continue;
l2.e = PointSet[i];
curcos = cosine(l1, l2); // 根据cos值求夹角余弦,范围在 (-1 -- 1 )
if (curcos > curmax || fabs(curcos - curmax) < 1e-6 && dist(l2.s, l2.e) > curdis)
{
curmax = curcos;
index = i;
curdis = dist(l2.s, l2.e);
}
}
use[first] = false; //清空第first个顶点标志,使最后能形成封闭的hull
use[index] = true;
ch[top++] = PointSet[index];
l1.s = ch[top - 2];
l1.e = ch[top - 1];
l2.s = ch[top - 1];
}
len = top - 1;
}
/
判断线段是否在简单多边形内(注意:如果多边形是凸多边形,下面的算法可以化简)
必要条件一:线段的两个端点都在多边形内;
必要条件二:线段和多边形的所有边都不内交;
用途: 1. 判断折线是否在简单多边形内
2. 判断简单多边形是否在另一个简单多边形内
*/
bool LinesegInsidePolygon(int vcount, POINT polygon[], LINESEG l)
{
// 判断线端l的端点是否不都在多边形内
if (!insidepolygon(vcount, polygon, l.s) || !insidepolygon(vcount, polygon, l.e))
return false;
int top = 0, i, j;
POINT PointSet[MAXV], tmp;
LINESEG s;
for (i = 0; i < vcount; i++)
{
s.s = polygon[i];
s.e = polygon[(i + 1) % vcount];
if (online(s, l.s)) //线段l的起始端点在线段s上
PointSet[top++] = l.s;
else if (online(s, l.e)) //线段l的终止端点在线段s上
PointSet[top++] = l.e;
else
{
if (online(l, s.s)) //线段s的起始端点在线段l上
PointSet[top++] = s.s;
else if (online(l, s.e)) // 线段s的终止端点在线段l上
PointSet[top++] = s.e;
else
{
if (intersect(l, s)) // 这个时候如果相交,肯定是内交,返回false
return false;
}
}
}
for (i = 0; i < top - 1; i++) /* 冒泡排序,x坐标小的排在前面;x坐标相同者,y坐标小的排在前面 */
{
for (j = i + 1; j < top; j++)
{
if ( PointSet[i].x > PointSet[j].x || fabs(PointSet[i].x - PointSet[j].x) < EP && PointSet[i].y > PointSet[j].y )
{
tmp = PointSet[i];
PointSet[i] = PointSet[j];
PointSet[j] = tmp;
}
}
}
for (i = 0; i < top - 1; i++)
{
tmp.x = (PointSet[i].x + PointSet[i + 1].x) / 2; //得到两个相邻交点的中点
tmp.y = (PointSet[i].y + PointSet[i + 1].y) / 2;
if (!insidepolygon(vcount, polygon, tmp))
return false;
}
return true;
}
/
求任意简单多边形polygon的重心
需要调用下面几个函数:
void AddPosPart(); 增加右边区域的面积
void AddNegPart(); 增加左边区域的面积
void AddRegion(); 增加区域面积
在使用该程序时,如果把xtr,ytr,wtr,xtl,ytl,wtl设成全局变量就可以使这些函数的形式得到化简,
但要注意函数的声明和调用要做相应变化
*/
void AddPosPart(double x, double y, double w, double &xtr, double &ytr, double &wtr)
{
if (abs(wtr + w) < 1e-10 ) return; // detect zero regions
xtr = ( wtr * xtr + w * x ) / ( wtr + w );
ytr = ( wtr * ytr + w * y ) / ( wtr + w );
wtr = w + wtr;
return;
}
void AddNegPart(double x, ouble y, double w, double &xtl, double &ytl, double &wtl)
{
if ( abs(wtl + w) < 1e-10 )
return; // detect zero regions
xtl = ( wtl * xtl + w * x ) / ( wtl + w );
ytl = ( wtl * ytl + w * y ) / ( wtl + w );
wtl = w + wtl;
return;
}
void AddRegion ( double x1, double y1, double x2, double y2, double &xtr, double &ytr,
double &wtr, double &xtl, double &ytl, double &wtl )
{
if ( abs (x1 - x2) < 1e-10 )
return;
if ( x2 > x1 )
{
AddPosPart ((x2 + x1) / 2, y1 / 2, (x2 - x1) * y1, xtr, ytr, wtr); /* rectangle 全局变量变化处 */
AddPosPart ((x1 + x2 + x2) / 3, (y1 + y1 + y2) / 3, (x2 - x1) * (y2 - y1) / 2, xtr, ytr, wtr);
// triangle 全局变量变化处
}
else
{
AddNegPart ((x2 + x1) / 2, y1 / 2, (x2 - x1) * y1, xtl, ytl, wtl);
// rectangle 全局变量变化处
AddNegPart ((x1 + x2 + x2) / 3, (y1 + y1 + y2) / 3, (x2 - x1) * (y2 - y1) / 2, xtl, ytl, wtl);
// triangle 全局变量变化处
}
}
POINT cg_simple(int vcount, POINT polygon[])
{
double xtr, ytr, wtr, xtl, ytl, wtl;
//注意: 如果把xtr,ytr,wtr,xtl,ytl,wtl改成全局变量后这里要删去
POINT p1, p2, tp;
xtr = ytr = wtr = 0.0;
xtl = ytl = wtl = 0.0;
for (int i = 0; i < vcount; i++)
{
p1 = polygon[i];
p2 = polygon[(i + 1) % vcount];
AddRegion(p1.x, p1.y, p2.x, p2.y, xtr, ytr, wtr, xtl, ytl, wtl); //全局变量变化处
}
tp.x = (wtr * xtr + wtl * xtl) / (wtr + wtl);
tp.y = (wtr * ytr + wtl * ytl) / (wtr + wtl);
return tp;
}
// 求凸多边形的重心,要求输入多边形按逆时针排序
POINT gravitycenter(int vcount, POINT polygon[])
{
POINT tp;
double x, y, s, x0, y0, cs, k;
x = 0; y = 0; s = 0;
for (int i = 1; i < vcount - 1; i++)
{
x0 = (polygon[0].x + polygon[i].x + polygon[i + 1].x) / 3;
y0 = (polygon[0].y + polygon[i].y + polygon[i + 1].y) / 3; //求当前三角形的重心
cs = multiply(polygon[i], polygon[i + 1], polygon[0]) / 2;
//三角形面积可以直接利用该公式求解
if (abs(s) < 1e-20)
{
x = x0; y = y0; s += cs; continue;
}
k = cs / s; //求面积比例
x = (x + k * x0) / (1 + k);
y = (y + k * y0) / (1 + k);
s += cs;
}
tp.x = x;
tp.y = y;
return tp;
}
/
给定一简单多边形,找出一个肯定在该多边形内的点
定理1 :每个多边形至少有一个凸顶点
定理2 :顶点数>=4的简单多边形至少有一条对角线
结论 : x坐标最大,最小的点肯定是凸顶点
y坐标最大,最小的点肯定是凸顶点
/
POINT a_point_insidepoly(int vcount, POINT polygon[])
{
POINT v, a, b, r;
int i, index;
v = polygon[0];
index = 0;
for (i = 1; i < vcount; i++) //寻找一个凸顶点
{
if (polygon[i].y < v.y)
{
v = polygon[i];
index = i;
}
}
a = polygon[(index - 1 + vcount) % vcount]; //得到v的前一个顶点
b = polygon[(index + 1) % vcount]; //得到v的后一个顶点
POINT tri[3], q;
tri[0] = a; tri[1] = v; tri[2] = b;
double md = INF;
int in1 = index;
bool bin = false;
for (i = 0; i < vcount; i++) //寻找在三角形avb内且离顶点v最近的顶点q
{
if (i == index)continue;
if (i == (index - 1 + vcount) % vcount)continue;
if (i == (index + 1) % vcount)continue;
if (!InsideConvexPolygon(3, tri, polygon[i]))continue;
bin = true;
if (dist(v, polygon[i]) < md)
{
q = polygon[i];
md = dist(v, q);
}
}
if (!bin) //没有顶点在三角形avb内,返回线段ab中点
{
r.x = (a.x + b.x) / 2;
r.y = (a.y + b.y) / 2;
return r;
}
r.x = (v.x + q.x) / 2; //返回线段vq的中点
r.y = (v.y + q.y) / 2;
return r;
}
/**/
求从多边形外一点p出发到一个简单多边形的切线,如果存在返回切点,其中rp点是右切点,lp是左切点
注意:p点一定要在多边形外 ,输入顶点序列是逆时针排列
原 理: 如果点在多边形内肯定无切线;凸多边形有唯一的两个切点,凹多边形就可能有多于两个的切点;
如果polygon是凸多边形,切点只有两个只要找到就可以,可以化简此算法
如果是凹多边形还有一种算法可以求解:先求凹多边形的凸包,然后求凸包的切线
/**/
void pointtangentpoly(int vcount, POINT polygon[], POINT p, POINT &rp, POINT &lp)
{
LINESEG ep, en;
bool blp, bln;
rp = polygon[0];
lp = polygon[0];
for (int i = 1; i < vcount; i++)
{
ep.s = polygon[(i + vcount - 1) % vcount];
ep.e = polygon[i];
en.s = polygon[i];
en.e = polygon[(i + 1) % vcount];
blp = multiply(ep.e, p, ep.s) >= 0; // p is to the left of pre edge
bln = multiply(en.e, p, en.s) >= 0; // p is to the left of next edge
if (!blp && bln)
{
if (multiply(polygon[i], rp, p) > 0) // polygon[i] is above rp
rp = polygon[i];
}
if (blp && !bln)
{
if (multiply(lp, polygon[i], p) > 0) // polygon[i] is below lp
lp = polygon[i];
}
}
return ;
}
// 如果多边形polygon的核存在,返回true,返回核上的一点p.顶点按逆时针方向输入
bool core_exist(int vcount, POINT polygon[], POINT &p)
{
int i, j, k;
LINESEG l;
LINE lineset[MAXV];
for (i = 0; i < vcount; i++)
{
lineset[i] = makeline(polygon[i], polygon[(i + 1) % vcount]);
}
for (i = 0; i < vcount; i++)
{
for (j = 0; j < vcount; j++)
{
if (i == j)continue;
if (lineintersect(lineset[i], lineset[j], p))
{
for (k = 0; k < vcount; k++)
{
l.s = polygon[k];
l.e = polygon[(k + 1) % vcount];
if (multiply(p, l.e, l.s) > 0)
//多边形顶点按逆时针方向排列,核肯定在每条边的左侧或边上
break;
}
if (k == vcount) //找到了一个核上的点
break;
}
}
if (j < vcount) break;
}
if (i < vcount)
return true;
else
return false;
}
/*\
* *
* 圆的基本运算 *
* *
\*/
/
返回值 : 点p在圆内(包括边界)时,返回true
用途 : 因为圆为凸集,所以判断点集,折线,多边形是否在圆内时,
只需要逐一判断点是否在圆内即可。
*/
bool point_in_circle(POINT o, double r, POINT p)
{
double d2 = (p.x - o.x) * (p.x - o.x) + (p.y - o.y) * (p.y - o.y);
double r2 = r * r;
return d2 < r2 || abs(d2 - r2) < EP;
}
/
用 途 :求不共线的三点确定一个圆
输 入 :三个点p1,p2,p3
返回值 :如果三点共线,返回false;反之,返回true。圆心由q返回,半径由r返回
*/
bool cocircle(POINT p1, POINT p2, POINT p3, POINT &q, double &r)
{
double x12 = p2.x - p1.x;
double y12 = p2.y - p1.y;
double x13 = p3.x - p1.x;
double y13 = p3.y - p1.y;
double z2 = x12 * (p1.x + p2.x) + y12 * (p1.y + p2.y);
double z3 = x13 * (p1.x + p3.x) + y13 * (p1.y + p3.y);
double d = 2.0 * (x12 * (p3.y - p2.y) - y12 * (p3.x - p2.x));
if (abs(d) < EP) //共线,圆不存在
return false;
q.x = (y13 * z2 - y12 * z3) / d;
q.y = (x12 * z3 - x13 * z2) / d;
r = dist(p1, q);
return true;
}
int line_circle(LINE l, POINT o, double r, POINT &p1, POINT &p2)
{
return true;
}
/**\
* *
* 矩形的基本运算 *
* *
\**/
/*
说明:因为矩形的特殊性,常用算法可以化简:
1.判断矩形是否包含点
只要判断该点的横坐标和纵坐标是否夹在矩形的左右边和上下边之间。
2.判断线段、折线、多边形是否在矩形中
因为矩形是个凸集,所以只要判断所有端点是否都在矩形中就可以了。
3.判断圆是否在矩形中
圆在矩形中的充要条件是:圆心在矩形中且圆的半径小于等于圆心到矩形四边的距离的最小值。
*/
// 已知矩形的三个顶点(a,b,c),计算第四个顶点d的坐标. 注意:已知的三个顶点可以是无序的
POINT rect4th(POINT a, POINT b, POINT c)
{
POINT d;
if (abs(dotmultiply(a, b, c)) < EP) // 说明c点是直角拐角处
{
d.x = a.x + b.x - c.x;
d.y = a.y + b.y - c.y;
}
if (abs(dotmultiply(a, c, b)) < EP) // 说明b点是直角拐角处
{
d.x = a.x + c.x - b.x;
d.y = a.y + c.y - b.x;
}
if (abs(dotmultiply(c, b, a)) < EP) // 说明a点是直角拐角处
{
d.x = c.x + b.x - a.x;
d.y = c.y + b.y - a.y;
}
return d;
}
/*\
* *
* 常用算法的描述 *
* *
\*/
/*
尚未实现的算法:
1. 求包含点集的最小圆
2. 求多边形的交
3. 简单多边形的三角剖分
4. 寻找包含点集的最小矩形
5. 折线的化简
6. 判断矩形是否在矩形中
7. 判断矩形能否放在矩形中
8. 矩形并的面积与周长
9. 矩形并的轮廓
10.矩形并的闭包
11.矩形的交
12.点集中的最近点对
13.多边形的并
14.圆的交与并
15.直线与圆的关系
16.线段与圆的关系
17.求多边形的核监视摄象机
18.求点集中不相交点对 railwai
*//*
寻找包含点集的最小矩形
原理:该矩形至少一条边与点集的凸壳的某条边共线
First take the convex hull of the points. Let the resulting convex
polygon be P. It has been known for some time that the minimum
area rectangle enclosing P must have one rectangle side flush with
(i.e., collinear with and overlapping) one edge of P. This geometric
fact was used by Godfried Toussaint to develop the "rotating calipers"
algorithm in a hard-to-find 1983 paper, "Solving Geometric Problems
with the Rotating Calipers" (Proc. IEEE MELECON). The algorithm
rotates a surrounding rectangle from one flush edge to the next,
keeping track of the minimum area for each edge. It achieves O(n)
time (after hull computation). See the "Rotating Calipers Homepage"
http://www.cs.mcgill.ca/~orm/rotcal.frame.html for a description
and applet.
*//*
折线的化简 伪码如下:
Input: tol = the approximation tolerance
L = {V0,V1,,Vn-1} is any n-vertex polyline
Set start = 0;
Set k = 0;
Set W0 = V0;
for each vertex Vi (i=1,n-1)
{
if Vi is within tol from Vstart
then ignore it, and continue with the next vertex
Vi is further than tol away from Vstart
so add it as a new vertex of the reduced polyline
Increment k++;
Set Wk = Vi;
Set start = i; as the new initial vertex
}
Output: W = {W0,W1,,Wk-1} = the k-vertex simplified polyline
*/
/**\
* *
* 补充 *
* *
\**/
//两圆关系:
/* 两圆:
相离: return 1;
外切: return 2;
相交: return 3;
内切: return 4;
内含: return 5;
*/
int CircleRelation(POINT p1, double r1, POINT p2, double r2)
{
double d = sqrt( (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) );
if ( fabs(d - r1 - r2) < EP ) // 必须保证前两个if先被判定!
return 2;
if ( fabs(d - fabs(r1 - r2)) < EP )
return 4;
if ( d > r1 + r2 )
return 1;
if ( d < fabs(r1 - r2) )
return 5;
if ( fabs(r1 - r2) < d && d < r1 + r2 )
return 3;
return 0; // indicate an error!
}
//判断圆是否在矩形内:
// 判定圆是否在矩形内,是就返回true(设矩形水平,且其四个顶点由左上开始按顺时针排列)
// 调用ptoldist函数,在第4页
bool CircleRecRelation(POINT pc, double r, POINT pr1, POINT pr2, POINT pr3, POINT pr4)
{
if ( pr1.x < pc.x && pc.x < pr2.x && pr3.y < pc.y && pc.y < pr2.y )
{
LINESEG line1(pr1, pr2);
LINESEG line2(pr2, pr3);
LINESEG line3(pr3, pr4);
LINESEG line4(pr4, pr1);
if ( r < ptoldist(pc, line1) && r < ptoldist(pc, line2) && r < ptoldist(pc, line3) && r < ptoldist(pc, line4) )
return true;
}
return false;
}
//点到平面的距离:
//点到平面的距离,平面用一般式表示ax+by+cz+d=0
double P2planeDist(double x, double y, double z, double a, double b, double c, double d)
{
return fabs(a * x + b * y + c * z + d) / sqrt(a * a + b * b + c * c);
}
//点是否在直线同侧:
//两个点是否在直线同侧,是则返回true
bool SameSide(POINT p1, POINT p2, LINE line)
{
return (line.a * p1.x + line.b * p1.y + line.c) *
(line.a * p2.x + line.b * p2.y + line.c) > 0;
}
//镜面反射线:
// 已知入射线、镜面,求反射线。
// a1,b1,c1为镜面直线方程(a1 x + b1 y + c1 = 0 ,下同)系数;
//a2,b2,c2为入射光直线方程系数;
//a,b,c为反射光直线方程系数.
// 光是有方向的,使用时注意:入射光向量:<-b2,a2>;反射光向量:<b,-a>.
// 不要忘记结果中可能会有"negative zeros"
void reflect(double a1, double b1, double c1, double a2, double b2, double c2, double &a, double &b, double &c)
{
double n, m;
double tpb, tpa;
tpb = b1 * b2 + a1 * a2;
tpa = a2 * b1 - a1 * b2;
m = (tpb * b1 + tpa * a1) / (b1 * b1 + a1 * a1);
n = (tpa * b1 - tpb * a1) / (b1 * b1 + a1 * a1);
if (fabs(a1 * b2 - a2 * b1) < 1e-20)
{
a = a2; b = b2; c = c2;
return;
}
double xx, yy; //(xx,yy)是入射线与镜面的交点。
xx = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1);
yy = (a2 * c1 - a1 * c2) / (a1 * b2 - a2 * b1);
a = n;
b = -m;
c = m * yy - xx * n;
}
//矩形包含:
// 矩形2(C,D)是否在1(A,B)内
bool r2inr1(double A, double B, double C, double D)
{
double X, Y, L, K, DMax;
if (A < B)
{
double tmp = A;
A = B;
B = tmp;
}
if (C < D)
{
double tmp = C;
C = D;
D = tmp;
}
if (A > C && B > D) // trivial case
return true;
else if (D >= B)
return false;
else
{
X = sqrt(A * A + B * B); // outer rectangle's diagonal
Y = sqrt(C * C + D * D); // inner rectangle's diagonal
if (Y < B) // check for marginal conditions
return true; // the inner rectangle can freely rotate inside
else if (Y > X)
return false;
else
{
L = (B - sqrt(Y * Y - A * A)) / 2;
K = (A - sqrt(Y * Y - B * B)) / 2;
DMax = sqrt(L * L + K * K);
if (D >= DMax)
return false;
else
return true;
}
}
}
//两圆交点:
// 两圆已经相交(相切)
void c2point(POINT p1, double r1, POINT p2, double r2, POINT &rp1, POINT &rp2)
{
double a, b, r;
a = p2.x - p1.x;
b = p2.y - p1.y;
r = (a * a + b * b + r1 * r1 - r2 * r2) / 2;
if (a == 0 && b != 0)
{
rp1.y = rp2.y = r / b;
rp1.x = sqrt(r1 * r1 - rp1.y * rp1.y);
rp2.x = -rp1.x;
}
else if (a != 0 && b == 0)
{
rp1.x = rp2.x = r / a;
rp1.y = sqrt(r1 * r1 - rp1.x * rp2.x);
rp2.y = -rp1.y;
}
else if (a != 0 && b != 0)
{
double delta;
delta = b * b * r * r - (a * a + b * b) * (r * r - r1 * r1 * a * a);
rp1.y = (b * r + sqrt(delta)) / (a * a + b * b);
rp2.y = (b * r - sqrt(delta)) / (a * a + b * b);
rp1.x = (r - b * rp1.y) / a;
rp2.x = (r - b * rp2.y) / a;
}
rp1.x += p1.x;
rp1.y += p1.y;
rp2.x += p1.x;
rp2.y += p1.y;
}
//两圆公共面积:
// 必须保证相交
double c2area(POINT p1, double r1, POINT p2, double r2)
{
POINT rp1, rp2;
c2point(p1, r1, p2, r2, rp1, rp2);
if (r1 > r2) //保证r2>r1
{
swap(p1, p2);
swap(r1, r2);
}
double a, b, rr;
a = p1.x - p2.x;
b = p1.y - p2.y;
rr = sqrt(a * a + b * b);
double dx1, dy1, dx2, dy2;
double sita1, sita2;
dx1 = rp1.x - p1.x;
dy1 = rp1.y - p1.y;
dx2 = rp2.x - p1.x;
dy2 = rp2.y - p1.y;
sita1 = acos((dx1 * dx2 + dy1 * dy2) / r1 / r1);
dx1 = rp1.x - p2.x;
dy1 = rp1.y - p2.y;
dx2 = rp2.x - p2.x;
dy2 = rp2.y - p2.y;
sita2 = acos((dx1 * dx2 + dy1 * dy2) / r2 / r2);
double s = 0;
if (rr < r2) //相交弧为优弧
s = r1 * r1 * (PI - sita1 / 2 + sin(sita1) / 2) + r2 * r2 * (sita2 - sin(sita2)) / 2;
else//相交弧为劣弧
s = (r1 * r1 * (sita1 - sin(sita1)) + r2 * r2 * (sita2 - sin(sita2))) / 2;
return s;
}
//圆和直线关系:
//0----相离 1----相切 2----相交
int clpoint(POINT p, double r, double a, double b, double c, POINT &rp1, POINT &rp2)
{
int res = 0;
c = c + a * p.x + b * p.y;
double tmp;
if (a == 0 && b != 0)
{
tmp = -c / b;
if (r * r < tmp * tmp)
res = 0;
else if (r * r == tmp * tmp)
{
res = 1;
rp1.y = tmp;
rp1.x = 0;
}
else
{
res = 2;
rp1.y = rp2.y = tmp;
rp1.x = sqrt(r * r - tmp * tmp);
rp2.x = -rp1.x;
}
}
else if (a != 0 && b == 0)
{
tmp = -c / a;
if (r * r < tmp * tmp)
res = 0;
else if (r * r == tmp * tmp)
{
res = 1;
rp1.x = tmp;
rp1.y = 0;
}
else
{
res = 2;
rp1.x = rp2.x = tmp;
rp1.y = sqrt(r * r - tmp * tmp);
rp2.y = -rp1.y;
}
}
else if (a != 0 && b != 0)
{
double delta;
delta = b * b * c * c - (a * a + b * b) * (c * c - a * a * r * r);
if (delta < 0)
res = 0;
else if (delta == 0)
{
res = 1;
rp1.y = -b * c / (a * a + b * b);
rp1.x = (-c - b * rp1.y) / a;
}
else
{
res = 2;
rp1.y = (-b * c + sqrt(delta)) / (a * a + b * b);
rp2.y = (-b * c - sqrt(delta)) / (a * a + b * b);
rp1.x = (-c - b * rp1.y) / a;
rp2.x = (-c - b * rp2.y) / a;
}
}
rp1.x += p.x;
rp1.y += p.y;
rp2.x += p.x;
rp2.y += p.y;
return res;
}
//内切圆:
void incircle(POINT p1, POINT p2, POINT p3, POINT &rp, double &r)
{
double dx31, dy31, dx21, dy21, d31, d21, a1, b1, c1;
dx31 = p3.x - p1.x;
dy31 = p3.y - p1.y;
dx21 = p2.x - p1.x;
dy21 = p2.y - p1.y;
d31 = sqrt(dx31 * dx31 + dy31 * dy31);
d21 = sqrt(dx21 * dx21 + dy21 * dy21);
a1 = dx31 * d21 - dx21 * d31;
b1 = dy31 * d21 - dy21 * d31;
c1 = a1 * p1.x + b1 * p1.y;
double dx32, dy32, dx12, dy12, d32, d12, a2, b2, c2;
dx32 = p3.x - p2.x;
dy32 = p3.y - p2.y;
dx12 = -dx21;
dy12 = -dy21;
d32 = sqrt(dx32 * dx32 + dy32 * dy32);
d12 = d21;
a2 = dx12 * d32 - dx32 * d12;
b2 = dy12 * d32 - dy32 * d12;
c2 = a2 * p2.x + b2 * p2.y;
rp.x = (c1 * b2 - c2 * b1) / (a1 * b2 - a2 * b1);
rp.y = (c2 * a1 - c1 * a2) / (a1 * b2 - a2 * b1);
r = fabs(dy21 * rp.x - dx21 * rp.y + dx21 * p1.y - dy21 * p1.x) / d21;
}
//求切点:
// p---圆心坐标, r---圆半径, sp---圆外一点, rp1,rp2---切点坐标
void cutpoint(POINT p, double r, POINT sp, POINT &rp1, POINT &rp2)
{
POINT p2;
p2.x = (p.x + sp.x) / 2;
p2.y = (p.y + sp.y) / 2;
double dx2, dy2, r2;
dx2 = p2.x - p.x;
dy2 = p2.y - p.y;
r2 = sqrt(dx2 * dx2 + dy2 * dy2);
c2point(p, r, p2, r2, rp1, rp2);
}
//线段的左右旋:
/* l2在l1的左/右方向(l1为基准线)
返回 0 : 重合;
返回 1 : 右旋;
返回 –1 : 左旋;
*/
int rotat(LINESEG l1, LINESEG l2)
{
double dx1, dx2, dy1, dy2;
dx1 = l1.s.x - l1.e.x;
dy1 = l1.s.y - l1.e.y;
dx2 = l2.s.x - l2.e.x;
dy2 = l2.s.y - l2.e.y;
double d;
d = dx1 * dy2 - dx2 * dy1;
if (d == 0)
return 0;
else if (d > 0)
return -1;
else
return 1;
}
/*
公式:
球坐标公式:
直角坐标为 P(x, y, z) 时,对应的球坐标是(rsinφcosθ, rsinφsinθ, rcosφ),其中φ是向量OP与Z轴的夹角,范围[0,π];是OP在XOY面上的投影到X轴的旋角,范围[0,2π]
直线的一般方程转化成向量方程:
ax+by+c=0
x-x0 y-y0
------ = ------- // (x0,y0)为直线上一点,m,n为向量
m n
转换关系:
a=n;b=-m;c=m·y0-n·x0;
m=-b; n=a;
三点平面方程:
三点为P1,P2,P3
设向量 M1=P2-P1; M2=P3-P1;
平面法向量: M=M1 x M2 ()
平面方程: M.i(x-P1.x)+M.j(y-P1.y)+M.k(z-P1.z)=0
*/

9.其他

Java大数以及加速读入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.math.*;
import java.util.*;
import java.io.*;
public class Main {
public static void main(String args[]) throws IOException {
StreamTokenizer miao = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
miao.nextToken();
int TTT, zhizhizhi = 1;
TTT = (int)miao.nval;
while (TTT-- != 0) {
miao.nextToken();
long x = (long)miao.nval;
BigInteger a, b;
a = BigInteger.valueOf(x);
b = a.multiply(a);
b = b.multiply(BigInteger.valueOf(8));
a = a.multiply(BigInteger.valueOf(7));
b = b.subtract(a);
b = b.add(BigInteger.valueOf(1));
out.println("Case #" + zhizhizhi++ + ": " + b);
out.flush();
}
out.close();
}
}


文章目录
  1. 1. 遗产 =。=
  2. 2. 0.头文件 && 读入挂
  3. 3. 1.基础算法
    1. 3.1. 离散化
      1. 3.1.1. 使用方法
      2. 3.1.2. 模版版本一
      3. 3.1.3. 版本二
    2. 3.2. 归并排序
      1. 3.2.1. 使用方法
      2. 3.2.2. Tipss
    3. 3.3. 二分计算
    4. 3.4. 三分计算
      1. 3.4.1. 使用方法
      2. 3.4.2. Tips
  4. 4. 2.博弈
    1. 4.1. bash博弈
    2. 4.2. SG函数
    3. 4.3. Wythoff博弈
      1. 4.3.1. 使用方法
  5. 5. 3.数学
    1. 5.1. 奇怪的数学公式系列
    2. 5.2. 组合数学
      1. 5.2.1. 公式
      2. 5.2.2. 特殊的数列
      3. 5.2.3. 斯特林数stirling
      4. 5.2.4. 公式:
      5. 5.2.5. 第二类 stirling数
      6. 5.2.6. 公式:
      7. 5.2.7. 错排问题
    3. 5.3. 中国剩余定理
      1. 5.3.1. 使用方法
      2. 5.3.2. Tips
    4. 5.4. 单变元模线性方程
      1. 5.4.1. 说明
      2. 5.4.2. 使用方法
    5. 5.5. 矩阵快速幂
    6. 5.6. 自适应Simpson求积分
    7. 5.7. FFT
    8. 5.8. NTT
    9. 5.9. 高斯消元
    10. 5.10. 莫比乌斯反演
  6. 6. 4.字符串
    1. 6.1. KMP
    2. 6.2. AC自动机
    3. 6.3. Manacher
    4. 6.4. Trie树
    5. 6.5. 使用方法
    6. 6.6. 字符串哈希
    7. 6.7. 后缀数组
    8. 6.8. 字符串最小表示法
  7. 7. 5.数据结构
    1. 7.1. 并查集
    2. 7.2. splay
    3. 7.3. DLX 覆盖问题
    4. 7.4. 主席树
    5. 7.5. LCA
    6. 7.6. 划分树
    7. 7.7. kd tree insert版本
    8. 7.8. kd tree no insert版本
    9. 7.9. LCT
    10. 7.10. 树状数组
    11. 7.11. 线段树完全版
    12. 7.12. 莫队
    13. 7.13. 树链剖分
    14. 7.14. 使用方法
      1. 7.14.1. 边权型
      2. 7.14.2. 区间更新
        1. 7.14.2.1. 点更新
        2. 7.14.2.2. 边更新
    15. 7.15. Treap
  8. 8. 6.动态规划
    1. 8.1. 背包
    2. 8.2. 数位DP
  9. 9. 7.图论
    1. 9.1. Floyed
    2. 9.2. SPFA
      1. 9.2.1. 最短路不用SPFA的都是异教徒!在玄学复杂度面前,只要你相信,它就是O(1)。
    3. 9.3. 次短路与第K短路
    4. 9.4. Tarjan无向图最小权值割边
    5. 9.5. 二分图匹配以及交叉染色
    6. 9.6. 拓扑排序
      1. 9.6.1. BFS说明
      2. 9.6.2. 使用方法
      3. 9.6.3. DFS说明
      4. 9.6.4. 使用方法
    7. 9.7. 无向图的割顶和割桥
      1. 9.7.1. 应用
      2. 9.7.2. 例题:
    8. 9.8. 双连通分量
      1. 9.8.1. Tips
    9. 9.9. 强连通分量
    10. 9.10. 最小生成树
    11. 9.11. 最小树形图
      1. 9.11.1. 使用方法
    12. 9.12. 次小生成树
      1. 9.12.1. 说明
      2. 9.12.2. 使用方法
      3. 9.12.3. Tips
    13. 9.13. 网络流:ISAP
    14. 9.14. 全局最小割
    15. 9.15. 最小费用最大流
      1. 9.15.1. 使用方法
      2. 9.15.2. Tips
  10. 10. 网络流上下界的问题
    1. 10.0.1. 无源汇点上下界可行流
    2. 10.0.2. 有源点汇点的上下界最大流
    3. 10.0.3. 有源汇点上下界最小流:
  11. 10.1. 2-SAT
  12. 10.2. 曼哈顿距离MST
  • 11. 8.计算几何
    1. 11.1. 目录
  • 12. 9.其他
    1. 12.1. Java大数以及加速读入