P's Blog

  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Search

CF-1208

Posted on 2019-08-27 | Edited on 2019-09-20 | In cf | Views:

C. Magic Grid

题意

给一个 $n(n\%4=0)$ ,将 $0\sim n^2-1$ 填入 $n\times n$ 的矩阵里,使得每行每列的异或和相同。

Read more »

2019HDU多校-Day8

Posted on 2019-08-20 | Edited on 2019-09-20 | Views:

F. Acesrc and Travel

题意

给一颗树,每个点有两个权值 $a_i,b_i$ ,两个人博弈,交替选择节点,A 获得 $a_i$ ,B 获得 $b_i$ ,每次只能选取与上一个人相邻的节点。

Read more »

C++

Posted on 2019-08-17 | Edited on 2019-09-26 | In 模板 | Views:

C++

数据范围

int $ -2.14\times10^9 \sim 2.14\times10^9$

unsigned int $0\sim4.29 \times10^ 9$

long long $-9.22\times10^{18}\sim9.22 \times10^ {18}$

unsigned long long $0\sim1.84 \times10^ {19}$

float $-3.40\times10^{38}\sim3.40 \times10^ {38}$ 6~7

double $-1.79\times10^{308}\sim1.79 \times10^ {308}$ 15~16

long double $-1.2\times10^{4932}\sim1.2\times10^{4932}$ 18-19

易错点

$memset$ 只管后面的两个字符,然后前面的直接就复制过去, 所以 $memset( a,0x3f,sizeof(a) )$ 效果等同于 $memset( a,0x3f3f3f3f,sizeof(a) )$

$strlen()$ 的时间复杂度为 $\mathcal{O}(n)$

$s.size()$ 的返回值为 $unsigned long$

动态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int *a = new int[50];
delete [] a;

int (*a)[50] = new int[100][50];

int **p= new int*[size];//定义指针数组
for(int i=0;i<size;i++){
p[i]=new int[Column];
}
for(int i=0;i<size;i++){
delete [] p[i];
}

char *p = (char *)malloc(100);
free(p);

IO

scanf

1
2
3
4
%f float
%lf double
%Lf long double
sscanf("3.42","%lf",&b); //将字符串转换为数值double

printf

1
2
3
4
5
6
7
- 左对齐
0 右对齐时,用0填充左边未使用的列
+ 当一个数为正数时,前面加上一个+号
' ' 当一个数为正数时,前面加上一个空格
printf("a = %08.3Lf",a); //右对齐,开头补零,字符宽度8位,精度3位,以long double型输出。
printf("%*d",width,num);
sprintf(buf,"%.2f",a); //将double型数值转换为字符串

整行读入

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
//scanf()读入char[]
char str[1024];
scanf("%[^\n]",&str);
getchar();

//getchar()读入char[]
char str[1024];
int i=0;
while((str[i]=getchar())!='\n')
i++;
getchar();

//gets()读入char[]
char str[1024];
gets(str);

//getline()
string str;
getline(cin,str);//读入string

char str2[1024];
cin.getline(str2,1024);//读入char数组

//get()读入char[]
char str3[1024];
cin.get(str3,1024);//读入char数组
cin.get(str,1024).get();

文件输入输出

1
2
3
4
5
6
7
8
freopen("/Users/perpeternal/Downloads/test.in", "r", stdin);	
freopen("/Users/perpeternal/Downloads/test.out", "w", stdout);

FILE *in = fopen("/Users/perpeternal/Downloads/test.in","r");
FILE *out = fopen("/Users/perpeternal/Downloads/test.out", "w");
fscanf(in,"%d",&n);
fprintf(out,"%d",n);
fclose(in);fclose(out);

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
void out(int count,...){
va_list ap; //声明一个va_list变量
va_start(ap, count); //第二个参数表示形参的个数
for (int i = 0; i < count; i++) {printf("%d ",va_arg(ap, int));}
va_end(ap); //用于清理
}
out(4,1,2,3,4);

void out(initializer_list<int> list) {
for (auto ptr = list.begin(); ptr != list.end(); ptr++) printf("%d ",*ptr);
}
out({1,2,3,4});

手动优化

手动加栈

1
#pragma comment(linker, “/STACK:1024000000,1024000000”)

优化

-O0 表示无优化状态

-O1 表示对代码进行了优化

-O2 表示减小目标文件大小

-O3 表示减小代码段及栈空间的大小

全局:

1
#pragma GCC optimize (2)

部分:

1
2
3
4
#pragma GCC push_options 
#pragma GCC optimize (“O0”)
...
#pragma GCC pop_options

随机数

1
2
3
4
//这是两个预定义类(类型),定义的变量可以用重载好的 () 运算符获取随机数
mt19937 gen(time(0)); //生成 [0, 2^32-1]
mt19937_64 gen(time(0)); //生成 [0, 2^64-1]
gen(); //生成随机数

assert

1
assert(0); //终止程序,报re

inf&nan

1
2
3
4
int isfinite(x); 判断x是否有限,是返回1,其它返回0;
int isnormal(x); 判断x是否为一个数(非inf或nan),是返回1,其它返回0;
int isnan(x); 当x时nan返回1,其它返回0;
int isinf(x); 当x是正无穷是返回1,当x是负无穷时返回-1,其它返回0。有些编译器不区分。

bitset

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
bitset<4> bitset1;  //无参构造,长度为4,默认每一位为0

bitset<8> bitset2(12);  //长度为8,二进制保存,前面用0补充
bitset<2> bitset2(12);  //长度为3,二进制保存,取12二进制的后两位

string s = "100101";
bitset<10> bitset3(s);  //长度为10,前面用0补充

char s2[] = "10101";
bitset<13> bitset4(s2);  //长度为13,前面用0补充
bitset<2> bitset5(s2) //取s2前两位

//位运算
bitset<4> foo (string("1001"));
bitset<4> bar (string("0011"));

cout << (foo^=bar) << endl; // 1010 (foo对bar按位异或后赋值给foo)
cout << (foo&=bar) << endl; // 0010 (按位与后赋值给foo)
cout << (foo|=bar) << endl; // 0011 (按位或后赋值给foo)

cout << (foo<<=2) << endl; // 1100 (左移2位,低位补0,有自身赋值)
cout << (foo>>=1) << endl; // 0110 (右移1位,高位补0,有自身赋值)

cout << (~bar) << endl; // 1100 (按位取反)
cout << (bar<<1) << endl; // 0110 (左移,不赋值)
cout << (bar>>1) << endl; // 0001 (右移,不赋值)

cout << (foo==bar) << endl; // false (0110==0011为false)
cout << (foo!=bar) << endl; // true (0110!=0011为true)

cout << (foo&bar) << endl; // 0010 (按位与,不赋值)
cout << (foo|bar) << endl; // 0111 (按位或,不赋值)
cout << (foo^bar) << endl; // 0101 (按位异或,不赋值)

bitset<8> foo ("10011011");
cout << foo.count() << endl;  //5  (count函数用来求bitset中1的位数,foo中共有5个1
cout << foo.size() << endl;   //8  (size函数用来求bitset的大小,一共有8位

cout << foo.test(0) << endl;  //true  (test函数用来查下标处的元素是0还是1,并返回false或true,此处foo[0]为1,返回true
cout << foo.test(2) << endl;  //false  (同理,foo[2]为0,返回false

cout << foo.any() << endl;  //true  (any函数检查bitset中是否有1
cout << foo.none() << endl;  //false  (none函数检查bitset中是否没有1
cout << foo.all() << endl;  //false  (all函数检查bitset中是全部为1
bitset<8> foo ("10011011");

cout << foo.flip(2) << endl;  //10011111  (flip函数传参数时,用于将参数位取反,本行代码将foo下标2处"反转",即0变1,1变0
cout << foo.flip() << endl;   //01100000  (flip函数不指定参数时,将bitset每一位全部取反

cout << foo.set() << endl;    //11111111  (set函数不指定参数时,将bitset的每一位全部置为1
cout << foo.set(3,0) << endl;  //11110111  (set函数指定两位参数时,将第一参数位的元素置为第二参数的值,本行对foo的操作相当于foo[3]=0
cout << foo.set(3) << endl;   //11111111  (set函数只有一个参数时,将参数下标处置为1

cout << foo.reset(4) << endl;  //11101111  (reset函数传一个参数时将参数下标处置为0
cout << foo.reset() << endl;   //00000000  (reset函数不传参数时将bitset的每一位全部置为0
bitset<8> foo ("10011011");

string s = foo.to_string();  //将bitset转换成string类型
unsigned long a = foo.to_ulong();  //将bitset转换成unsigned long类型
unsigned long long b = foo.to_ullong();  //将bitset转换成unsigned long long类型

cout << s << endl;  //10011011
cout << a << endl;  //155
cout << b << endl;  //155

priority_queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在结构体外重载结构体小于运算符
struct Time{
int start, end;
};
bool operator <(const Time& a,const Time& b){
return a.start > b.start;
}//只能重载小于,这里以大于重载小于是因为默认情况下,优先队列是以大的作为队首,这样一反,就可以再默认情况下使得小的作为队首

//在结构体中重载小于运算符
struct Time{
int start, end;
bool operator < (const Time& t)const{
return start > t.start;
}
};

priority_queue<Time> pq;

vector

1
2
3
4
5
6
7
8
9
vector<int>v1;
vector<int>v1(v2);
vector<int>v1=v2;
vector<int> v1 = {1,2,3.0,4,5,6,7};
vector<int> v3(v1.begin()+2,v2.end()-1);
vector<int> v4(n);//初始化vector大小为n
vector<int> v5(n,x);//初始化vector为n个x

v.resize(n,x)//重置vector为n个x

set & map

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct node{  
int x,y;
bool operator < (const node& t)const{
return x > t.x;
}
};
set<node> SET;

struct cmp{
bool operator()(const int &k1,const int &k2)const{
return k1>k2;
}
};
set<int,cmp>s;

map

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
typedef struct UrlKey
{
uint64_t dwBussID;
uint64_t dwVersion;
uint64_t dwHashUrl;
}UrlKey;

//自定义map的value
typedef struct UrlValue
{
string strUrl;
}UrlValue;
struct cmp_key
{
bool operator()(const UrlKey &k1, const UrlKey &k2)const
{
if(k1.dwBussID != k2.dwBussID)
{
return k1.dwBussID < k2.dwBussID;
}

if(k1.dwVersion != k2.dwVersion)
{
return k1.dwVersion < k2.dwVersion;
}
if(k1.dwHashUrl != k2.dwHashUrl)
{
return k1.dwHashUrl < k2.dwHashUrl;
}
         return false;
}
};

map<UrlKey, UrlValue, cmp_key> UrlMap;

unordered_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct node{
string s;
int step;
node(string _s):s(_s),step(0){}
bool operator == (const node y)const{
return s==y.s;
}
};
struct Hash{
size_t operator () (const node y)const{
return hash<string>()(y.s);
}
};
unordered_set<node,Hash>us;

multiset

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 <iostream>
#include <set>
using namespace std;

int main()
{
/*type of the collection:
*-duplicates allowed
*-elements are integral values
*-descending order
*/
typedef multiset<int,greater<int> > IntSet;

IntSet coll1, // empty multiset container

//insert elements in random order
coll1.insert(4);
coll1.insert(3);
coll1.insert(5);
coll1.insert(l);
coll1.insert(6);
coll1.insert(2);
coll1.insert(5);

//iterate over all elements and print them
IntSet::iterator pos;
for (pos = coll1.begin(); pos != coll1.end(); ++pos) {
cout << *pos << ' ';
}
cout << endl;

//insert 4 again and process return value
IntSet::iterator ipos = coll1.insert(4);
cout << "4 inserted as element "
<< distance (coll1.begin(),ipos) + 1
<< endl;

//assign elements to another multiset with ascending order
multiset<int> coll2(coll1.begin(),coll1.end());

//print all elements of the copy
copy (coll2.begin(), coll2.end(),
ostream_iterator<int>(cout," "));
cout << endl;

//remove all elements up to element with value 3
coll2.erase (coll2.begin(), coll2.find(3));

//remove all elements with value 5
int num;
num = coll2.erase (5);
cout << num << " element(s) removed" << endl;

//print all elements
copy (coll2.begin(), coll2.end(),
ostream_iterator<int>(cout," "));
cout << endl;
}

string

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
string s(str) //拷贝构造函数 生成str的复制品
string s(str,stridx) //将字符串str内“始于位置stridx”的部分当作字符串的初值
string s(str,stridx,strlen) //将字符串str内“始于stridx且长度顶多strlen”的部分作为字符串的初值
string s(cstr) //将C字符串作为s的初值
string s(chars,chars_len) //将chars字符串前chars_len个字符作为字符串s的初值。

s.assign(str); //不说
s.assign(str,1,3);//如果str是”iamangel” 就是把”ama”赋给字符串
s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s
s.assign(“gaint”); //不说
s.assign(“nico”,5);//把’n’ ‘I’ ‘c’ ‘o’ ‘\0’赋给字符串
s.assign(5,’x’);//把五个x赋给字符串

s.append(str);
s.append(str,1,3);//不解释了 同前面的函数参数assign的解释
s.append(str,2,string::npos)//不解释了
s.append(“my name is jiayp”);
s.append(“nico”,5);
s.append(5,’x’);
s.push_back(‘a’);//这个函数只能增加单个字符 对STL熟悉的理解起来很简单

s.insert(0,”my name”);
s.insert(1,str);

s.replace(1,2,”nternationalizatio”);//从索引1开始的2个替换成后面的C_string

s.erase(13);//从索引13开始往后全删除
s.erase(7,5);//从索引7开始往后删5个

s.substr();//返回s的全部内容
s.substr(11);//从索引11往后的子串
s.substr(5,6);//从索引5开始6个字符

返回符合搜索条件的字符区间内的第一个字符的索引,没找到目标就返回npos
第一个参数是被搜寻的对象。第二个参数(可有可无)指出string内的搜寻起点索引,第三个参数(可有可无)指出搜寻的字符个数。
find()
rfind() //从后向前
find_first_of()
find_last_of()
find_first_not_of()返回在字符串中首次出现的不匹配str中的任何一个字符的首字符索引, 从index开始搜索, 如果全部匹配则返回string::npos。
find_last_not_of()

s.c_str() 返回 char*

strcmp(const char *s1,const char * s2)//若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。

编译过程

compile

test.cpp - 预处理(Pre-processing) - test.i - 编译(Compiling) - test.s - 汇编(Assembling) - test.o - 链接(Linking) - test

Pre-processing

-E 选项使用g++/gcc将源代码预处理后不执行其他动作。
下面的命令将test.cpp预处理,并在标准输出中显示:

1
g++ -E test.cpp

后面加上 -o 选项表示将源代码预处理后输出在指定文件中,比如test.i:

1
g++ -E test.cpp -o test.i

Compiling

-S 选项使用g++/gcc将预处理后的文件编译,翻译成汇编代码。只编译不汇编
下面的命令将会编译test.i文件,并自动在当前文件夹生成test.s文件

1
g++ -S test.i

若要指定其他输出名,则需 -o 指定,比如生成名为xxx.s的汇编代码文件

1
g++ -S test.i -o xxx.s

Assembling

-c 选项将编译生成的test.s文件生成二进制目标代码
下面的命令将在当前文件夹自动生成test.o的二进制目标代码文件

1
g++ -c test.s

如果要指定输出文件名,则需 -o 指定,比如生成xxx.o的二进制目标代码文件

1
g++ -c test.s -o xxx.o

Linking

链接阶段是将相关的目标文件链接起来,形成一个整体,生成可执行文件
无选项链接
下面的命令会把二进制目标文件test.o所需的相关文件链接成一个整体,并在当前文件夹自动生成一个名为a.out的可执行文件

1
g++ test.o

如果要执行这个可执行文件,需要输入命令

1
./a.out

当然也可以指定生成的可执行文件的文件名

1
g++ test.o -o test.exe

单个源文件直接生成可执行文件

当然g++/gcc也可以直接把源代码直接生成可执行文件
下面的命令将test.cpp直接在当前文件夹生成a.out可执行文件,若要指定文件名,可使用 -o 选项

1
2
g++ test.cpp
g++ test.cpp -o test.exe

多个源文件直接生成可执行文件

也可以将多个源代码编译链接成一个可执行文件
下面的命令将test.cpp直接在当前文件夹生成a.out可执行文件,若要指定文件名,可使用 -o 选项

1
2
g++ test1.cpp test2.cpp 
g++ test1.cpp test2.cpp -o test.exe

使用C++11标准编译

如果要使用C++11版本特性,则需要使用 -std=c++11 选项

1
g++ -std=c++11 test.cpp -o test.exe

Combination & Sequence

Posted on 2019-08-17 | Edited on 2019-09-27 | In 模板 | Views:

Combination & Sequence

组合数性质

n 个球 m 个盒子

1.球同,盒同,可空

2. 球同,盒同,不可空

先在每个箱子里面放 $1$ 个球, 然后还剩下 $n-m$ 个球了, 就转化成了上一种情况, $dp_1[n-m][m]$

3.球同,盒不同,不可空

一共有 $n-1$ 个空隙, 要插 $m-1$ 个板, $C_{n-1}^{m-1}$

4.球同,盒不同,可空

先给每个盒子一个球, 把问题转化为不能空的情况, 相当于 $n+m$ 个小球放入 $m$ 个盒子且不能空, $C_{n+m-1}^{m-1}$

应用
  • 从 $n$ 个不同的物品中选取 $m$ 个,每种物品可以取多次,问方案数。

    设从每种物品中取 $a_i$ 个,显然 $\sum a_i=m$ ,所以就相当于将 $m$ 个相同的球放入 $n$ 个不同的箱子里。

5.球不同,盒同,不可空

第二类斯特林数
$dp_2[n][m]$ 代表 $n$ 个小球放入 $m$ 个不同的盒子且不能空的方法

6.球不同,盒同,可空

7.球不同,盒不同,不可空

8.球不同,盒不同,可空

Burnside引理

对于一个置换 $f$, 若一个染色方案 $s$ 经过置换后不变,称 $s$ 为 $f$ 的不动点。将 $f$ 的不动点数目记为 $c(f)$,则可以证明等价类数目为所有的 $c(f)$ 平均值。

例子: 一正方形分成 $4$ 格,$2$ 着色,有多少种方案?

Image

对于这 $16$ 种方案可以归一下类:

  • 不动:

  • 转 $90$ 度 :$a2=(1)(2)(6 ,5, 4, 3)(10, 9 ,8 ,7)(11, 12)(16 ,15 ,14 ,13) $

  • 转 $180$ 度:$a3=(1)(2)(3 ,5)(4, 6)(7, 9)(8, 10)(11)(12) (13, 15)(14, 16) $

  • 转 $270$ 度:$a4=(1)(2)(3, 4, 5 ,6)(7 ,8, 9, 10) (11, 12)(13, 14 ,15 ,16)$

$(a,b,c)$ 表示可以通过 $a,b,c$ 旋转得到

所以共有 $\frac {16+2+4+2}4=6$ 种方案.

Polya定理

假设一个置换有 $k$ 个循环,易知每个循环对应的所有位置颜色需一致,而任意两个循环之间选什么颜色互不影响。因此,如果有 $m$ 种可选颜色,则该置换对应的不动点个数为 $m^k$。用其替换 $burnside$ 引理中的 $c(f)$,即 $c(f)=m^k$ 得到等价类数目为:

  • 不动:$a1=(1)(2)(3)(4) $

  • 旋转 $90$ 度:$a2=(1, 2, 3, 4)$

  • 旋转 $180$ 度 :$a3=(1 ,3)(2 ,4)$

  • 旋转 $270$ 度:$a4=(1, 4, 3, 2)$

由$Polya$ 定理得,共有 $\large\frac {2^4+2^1+2^2+2^1}4=6$ 种方案。

容斥原理

鸽巢原理

定义

如果要把 $n$ 个物品放入 $m$ 个容器中,至少有一个容器中的物品的数量 $\large\ge\lceil\frac n m\rceil$

例子

  • 给 $n$ 个数,找出一些数,其和为 $n$ 的倍数

    有 $n$ 个前缀和,如果没有等于 $0$ 的,那么剩余的数的取值为 $1\sim {n-1}$ ,必定有一个数使用了两次

Ramsey定理(广义鸽巢定理)

定义

  • 通俗解释

    在 $6$ 个或更多人中,或者有 $3$ 个人,他们中的每两个人都互相认识;或者有 $3$ 个人,他们中的每两个人都彼此不认识。记为 $K_6\to K_3,K_3$

  • 一般解释

    对于一个给定的两个整数 $n,m\ge2$,则一定存在一个最小整数 $r$,使得用两种颜色(红色或蓝色)无论给 $K_r$ 的每条边如何染色,总能找到一个红色的 $K_m$ 或者蓝色的 $K_n$ 。显然,当 $p\ge r$ 的时候,$K_p$ 亦满足这个性质。

性质

  • $r(m,n)=r(n,m)$
  • $r(2,n)=n$
  • $r(m,2)=m$

斐波那契数列

  • $f(i)=f(i-1)+f(i-2),f(1)=f(2)=1$

  • 与集合子集

    斐波那契数列的第 $n+2$ 项同时也代表了集合 {1,2,…,n} 中所有不包含相邻正整数的子集个数。

  • 如果 $f(k)$ 能被 $x$ 整除,则 $f(k*i)$ 都可以被 $x$ 整除。

  • 求递推公式 $a(1)=1,a(n+1)=1+\frac 1 {a(n)}$ 的通项公式 $a(n)= \frac {fib(n+1)}{fib(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
    struct matrix {
    ll a[2][2];
    matrix(){
    mem0(a);
    }
    };
    matrix mat_mul(matrix x, matrix y) {
    matrix res;
    int (int i = 0; i < 2; i++)
    int (int j = 0; j < 2; j++)
    int (int k = 0; k < 2; k++)
    res.a[i][j] = (res.a[i][j] + x.a[i][k] * y.a[k][j]) % mod;
    return res;
    }
    int mat_pow(ll n) {
    if (n <= 2) return 1;
    n -= 2;
    matrix c, res;
    c.a[0][0] = c.a[0][1] = c.a[1][0] = 1;
    int (int i = 0; i < 2; i++) res.a[i][i] = 1;
    while (n) {
    if (n % 2) res = mat_mul(res, c);
    c = mat_mul(c, c);
    n /= 2;
    }
    return (res.a[0][0] + res.a[0][1]) % mod;
    }

卢卡斯数列

$F_0=2,F_1=1,F_n=F_{n-1}+F_{n-2}$

通项为

法里数列

定义

$n$ 阶的法里数列是 $0$ 和 $1$ 之间最简分数(分母不大于 n )的数列,由小至大排列,每个分数的分母不大于 $n$。

构造

已知两个法里数 $\frac ab$和 $\frac c d$,那么两者之间的法里数就是$\frac {a+c} {b+d}$

性质

  • $F_n$ 包含了 $F_{n-1}$ 的全部项

  • $|F_n|=|F_{n-1}+\phi(n)|$

  • $|F_n|\sim \frac {3n^2} {\pi^2}$

  • 若 $\frac a b,\frac c d(\frac a b<\frac c d)$ 是法里数列的邻项,则它们之差为 $\frac 1 {bd}$

  • F1 = {0⁄1, 1⁄1}

    F2 = {0⁄1, 1⁄2, 1⁄1}

    F3 = {0⁄1, 1⁄3, 1⁄2, 2⁄3, 1⁄1}

    F4 = {0⁄1, 1⁄4, 1⁄3, 1⁄2, 2⁄3, 3⁄4, 1⁄1}

    F5 = {0⁄1, 1⁄5, 1⁄4, 1⁄3, 2⁄5, 1⁄2, 3⁄5, 2⁄3, 3⁄4, 4⁄5, 1⁄1}

    F6 = {0⁄1, 1⁄6, 1⁄5, 1⁄4, 1⁄3, 2⁄5, 1⁄2, 3⁄5, 2⁄3, 3⁄4, 4⁄5, 5⁄6, 1⁄1}

    F7 = {0⁄1, 1⁄7, 1⁄6, 1⁄5, 1⁄4, 2⁄7, 1⁄3, 2⁄5, 3⁄7, 1⁄2, 4⁄7, 3⁄5, 2⁄3, 5⁄7, 3⁄4, 4⁄5, 5⁄6, 6⁄7, 1⁄1}

    F8 = {0⁄1, 1⁄8, 1⁄7, 1⁄6, 1⁄5, 1⁄4, 2⁄7, 1⁄3, 3⁄8, 2⁄5, 3⁄7, 1⁄2, 4⁄7, 3⁄5, 5⁄8, 2⁄3, 5⁄7, 3⁄4, 4⁄5, 5⁄6, 6⁄7, 7⁄8, 1⁄1}

卡特兰数

例子

  • 进出栈问题

    一个足够大的栈的进栈序列为 $1,2,3,⋯,n$ 时有多少个不同的出栈序列?

    我们可以这样想,假设 $k$ 是最后一个出栈的数。比 $k$ 早进栈且早出栈的有 $k-1$ 个数,一共有 $h(k-1)$ 种方案。比$k$ 晚进栈且早出栈的有 $n-k$ 个数,一共有 $h(n-k)$ 种方案。所以一共有 $h(k-1)\times h(n-k)$ 种方案。显而易见,$k$ 取不同值时,产生的出栈序列是相互独立的,所以结果可以累加。$k$ 的取值范围为 $1$ 至 $n$,所以结果就为$h(n)= h(0)h(n-1)+h(1)h(n-2) + … + h(n-1)h(0)$

  • 有n个结点,问总共能构成几种不同的二叉树。

    我们可以假设,如果采用中序遍历的话,根结点第k个被访问到,则根结点的左子树有k-1个点、根结点的右指数有n-k个点。k的取值范围为1到n。讲到这里就很明显看得出是卡特兰数了。

  • 凸多边形的三角形划分

    一个凸的n边形,用直线连接他的两个顶点使之分成多个三角形,每条直线不能相交,问一共有多少种划分方案。

    选择一个基边,显然这是多边形划分完之后某个三角形的一条边。图中我们假设基边是p1pn,我们就可以用p1、pn和另外一个点假设为pi做一个三角形,并将多边形分成三部分,除了中间的三角形之外,一边是i边形,另一边是n-i+1边形。i的取值范围是2到n-1。所以本题的解$c(n)=c(2)c(n-1)+c(3)c(n-2)+…c(n-1)c(2)$。令t(i)=c(i+2)。则$t(i)=t(0)t(i-1)+t(1)t(i-2)…+t(i-1)t(0)$。很明显,这就是一个卡特兰数了。

  • $n$ 个 $1$ , $n$ 个 $-1$ ,构成的排列中,满足任意一段前缀和都 $\ge0$ 或 $\le0$ 的数量为 $h(n)$,可以将 $1$ 看作入栈,将 $-1$ 看作出栈

    $x$ 个 $1$ , $y(x\ge y)$ 个 $-1$ ,构成的排列中,满足任意一段前缀和都 $\ge0$ 的数量为 $C(x+y,x)-C(x+y,x+1)$,可以将 $1$ 看作入栈,将 $-1$ 看作出栈

贝尔数

  • n个元素的集合的划分方案数

  • 贝尔三角形

    • 第一行第一项是1, $a(1,1)= 1$

    • 对于$n>1$,第 $n$ 行第一项等同第 $n-1$ 行最后一项, $ a(n,1)=a(n-1,n-1)$

    • 对于$m,n>1$,第 $n$ 行第 $m$ 项等于它左边和左上方的两个数之和, $a(n,m)=a(n,m-1)+a(n-1,m-1)$

    • 同余性质 (p是不大于100的素数)

$Stirling$ 数

第一类 $Stirling$ 数

其绝对值是包含 $n$ 个元素的集合分作 $k$ 个环排列的方案数

Screen Shot 2018-08-10 at 12.21.46 PM

无符号 $Stirling$ 数

有符号 $Stirling$ 数

第二类Stirling数

将 $n$ 个不同的元素拆分成 $m$ 个集合的方案数

两类Stirling数之间的关系

施罗德数

从 $(0,0)$ 到 $(n,n)$ 的格路中,只能使用 $(1,0),(0,1),(1,1)$ 三种移动方式,始终位于对角线下方且不越过对角线的路径数, $1, 2, 6, 22, 90, 394, 1806, 8558, 41586, 206098,… (OEIS A006318)$

Image-7800632

多边形数

  • 五边形数

    $\large p(n)=\frac {(3*n^2-n)}2$

    前几个五边形数:1, 5, 12, 22, 35, 51, 70, 92, 117, 145, 176, 210, 247, 287, 330, 376, 425, 477, 532, 590, 651, 715, 782, 852, 925, 1001 ………

  • 广义五边形数:

    n的取值0,1,-1,2,-2,3,-3…….

    前几个广义五边形数:0, 1, 2, 5, 7, 12, 15, 22, 26, 35, 40, 51, 57, 70, 77, 92, 100, 117, 126, 145, 155, 176, 187, 210, 222, 247, 260, 287, 301, 330…..

  • 五边形数测试

  • 中心五边形数

    Image-7800672

  • 中心六边形数(相邻两个广义五边形数之和)

    Image-7800682

  • 费马多边形数定理: 每一个正整数最多可以表示为 n 个 n-边形数 的和

求 $1^k+2^k+3^k+….+n^k=?$

$(n-1)^k=n^k+C(k,1)n^(k-1)(-1)+C(k,2)n^(k-2)(-1)^2+…+C(k,k)*(-1)^k$

$(n-2)^k=[(n-1)-1]^k=(n-1)^k+C(k,1)(n-1)^(k-1)(-1)+C(k,2)(n-1)^(k-2)(-1)^2+…+C(k,k)*(-1)^k$

$(n-3)^k=[(n-2)-1]^k=(n-2)^k+C(k,1)(n-2)^(k-1)(-1)+C(k,2)(n-2)^(k-2)(-1)^2+…+C(k,k)*(-1)^k$

…

$2^k=(3-1)^k=3^k+C(k,1)3^(k-1)(-1)+C(k,2)3^(k-2)(-1)^2+…+C(k,k)*(-1)^k$

$1^k=(2-1)^k=2^k+C(k,1)2^(k-1)(-1)+C(k,2)2^(k-2)(-1)^2+…+C(k,k)*(-1)^k$

这n-1个式子相加,得:

$1^k=n^k+C(k,1)(-1)[2^{k-1}+3^{k-1}+…+n^{k-1}]+C(k,2)(-1)^2[2^{k-2}+3^{k-2}+…+n^{k-1}]+…+(n-1)C(k,k)(-1)^k$

如果令关于k的函数$S(k)=1^k+2^k+…+n^k$

则$1=n^k+C(k,1)(-1)[S(k-1)-1]+C(k,2)(-1)^2[S(k-2)-1]+…+(n-1)*(-1)^k$

由此可以得出S(k-1)关于S(k-2)、S(k-3)、…、S(2)和S(1)的地推公式

已知

$S(1)=1+2+…+n=\frac {n(n+1)} 2 $
$S(2)=1^2+2^2+…+n^2=\frac {n(n+1)(2n+1)}2$
$S(3)=1^3+2^3+…+n^3=S(1)^2$

广义斐波那契数列取模循环节

若递推式为 $f(n)=a\times f(n-1)+b\times f(n-2)$ ,其矩阵关系为

寻找最小的 $n$ 使得

令 $c=a^2+4b$ ,分类讨论

  • 若 $c$ 是 $p$ 的二次剩余, $n$ 为 $p-1$ 的因子
  • 若 $c$ 是 $p$ 的二次非剩余, $n$ 为 $(p-1)(p+1)$ 的因子

斐波那契数列取模循环节

  1. 把n素因子分解,即 $n=p_1^{a_1} p_2^{a_2} · · · *p_k^{a_k}$

  2. 分别计算Fib数模每个$p^m$的循环节长度,假设长度分别是$x_1,x_2,x_3, · · · ,x_k$

  3. 那么Fib模n的循环节长度$L=lcm(x_1,x_2,x_3, · · · ,x_k)$

  4. Fib数模$p^m$的最小循环节长度等于$G(p)*p^{m-1}$,其中$G(p)$表示Fib数模素数p的最小循环节长度
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
//简单算法
//暴力枚举fib[i]%p==0的最小的i,然后记录pos=i+1,设a为fib[i]%p==0的前一位数,即a=fib[i-1]
//那么我们知道fib数列模p的最小循环节长度一定是pos*x,那么也就是说现在要求一个最小的数x,满足a^x = 1 (mod p)
bool isprime[N];
ll prime[N],primeNum;
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
void getprime(){
ll i,j;
memset(isprime,1,sizeof(isprime));
primeNum=0;
int(i=2; i<N; i++)
if(isprime[i]){
prime[primeNum++]=i;
int(j=i*i; j<N; j+=i) isprime[j]=0;
}
}
ll factor[100][2],tol;
void findfac(ll n){
ll x=n,l=(ll)sqrt((double)n);
tol=0;
memset(factor,0,sizeof(factor));
int(int i=0; prime[i]<=l; i++)
if(x%prime[i]==0){
factor[tol][0]=prime[i];
while(x%prime[i]==0) {
factor[tol][1]++;
x/=prime[i];
}
tol++;
}
if(x>1) {
factor[tol][0]=x;
factor[tol++][1]++;
}
}
ll exp_mod(ll a,ll b,ll c)
{
ll ret=1;
while(b)
{
if(b&1) ret=ret*a%c;
b>>=1;
a=a*a%c;
}
return ret;
}
ll getPrimeLoop(ll p){//求一个素数的循环节
ll pos=3,f1=1,f2=1,f3=2%p,k=1e9,l=(ll)sqrt((double)p-1);
while(f3) {//找到第一个值是0的点
f1=f2;
f2=f3;
f3=(f1+f2)%p;
pos++;
}
for(ll i=1; i<=l; i++)
if((p-1)%i==0){
if(exp_mod(f2,(p-1)/i,p)==1) k=min(k,(p-1)/i);
if(exp_mod(f2,i,p)==1) k=min(k,i);
}
return pos*k;
}
ll solve(ll p,ll k){//求一个素数的k次方的循环节
ll ans=getPrimeLoop(p);
for(int i=0; i<k-1; i++) ans*=p;
return ans;
}

//kuangbin版
//如果5是模P的二次剩余,那么循环节的的长度是 P-1 的因子,否则,循环节的长度是 2*P+1 的因子。
//对于小于等于5的素数,我们直接特殊判断,loop(2)=3,loop(3)=8,loop(5)=20。
//那么我们可以先求出所有的因子,然后用矩阵快速幂来一个一个判断,

ll gcd(ll a,ll b){
if(b == 0)return a;
return gcd(b,a%b);
}
ll lcm(ll a,ll b){
return a/gcd(a,b)*b;
}
struct Matrix{
ll mat[2][2];
};
Matrix mul_M(Matrix a,Matrix b,ll mod){
Matrix ret;
for(int i = 0;i < 2;i++)
for(int j = 0;j < 2;j++){
ret.mat[i][j] = 0;
for(int k = 0;k < 2;k++){
ret.mat[i][j] += a.mat[i][k]*b.mat[k][j]%mod;
if(ret.mat[i][j] >= mod)ret.mat[i][j] -= mod;
}
}
return ret;
}
Matrix pow_M(Matrix a,ll n,ll mod){
Matrix ret;
memset(ret.mat,0,sizeof(ret.mat));
for(int i = 0;i < 2;i++)ret.mat[i][i] = 1;
Matrix tmp = a;
while(n){
if(n&1)ret = mul_M(ret,tmp,mod);
tmp = mul_M(tmp,tmp,mod);
n >>= 1;
}
return ret;
}
ll pow_m(ll a,ll n,ll mod){//a^b % mod
ll ret = 1;
ll tmp = a%mod;
while(n){
if(n&1)ret = ret*tmp%mod;
tmp = tmp*tmp%mod;
n >>= 1;
}
return ret;
}
//素数筛选和合数分解
const int MAXN = 100000;
int prime[MAXN+1];
void getPrime(){
memset(prime,0,sizeof(prime));
for(int i = 2;i <= MAXN;i++){
if(!prime[i])prime[++prime[0]] = i;
for(int j = 1;j <= prime[0] && prime[j] <= MAXN/i;j++){
prime[prime[j]*i] = 1;
if(i%prime[j] == 0)break;
}
}
}
ll factor[100][2];
int fatCnt;
int getFactors(ll x){
fatCnt = 0;
ll tmp = x;
int(int i = 1;prime[i] <= tmp/prime[i];i++){
factor[fatCnt][1] = 0;
if(tmp%prime[i] == 0){
factor[fatCnt][0] = prime[i];
while(tmp%prime[i] == 0){
factor[fatCnt][1]++;
tmp /= prime[i];
}
fatCnt++;
}
}
if(tmp != 1){
factor[fatCnt][0] = tmp;
factor[fatCnt++][1] = 1;
}
return fatCnt;
}
int legendre(ll a,ll p){ //勒让德符号
if(pow_m(a,(p-1)>>1,p) == 1)return 1;
else return -1;
}
int f0 = 1;
int f1 = 1;
ll getFib(ll n,ll mod){
if(mod == 1)return 0;
Matrix A;
A.mat[0][0] = 0;
A.mat[1][0] = 1;
A.mat[0][1] = 1;
A.mat[1][1] = 1;
Matrix B = pow_M(A,n,mod);
ll ret = f0*B.mat[0][0] + f1*B.mat[1][0];
return ret%mod;
}
ll fac[1000000];
ll G(ll p){
ll num;
if(legendre(5,p) == 1)num = p-1;
else num = 2*(p+1);
//找出num 的所有约数
int cnt = 0;
int(ll i = 1;i*i <= num;i++)
if(num%i == 0){
fac[cnt++] = i;
if(i*i != num)
fac[cnt++] = num/i;
}
sort(fac,fac+cnt);
ll ans = 0;
int(int i = 0;i < cnt;i++){
if(getFib(fac[i],p) == f0 && getFib(fac[i]+1,p) == f1){
ans = fac[i];
break;
}
}
return ans;
}
ll find_loop(ll n){
getFactors(n);
ll ans = 1;
int(int i = 0;i < fatCnt;i++){
ll record = 1;
if(factor[i][0] == 2)record = 3;
else if(factor[i][0] == 3)record = 8;
else if(factor[i][0] == 5)record = 20;
else record = G(factor[i][0]);
int(int j = 1;j < factor[i][1];j++)
record *= factor[i][0];
ans = lcm(ans,record);
}
return ans;
}

DataStructure

Posted on 2019-08-17 | Edited on 2019-10-01 | In 模板 | Views:

DataStructure

树

重心

对于一棵树来说,删去该树的重心后,所有的子树的大小不会超过原树大小的 $\large\frac 1 2$。树的重心还有一个性质,是相对于树上的其他点而言的,就是删去重心后形成的所有子树中最大的一棵节点数最少。换句话说,就是删去重心后生成的多棵子树是最平衡的。一棵树的重心至多有两个。
我们可以很容易的在一次DFS过程中求出所有节点的 $size$,即子树大小。我们每搜索完一个节点 u 的儿子 v,就判断 size[v] 是否大于 $\frac n 2$,然后在搜索完所有儿子后计算出本节点的 $size$,再判断 $n-size[u]$ 是否大于 n/2( n-siz[u] 是节点 u 上面的连通块大小)即可求出重心,时间复杂度 O(n)。

直径

树上的最长路径,可以有多条。
法一:任取树中的一个节点x,找出距离它最远的点y,那么点y就是这棵树中一条直径的一个端点。我们再从y出发,找出距离y最远的点就找到了一条直径。对于树中的任一个点,距离它最远的点一定是树上一条直径的一个端点。
法二:定义F[i]表示从i出发向远离根节点的方向走的最长路径的长度,G[i]表示从i向远离根节点的方向走的次长路径的长度。注意F[i]和G[i]不能沿着i的同一个儿子走。特别地,如果i只有一个儿子,那么G[i]=0。答案为max(F[i]+G[i])。

最近公共祖先(LCA)

树链剖分

树状数组

lowbit

1
2
3
int lowbit(int x){
return x&(-x);
}

单点修改,区间查询

1
2
3
4
5
6
7
8
9
ll n,sum[N];
void add(int p,int x){ //a[p]+=x,数组为[1,n]
while(p<=n)sum[p]+=x,p+=p&-p;
}
ll query(int p){
ll ans = 0;
while(p)ans+=sum[p],p-=p&-p;
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
int lowbit(int x){
return x&(-x);
}
ll a[N],h[N],n;
void update(int p,int x){
a[p]=x;
int lx;
while(p<=n){
h[p]=a[p];
lx = lowbit(p);
for (int i=1; i<lx; i<<=1)
h[p] = max(h[p], h[p-i]);
p += lowbit(p);
}
}
int query(int l,int r){
int ans = 0;
while(l<=r){
ans = max(ans,a[r]);
r--;
for(; l<=r-lowbit(r) ;r-=lowbit(r))ans = max(ans,h[r]);
}
return ans;
}

区间修改,单点查询

差分,设数组 $d[i]=a[i]-a[i-1],(a[0]=0)$,则 $a[i]=\sum\limits_{j=1}^id[j]$。

1
2
3
4
5
6
7
8
9
10
11
12
ll n,sum[N];
void add(int p, int x){ //这个函数用来在树状数组中直接修改
while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
add(l, x), add(r + 1, -x);
}
ll query(int p){ //单点查询
ll res = 0;
while(p) res += sum[p], p -= p & -p;
return res;
}

区间修改,区间查询

$\sum\limits_{i=1}^pa[i]=\sum\limits_{i=1}^p\sum\limits_{j=1}^id[j]=(p+1)\times\sum\limits_{i=1}^pd[i]-\sum\limits_{i=1}^pd[i]\times i$,所以我们需要维护两个数状数组$d[i],d[i]*i$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ll sum1[N],sum2[N],n;
void add(int p, ll x){
while(p<=n)sum1[i] += x, sum2[i] += x * p ,p+=p&-p;
}
void range_add(int l, int r, ll x){
add(l, x), add(r + 1, -x);
}
ll query(int p){
ll res = 0;
while(p)res += (p + 1) * sum1[i] - sum2[i],p-=p&-p;
return res;
}
ll range_query(int l, int r){
return query(r) - query(l - 1);
}

二维树状数组

单点修改+区间查询

定义$tree[x][y]$记录的是右下角为(x, y),高为lowbit(x), 宽为 lowbit(y)的区间的区间和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void add(int x, int y, int z){ //将点(x, y)加上z
int memo_y = y;
while(x <= n){
y = memo_y;
while(y <= n)
tree[x][y] += z, y += y & -y;
x += x & -x;
}
}
void ask(int x, int y){//求左上角为(1,1)右下角为(x,y) 的矩阵和
int res = 0, memo_y = y;
while(x){
y = memo_y;
while(y)
res += tree[x][y], y -= y & -y;
x -= x & -x;
}
}
区间修改 + 单点查询

差分 $d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-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
void add(int x, int y, int z){ 
int memo_y = y;
while(x <= n){
y = memo_y;
while(y <= n)
tree[x][y] += z, y += y & -y;
x += x & -x;
}
}
void range_add(int xa, int ya, int xb, int yb, int z){
add(xa, ya, z);
add(xa, yb + 1, -z);
add(xb + 1, ya, -z);
add(xb + 1, yb + 1, z);
}
void ask(int x, int y){
int res = 0, memo_y = y;
while(x){
y = memo_y;
while(y)
res += tree[x][y], y -= y & -y;
x -= x & -x;
}
}
区间修改 + 区间查询
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
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
ll read(){
char c; bool op = 0;
while((c = getchar()) < '0' || c > '9')
if(c == '-') op = 1;
ll res = c - '0';
while((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
return op ? -res : res;
}
const int N = 205;
ll n, m, Q;
ll t1[N][N], t2[N][N], t3[N][N], t4[N][N];
void add(ll x, ll y, ll z){
for(int X = x; X <= n; X += X & -X)
for(int Y = y; Y <= m; Y += Y & -Y){
t1[X][Y] += z;
t2[X][Y] += z * x;
t3[X][Y] += z * y;
t4[X][Y] += z * x * y;
}
}
void range_add(ll xa, ll ya, ll xb, ll yb, ll z){ //(xa, ya) 到 (xb, yb) 的矩形
add(xa, ya, z);
add(xa, yb + 1, -z);
add(xb + 1, ya, -z);
add(xb + 1, yb + 1, z);
}
ll ask(ll x, ll y){
ll res = 0;
for(int i = x; i; i -= i & -i)
for(int j = y; j; j -= j & -j)
res += (x + 1) * (y + 1) * t1[i][j]
- (y + 1) * t2[i][j]
- (x + 1) * t3[i][j]
+ t4[i][j];
return res;
}
ll range_ask(ll xa, ll ya, ll xb, ll yb){
return ask(xb, yb) - ask(xb, ya - 1) - ask(xa - 1, yb) + ask(xa - 1, ya - 1);
}
int main(){
n = read(), m = read(), Q = read();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
ll z = read();
range_add(i, j, i, j, z);
}
}
while(Q--){
ll ya = read(), xa = read(), yb = read(), xb = read(), z = read(), a = read();
if(range_ask(xa, ya, xb, yb) < z * (xb - xa + 1) * (yb - ya + 1))
range_add(xa, ya, xb, yb, a);
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++)
printf("%lld ", range_ask(i, j, i, j));
putchar('\n');
}
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
    int A[N],Sum[N<<2],Lazy[N<<2];
    void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}//PushUp函数更新节点信息 ,这里是求和
    void Build(int l,int r,int rt){ //Build函数建树,l,r表示当前节点区间,rt表示当前节点编号
    if(l==r) {//若到达叶节点
    scanf("%d",&Sum[rt]);
    //Sum[rt]=A[l];//储存数组值
    return;
    }
    int m=(l+r)>>1;
    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    PushUp(rt);//更新信息
    }
    void PushDown(int rt,int ln,int rn){ //ln,rn为左子树,右子树的数字数量。
    if(Lazy[rt]){
    //下推标记
    Lazy[rt<<1]+=Lazy[rt];
    Lazy[rt<<1|1]+=Lazy[rt]
    //修改子节点的Sum使之与对应的Lazy相对应
    Sum[rt<<1]+=Lazy[rt]*ln;
    Sum[rt<<1|1]+=Lazy[rt]*rn;
    Lazy[rt]=0;//清除本节点标记
    }
    }
    //区间修改
    void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
    if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内
    Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确
    Lazy[rt]+=C;//增加Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Lazy的值来调整
    return ;
    }
    int m=(l+r)>>1;
    PushDown(rt,m-l+1,r-m);//下推标记
    //这里判断左右子树跟[L,R]有无交集,有交集才递归
    if(L <= m) Update(L,R,C,l,m,rt<<1);
    if(R > m) Update(L,R,C,m+1,r,rt<<1|1);
    PushUp(rt);//更新本节点信息
    }
    //区间查询
    int Query(int L,int R,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
    if(L <= l && r <= R){//在区间内,直接返回
    return Sum[rt];
    }
    int m=(l+r)>>1;
    PushDown(rt,m-l+1,r-m); //下推标记
    if(R <= m) return Query(L,R,l,m,rt<<1);
    if(L > m) return Query(L,R,m+1,r,rt<<1|1);
    return Query(L,R,l,m,rt<<1)+Query(L,R,m+1,r,rt<<1|1);
    }

平衡二叉树

heap

  • 建堆

    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
    int Heap[n],n;
    void node_swap(int x,int y){
    int temp=Heap[x];
    Heap[x]=Heap[y];
    Heap[y]=temp;
    }
    void siftdown(int i){ //i表示操作的结点编号(从1开始)
    while (2*i<=n) {
    int minn=Heap[2*i],temp=2*i;
    if (2*i+1<=n&&Heap[2*i]>Heap[2*i+1]) {
    minn=Heap[2*i+1];
    temp=2*i+1;
    }
    if (Heap[i]>minn) {
    node_swap(i,temp);
    i=temp;
    }else break;
    }
    }
    void siftup(int i){ //i表示操作的结点编号(从1开始)
    while (i!=1) {
    if (Heap[i]<Heap[i/2]) {
    node_swap(i,i/2);
    i/=2;
    }else break;
    }
    }
    void creat(){//建立堆
    for (int i=n/2; i>=1; i--) siftdown(i);
    }
  • 删除-输出最小元素

    1
    2
    3
    4
    5
    6
    7
    int delete_min(){
    int t=Heap[1];
    Heap[1]=Heap[n];
    n--;
    siftdown(1);
    return t;
    }
  • 堆排序(从大到小)

    1
    2
    3
    4
    5
    6
    7
    void heap_sort(){
    while (n>1) {
    node_swap(1, n);
    n--;
    siftdown(1);
    }
    }

top-k

n 个数中,找出最大(最小)的 m 个,建立一个小(大)顶堆维护当前最大(最小)的 m 个数再将剩余的推入。

RMQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ll dp[N][70]
void ST(int n) {
for (int i = 1; i <= n; i++) dp[i][0] = A[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
ll RMQ(int l, int r) {
int k = 0;
while ((1 << (k + 1)) <= r - l + 1) k++;
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}

主席树

可持久化权值线段树,因为其中的每一颗树都是一颗权值线段树。

dfs序

1
2
3
4
5
6
7
8
9
10
int L[N],R[N],dep[N],tot;
vii node[N];
void dfs(int rt,int fa,int d){
L[rt]=++tot;
dep[rt]=d;
for(int v:node[rt]){
if(v!=fa)dfs(v,rt,d+1);
}
R[rt]=tot;
}
  1. 对某个节点X权值加上一个数W, 查询某个子树X里所有点权的和.

  2. 对节点X到Y的最短路上所有点权都加一个数W, 查询某个点的权值.

    这个操作等价于

    a. 对X到根节点路径上所有点权加W

    b. 对Y到根节点路径上所有点权加W

    c. 对LCA(x, y)到根节点路径上所有点权值减W

    d. 对LCA(x,y)的父节点 fa(LCA(x, y))到根节点路径上所有权值减W

    于是要进行四次这样从一个点到根节点的区间修改.将问题进一步简化, 进行一个点X到根节点的区间修改, 查询其他一点Y时,只有X在Y的子树内, X对Y的值才有贡献且贡献值为W.当单点更新X时,X实现了对X到根的路径上所有点贡献了W.于是只需要更新四个点(单点更新) ,查询一个点的子树内所有点权的和(区间求和)即可.

  3. 对节点X到Y的最短路上所有点权都加一个数W, 查询某个点子树的权值之和.

    同问题2中的修改方法, 转化为修改某点到根节点的权值加/减W
    当修改某个节点A, 查询另一节点B时
    只有A在B的子树内, Y的值会增加
    W (dep[A] - dep[B] + 1) => W (dep [A] + 1) - W dep[B]
    那么我们处理两个数组就可以实现:
    处理出数组Sum1,每次更新W
    (dep[A]+1),和数组Sum2,每次更新W.
    每次查询结果为Sum1(R[B]) – Sum1(L[B]-1) - (Sum2(R[B]) – Sum2(L[B]-1)) * dep [B].

  4. 对某个点X权值加上一个数W, 查询X到Y路径上所有点权之和.

    求X到Y路径上所有的点权之和, 和前面X到Y路径上所有点权加一个数相似
    这个问题转化为
    X到根节点的和 + Y到根节点的和 - LCA(x, y)到根节点的和 - fa(LCA(x,y)) 到根节点的和
    更新某个点x的权值时,只会对它的子树产生影响,对x的子树的每个点到根的距离都加了W.
    那么我们用”刷漆”(差分前缀和),更新一个子树的权值.给L[x]加上W,给R[x]+1减去W,那么sum(1~L[k])就是k到根的路径点权和.

  5. 对节点X的子树所有节点加上一个值W, 查询X到Y的路径上所有点的权值和

    同问题4把路径上求和转化为四个点到根节点的和
    X到根节点的和 + Y到根节点的和 - LCA(x, y)到根节点的和 - parent(LCA(x,y)) 到根节点的
    再用刷漆只更新子树.
    修改一点A, 查询某点B到根节点时, 只有B在A的子树内, A对B才有贡献.
    贡献为W (dep[B] - dep[A] + 1) => W (1 - dep[A]) + W dep[B]
    和第三题一样, 用两个sum1,sum2维护 W
    (dep[A] + 1),和W.
    最后答案就是sum2*dep[B]-sum1.

  6. 对子树X里所有节点加上一个值W, 查询某个点的值.

    对DFS序来说, 子树内所有节点加W, 就是一段区间加W.
    所以这个问题就是 区间修改, 单点查询.树状数组+刷漆.

  7. 对子树X里所有节点加上一个值W, 查询某个子树的权值和.

    子树所有节点加W, 就是某段区间加W, 查询某个子树的权值和, 就是查询某段区间的和
    区间修改区间求和,用线段树可以很好解决.

欧拉序

从根结点出发,按dfs的顺序在绕回原点所经过所有点的顺序

Others

曼哈顿最小生成树

以每个点为原点,划分八个区域,将每个区域里距离 $S$ 最近的点连边,再跑最小生成树算法。

18161518-76bf4a5ccb434262849bdad165f88256

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
struct point{
int x,y,id;
oper(point){
if(x==obj.x)return y<obj.y;
else
return x<obj.x;
}
}a[N];
int b[N];
int uni[N];
int find_root(int x){
if (uni[x]==x) return x;
else return uni[x]=find_root(uni[x]);
}
struct Edge{
int u,v,w;
oper(Edge){
return w<obj.w;
}
}edge[2*M];
int cnt;
void addedge(int u,int v,int w){
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt++].w=w;
}
int n;
pii h[N];
void add(int p,int loc,int x,int nn){
while(p<=nn){
if(h[p].fi>x)h[p]=pii(x,loc);
p+=p&-p;
}
}
pii query(int p){
pii mi(inf,-1);
while(p){
if(h[p].fi<mi.fi)mi=h[p];
p-=p&-p;
}
return mi;
}
bool cmp(int a,int b){
return a>b;
}
void fun(){
sort(a,a+n);
for0(i,n)b[i]=a[i].y-a[i].x;
sort(b,b+n,cmp);
int m=unique(b,b+n)-b;//离散化 y-x
for(int i=1;i<=m;i++)h[i]=pii(inf,-1);
for(int i=n-1;i>=0;i--){
int p=upper_bound(b,b+m,a[i].y-a[i].x,cmp)-b;
pii mi=query(p);
if(mi.se!=-1)addedge(a[i].id,mi.se,mi.fi-a[i].y-a[i].x);
add(p,a[i].id,a[i].y+a[i].x,m);
}
}
int main() {
int k;
in(n,k);
for0(i,n){
in(a[i].x,a[i].y);
a[i].id=i;
}
fun();
for0(i,n)swap(a[i].x,a[i].y);
fun();
for0(i,n)a[i].x*=-1;
fun();
for0(i,n)swap(a[i].x,a[i].y);
fun();
sort(edge,edge+cnt);
for0(i,n)uni[i]=i;
int ans=0,cc=0;
for0(i,cnt){
int u=edge[i].u,v=edge[i].v;
if(find_root(u)!=find_root(v)){
cc++;
uni[find_root(u)]=find_root(v);
if(cc==n-k){
ans=edge[i].w;
break;
}
}
}
out(ans,1);
return 0;
}

莫队

对区间询问按左端点排序,将序列分成 $sqrt(n)$ 个长度为 $sqrt(n)$ 的块,若左端点在同一个块内,则按右端点排序。

优化
  • 块的大小为 $\huge\frac n {\sqrt{\frac 2 3 m}}$ 最优
  • 奇偶排序

    • 在块的序号为奇时,对 r 按从小到大排序,反之按从大到小排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      struct node {
      int l,r,id;
      }q[N];
      bool cmp(node a,node b){
      return a.l/block==b.l/block?a.l/block&1?a.r<b.r:a.r>b.r:a.l<b.l;
      }
      int main(){
      block=n/sqrt(Q*2/3);
      for(int i=0;i<m;i++){
      while(pl < q[i].l) del(a[pl++]);
      while(pl > q[i].l) add(a[--pl]);
      while(pr < q[i].r) add(a[++pr]);
      while(pr > q[i].r) del(a[pr--]);
      ans[q[i].id] = sum;
      }
      }
带修改

加上一个时间维,表示操作的时间。
即把询问 $[l,r]$ 变为 $[l,r,time]$
这一次我们排序的方式是以 $n^{\frac 2 3}$ 为一块,分成了 $n^{\frac 1 3}$ 块,第一关键字是左端点所在块,第二关键字是右端点所在块,第三关键字是时间。

Union Find

1
2
3
4
5
int uni[N];
int find_root(int x){
if (uni[x]==x) return x;
else return uni[x]=find_root(uni[x]);
}

主元素问题

找出数组中数量超过一半的数

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
int findMainElement(const int* array, size_t size) {
int candidate = array[0];
int counter = 1;
for (int i = 1; i < size; ++i) {
if (candidate == array[i]) {
++counter;
}
else if (counter == 1) {
candidate == array[i];
}
else {
--counter;
}
}
counter = 0;
for (int i = 0; i < size; ++i) {
if (candidate == array[i]) {
++counter;
}
}
if (counter * 2 > size) {
return candidate;
}
else {
return -1;
}
}

子段和

最大子段和

1
2
3
4
5
6
int b=0,sum=-100000000,l,r;
for(int i=0;i<n;i++){
if(b>0) b=b+a[i];
else b=a[i];
if(b>sum) sum=b;
}

最大环形子段和

思路一:

​ 把环形最大子段和可以看做两部分。第一部分——正常最大子段和,第二部分——跨越a[0] 和 a[n-1]的最大子段和。 第一部分可以用O(n) 求出,第二部分我们从a[0] 开始计算 0~n-2 的最大和,记录结束位置position1。 再从a[n-1] 开始计算 n-1~1的最大和,记录结束位置 position2。

position2 > position1 则第二部分最大和 a[0] + … a[position1] + a[position2] + …a[n-1]

position2 <= position1 则第二部分最大和 a[0] + ……. a[n-1]

思路二: 数组总和 - 最小子段和

最大M个子段和

设F(i, j) 为 在前i个元素中选j个子段的最大和,且包含元素a[j]. 那么对于a[j] , 1) a[j] 自己组成第 j 子段 ; 2) a[j] 包含于第 j 子段中;

长度不超过 m 的最大子段和

dp[i] = sum[i] - min(sum[j] | i- m <= j <= i)

利用单调队列优化.如果来了一个前缀,肯定下标是比在队列里的是靠后的,如果它的值还比队列里的小,那么队列里的元素就没有必要存在了,就把它们踢出去。

这样的话最小值就是队首的元素,当然在用队首元素的时候还要在看一下当前的队首元素还符不符合限制条件,如果不符合就弹出。

1
2
3
4
5
6
7
8
9
10
11
12
ll fun(ll m, ll a[], int len) {  // a的下标从1开始
list<ll> li;
li.push_back(0);
ll ans = 0;
for1(i, len) {
while (!li.empty() && a[li.back()] > a[i]) li.pop_back();
li.push_back(i);
while (!li.empty() && i - li.front() > m) li.pop_front();
ans = max(ans, a[i] - a[li.front()]);
}
return ans;
}

最大绝对值子段和

要么正的最大,要么负的最小。 所以问题解等于max{abs(最大子段和), abs(最小子段和)}

最小绝对值字段和

  • 构造数组
  • 对b排序(保留index 信息)

  • 求排序后数组相邻位置差最小

固定长度的区间极值

1
2
3
4
5
6
7
int l=1,r=0,q[N];//q[i]表示从 i 开始长度为 len 的序列中的最小值的下标
for1(i,n){
while(i-q[l]+1>a&&l<=r)l++;
while(a[q[r]]>a[i]&&l<=r)r--;
q[++r]=i;
minn[i]=a[q[l]];//minn[i]表示以 i 结尾的长度为 len 的序列中的最小值
}

DP

Posted on 2019-08-17 | Edited on 2019-09-21 | In 模板 | Views:

DP

背包

数位

  • $1-n$ 所有数中,数位 $0-9$ 的数量

    $f[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
    ll f[20],cnt[10];
    void fun(ll n){
    mem0(cnt);
    if(!n)return;
    if(!f[1]){
    f[1]=1;
    ll s=10;
    for(int i=2;i<20;i++)f[i]=f[i-1]*10+s,s*=10;
    }
    ll k=n,m;
    int w=0,i,j,sum[15];
    while(k) sum[++w]=k%10,k/=10;
    for (m=1,i=1; i<w; i++){
    cnt[0]+=f[i-1]*9;
    for (j=1; j<=9; j++) cnt[j]+=f[i-1]*9+m;
    m*=10;
    }
    k=n-sum[w]*m;
    for (i=1; i<sum[w]; i++) cnt[i]+=m;
    for (i=0; i<=9; i++) cnt[i]+=f[w-1]*(sum[w]-1);
    cnt[sum[w]]+=k+1;
    for (i=w-1; i; i--){
    m/=10,k-=sum[i]*m;
    for (j=0; j<sum[i]; j++) cnt[j]+=m;
    for (j=0; j<=9; j++) cnt[j]+=f[i-1]*sum[i];
    cnt[sum[i]]+=k+1;
    }
    }

Game

Posted on 2019-08-17 | Edited on 2019-09-21 | In 模板 | Views:

Game

只要找到一个子局面是P-position(先手必败)就能说明是N-position(后手必败),反之为P-position。

SG函数(Sprague-Grund)

mex(minimal excludant)是定义在整数集合上的操作。它的自变量是任意整数集合,函数值是不属于该集合的最小自然数。

终止状态的SG值显然为0,并且SG值为0的状态就是P状态,SG值不为0的状态就是N状态.

Nim Game

有n堆石子,石子数目=分别为$a_1,a_2,a_3 · · · a_n$,A,B两人每次可以选一堆石子取走任意多个

P-position: $a_1\wedge a_2\wedge · · · \wedge a_n=0$

Wythoff’s Game

两堆石子,个数为$x_1,x_2$; A,B轮流取石子,规定要么只取一堆的任意多个,要么在两堆里取同样任意多个

P-position: (0,0)(1,2)(3,5)(4,7)

  • $a_k$是未在之前出现过的最小自然数
  • $b_k=a_k+k$
  • 由Beatty定理可得: $\frac 1\alpha+\frac 1 {\alpha+1}=1,\alpha=\frac {1+\sqrt5}2$

Beatty数列和Beatty定理

取正无理数α,β,使得$\frac 1\alpha+\frac 1 \beta=1$

构造两个数列a,b,它们的通项为$a_n=⌊αn⌋,b_n=⌊βn⌋$

那么这个数列显然是正整数序列,Beatty定理指出,两个数列都是严格递增的,并且每个正整数在两个数列中只出现一次

Fibonacci Nim

有一堆个数为n的石子,A,B轮流取石子,满足:

  • 先手不能在第一次把所有的石子取完;
  • 之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间 (包含1和对手刚取的石子数的2倍)

P-position: 1,2,3,5,8,13,21,34,55,89,…(Fibonacci)

Zeckendorf定理

任何正整数可以表示为若干个不连续的Fibonacci数之和。

Staircase Nim

n堆石子,每堆石子的数量为$x_1,x_2,….x_n$,A,B轮流操作,每次可以选第k堆中的任意多个石子放到第k-1堆中,第1堆中的石子可以放到第0堆中.

P-position: $x_1 \wedge x_3\wedge x_5…\wedge x_{2*n+1}=0$

Anti Nim

有n堆石子,A,B两人每次可以选一堆石子取走任意多个,取到最后一个石子的人为输.

N-position: 所有堆石子数都为1且SG值为0 ; 至少有一堆石子数大于1且SG值不为0

Nim变形

两个人在一个1*N的格子内挪动棋子,刚开始在若干个位置上有若干个棋子,每一个选手可以进行的操作时选择一个棋子并把它向左方移动,当然不能越过其它的棋子,也不能超出边界。

按位置升序排列后,从后往前把他们两两绑定成一对。如果总个数是奇数,就把最前面一个和边界(位置为0)绑定。对手移动左,你移动相同步数;对手移动右,表示取相应的石子.

K倍动态减法

  • K=1 P-position: 2^k 先手的策略是取lowbit(n)

  • K=2 P-position: Fibonacci Nim

  • K>2 (K=1或K=2也可以用次方法构造)仿照斐波那契博弈的构造方法构造一个数列an b[]作为辅助数组表示前i个的a能够按规则构造出的最大的数

    1
    2
    3
    4
    a[i+1]=b[i]+1;
    //寻找最大的 t 使得 a[t] * K < a[i+1];
    b[i+1] = b[t] + a[i+1];
    P-position: a[]

Geomerty

Posted on 2019-08-17 | Edited on 2019-09-30 | In 模板 | Views:

Geomerty

preprocess

1
2
3
4
5
6
7
typedef double db;
const db pi = acos(-1.0);
const db inf = 1e100;
const db eps = 1e-8;
int sign(db a) { return a < -eps ? -1 : a > eps; }
int db_cmp(db a, db b){ return sign(a-b); }
int inmid(db k1,db k2,db k3){return sign(k1-k3)*sign(k2-k3)<=0;}// k3 在 [k1,k2] 内

Point&Vector

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
struct Point{
db x,y;
Point(){}
Point(db _x,db _y) : x(_x),y(_y){}
Point operator + (const Point &k1) const{return Point(k1.x+x,k1.y+y);}
Point operator - (const Point &k1) const{return Point(x-k1.x,y-k1.y);}
Point operator * (const db k1) const{return Point(x*k1,y*k1);}
Point operator / (const db k1) const{return Point(x/k1,y/k1);}
db operator * (const Point b) const{return x * b.x + y * b.y;}//点积
db operator ^ (const Point b) const{return x * b.y - y * b.x;}//叉积,顺时针为负
bool operator == (const Point &k1) const{return db_cmp(x,k1.x)==0&&db_cmp(y,k1.y)==0;}
Point rotate(db k1){return Point(x*cos(k1)-y*sin(k1),x*sin(k1)+y*cos(k1));}// 逆时针旋转
Point rotate90(){return Point(-y,x);}
db abs(){return sqrt(x*x+y*y);}
db abs2(){return x*x+y*y;}
Point unit(){return *this/abs();}
db angle(){return atan2(y,x);}
bool operator < (const Point k1) const{//极角排序
int p1=sign(y)==1||(sign(y)==0&&sign(x)==-1),p2=sign(k1.y)==1||(sign(k1.y)==0&&sign(k1.x)==-1);
return p1<p2||(p1==p2&&sign(*this^k1)>0);
}
void out(){printf("%.10f %.10f\n",x,y);}
};
typedef Point Vector;
int inmid(Point k1,Point k2,Point k3){return inmid(k1.x,k2.x,k3.x)&&inmid(k1.y,k2.y,k3.y);}
db angleOfTwoVector(Vector a, Vector b) {return atan2(a ^ b, a * b);}
Point proj(Point q,Point k1,Point k2){ // q 到直线 k1,k2 的投影
Point k=k2-k1;
return k1+k*((q-k1)*k/k.abs2());
}
Point reflect(Point q,Point k1,Point k2){// q 关于直线 k1,k2 的对称点
return proj(q,k1,k2)*2-q;
}
Point getLL(Point k1,Point k2,Point k3,Point k4){//直线交点
db w1=(k1-k3)^(k4-k3),w2=(k4-k3)^(k2-k3);
return (k1*w2+k2*w1)/(w1+w2);
}
int intersect(db l1,db r1,db l2,db r2){//是否有交集
if (l1>r1) swap(l1,r1); if (l2>r2) swap(l2,r2); return db_cmp(r1,l2)!=-1&&db_cmp(r2,l1)!=-1;
}
int checkSL(Point k1,Point k2,Point k3,Point k4){// 求线段 (S) k1,k2 和直线 (L) k3,k4 的交点
return sign((k3-k1)^(k4-k1))*sign((k3-k2)^(k4-k2))<=0;
}
int checkSS(Point k1,Point k2,Point k3,Point k4){//两线段是否相交
return intersect(k1.x,k2.x,k3.x,k4.x)&&intersect(k1.y,k2.y,k3.y,k4.y)&&checkSL(k1,k2,k3,k4)&&checkSL(k3,k4,k1,k2);
}
db disPP(Point k1,Point k2){return (k2-k1).abs();}
db disPP2(Point k1,Point k2){return (k2-k1).abs2();}
db disPS(Point q,Point k1,Point k2){
Point k3=proj(q,k1,k2);
if (inmid(k1,k2,k3)) return disPP(q,k3);
return min(disPP(q,k1),disPP(q,k2));
}
db disPL(Point q,Point k1,Point k2){
if(k1==k2)return disPP(q,k1);
return fabs((q - k1) ^ (k2 - k1)) / (k2-k1).abs();
}
db disSS(Point k1,Point k2,Point k3,Point k4){
if (checkSS(k1,k2,k3,k4)) return 0;
return min(min(disPS(k1,k3,k4),disPS(k2,k3,k4)),min(disPS(k3,k1,k2),disPS(k4,k1,k2)));
}
db disSL(Point k1,Point k2,Point k3,Point k4){
if (checkSL(k1,k2,k3,k4)) return 0;
return min(disPL(k1,k3,k4),disPL(k2,k3,k4));
}
int onS(Point q,Point k1,Point k2){return inmid(k1,k2,q)&&sign((k1-q)^(k2-k1))==0;}

PolarPoint

1
2
3
4
5
6
7
8
9
10
struct PolarPoint {
db Angle, Length;
PolarPoint(db a,db b):Angle(a),Length(b){}
};
Vector PolarPointToVector(PolarPoint a) {
return (Vector){a.Length * cos(a.Angle),a.Length * sin(a.Angle)};
}
PolarPoint VectorToPolarPoint (Vector a){
return PolarPoint(a.angle(),a.abs());
}

Line&Segment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Line {
Point s, t;
Line(){}
Line(Point _s,Point _t):s(_s),t(_t){}
Vector dir(){return (t-s);}
Vector unitDir(){return (t-s).unit();}
int place(Point k){return sign((t-s)^(t-k));}
db len(){return (t-s).abs();}
};
typedef Line Segment;
Point proj(Point q,Line k){return proj(q,k.s,k.t);}
Point reflect(Point q,Line k){return reflect(q,k.s,k.t);}
Point getLL(Line k1,Line k2){return getLL(k1.s,k1.t,k2.s,k2.t);}
bool parallel(Line k1,Line k2){return sign(k1.dir()^k2.dir())==0;}
bool sameDir(Line k1,Line k2){return parallel(k1,k2)&&sign(k1.dir()*k2.dir())==1;}
int checkSS(Segment k1,Segment k2){return checkSS(k1.s,k1.t,k2.s,k2.t);}
int checkSL(Segment k1,Line k2){return checkSL(k1.s,k1.t,k2.s,k2.t);}
db disPS(Point a, Segment b) {return disPS(a,b.s,b.t);}
db disPL(Point a, Line b) {return disPL(a,b.s,b.t);}
db disSS(Segment k1,Segment k2){return disSS(k1.s,k1.t,k2.s,k2.t);}
db disSL(Segment k1,Line k2){return disSL(k1.s,k1.t,k2.s,k2.t);}
int onS(Point q,Segment k){return onS(q,k.s,k.t);}

Ciercle

1
2
3
4
5
struct Circle {
Point O;
db R;
Circle(Point _O,db _R):O(_O),R(_R){}
};

Simpson

用二次曲线逼近原函数,在平面直角坐标系中,由三点 $(x_1,y_1),(x_2,y_2),(x_3,y_3) (x_1<x_2<x_3,x_2=\frac{x_1+x_3} 2)$ 确定的抛物线 $y=f(x)$ 的定积分为

将要积分的区域 $[L, R]$ 划分成 $n$ 份,横坐标为 $x_0\sim x_n$,对应的函数值分别为 $y_0\sim y_n$ 其结果为

自适应Simpson

对要积分的区域 $[L, R]$ 分成两半 $[L, mid]$ 和 $[mid, R]$,如果用二次曲线对 $(L, mid, R)$ 求出的积分值,和对 $(L, \frac {L+mid} 2, mid)$ 和 $(mid,\frac {L+mid} 2, R)$ 分别积分再加起来的值,相差不超过某个精确度,那么就可以停止划分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1
db simpson (db a,db b){
db c = a + (b-a)/2;
return (F(a)+4*F(c)+F(b))*(b-a)/6;
}
db asr (db a,db b,db eps,db A){
db c = a + (b-a)/2;
db 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);
}
db asr(db a,db b,db eps){//a-左端点, b-右端点
return asr(a,b,eps,simpson(a,b));
}
1
2
3
4
5
6
7
8
9
10
11
// 2
db calc(db len,db fL,db fM,db fR){ //求长度为len的[L,R]区间,中点为M的Simpson近似面积
return (fL+4*fM+fR)*len/6;
}
db Simpson(db L,db R) {//Simpson积分求区间[L,R]的面积并,F(L)=L,F(R)=R,F(M)=M,把[L,R]当成整体来拟合得到的面积是sqr
db M=(L+R)/2,fL=F(L),fM=F(M),fR=F(R),sqr=calc(R-L,fL,fM,fR);
db g1=calc(M-L,fL,F((L+M)/2),fM),g2=calc(R-M,fM,F((M+R)/2),fR);
if(fabs(sqr-g1-g2)<=eps) //把当前区间分成2半再拟合得到的答案差别很小,就不再递归下去了
return g1+g2;
return Simpson(L,M)+Simpson(M,R);
}

Others

n 维球体的体积递推公式

Graph

Posted on 2019-08-17 | Edited on 2019-09-21 | In 模板 | Views:

Graph

存图

前向星

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
struct Edge{
int u,v,nxt;
ll w;
}edge[2*M];
int fir[N],cnt,FLAG;
void addedge(int u,int v,ll w){
if(!FLAG){
mem_1(fir);
FLAG=1;
cnt=0;
}
//edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=fir[u];
fir[u]=cnt++;
}
struct node{
int u;
ll d;
node(int u,ll d):u(u),d(d){}
bool operator < (const node &a) const{
return d>a.d;
}
};

最短路

floyd-warshall

1
2
3
4
5
6
//Floyd-Warshall算法核心语句   
for(k=1;k<=n;k++)//先枚举中转点
for(i=1;i<=n;i++)//枚举起点
for(j=1;j<=n;j++)//枚举终点
if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];

bellman

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool bellman_ford(int start, int n){
for (int i = 1; i <= n; i++)dist[i] = INF;
dist[start] = 0;
for (int i = 1; i<n; i++){
bool flag = false;
for (int j = 0; j<E.size(); j++){
int u = E[j].u;
int v = E[j].v;
int cost = E[j].cost;
if (dist[v]>dist[u] + cost){
dist[v] = dist[u] + cost;
flag = true;
}
}
if (!flag)return true;
}
for (int j = 0; j<E.size(); j++)
if (dist[E[j].v]>dist[E[j].u] + E[j].cost) return false;
return true;
}

SPFA

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
const int inf=0x3f3f3f3f;
int cnt[509],bm[109];
bool used[509];
vector< pair<int,int> > edge[509];
bool spfa(int st){
for(int i=1; i<=n; i++)bm[i]=inf;
memset(used,0,sizeof(used));
memset(cnt,0,sizeof(cnt));//记录从起点到i点的最短距离包含点的个数
bm[st]=0; cnt[st]=1; used[st]=1;
queue<int> que;
que.push(st);
while(!que.empty()){
int u=que.front();
used[u]=0; que.pop();
for(int i=0; i<edge[u].size(); i++){
int v=edge[u][i].first;
int w=edge[u][i].second;
if(bm[v]>bm[u]+w){
bm[v]=bm[u]+w;
cnt[v]=cnt[u]+1;
if(cnt[v]>n) return 0;//存在负环
if(!used[v]){
que.push(v);
used[v]=1;
}
}
}
}
return 1;
}

堆优dijkstra

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
struct Edge{
int u,v,nxt;
ll w;
}edge[2*M];
int fir[N],cnt,FLAG;
void addedge(int u,int v,ll w){
if(!FLAG){
mem_1(fir);
FLAG=1;
cnt=0;
}
//edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=fir[u];
fir[u]=cnt++;
}
struct node{
int u;
ll d;
node(int u,ll d):u(u),d(d){}
bool operator < (const node &a) const{
return d>a.d;
}
};
bool used[N];
ll dis[N];
void dijkstra(){
priority_queue<node> que;
memset(dis,inf,sizeof(dis));
dis[1]=0;
que.push(node(1,dis[1]));
memset(used,0,sizeof(used));
while(!que.empty()){
int u=que.top().u;
que.pop()
if(used[u]) continue;
used[u]=1;
for(int i=fir[u]; i!=-1; i=edge[i].nxt){
int v=edge[i].v;
int w=edge[i].w;
if(!used[v]&&dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
que.push(node(v,dis[v]));
}
}
}
}

dijkstra

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
for(int i=1; i<=n; i++)
{
dis[i]=edge[1][i];
used[i]=1;
}

used[1]=0; dis[1]=0;
for(int i=1; i<=n; i++)
{
//printf("ASD\n");
int minV=inf;
int id=1;//important

for(int j=1; j<=n; j++)
if(used[j]&&minV>dis[j])
{
minV=dis[j];
id=j;
}

used[id]=0;
for(int j=1; j<=n; j++)
{
if(edge[id][j]<inf&&used[j]&&dis[j]>dis[id]+edge[id][j]) dis[j]=dis[id]+edge[id][j];
}
}

差分约束系统

求解差分约束系统,都可以转化成图论的单源最短路径(或最长路径)问题。
我们观察上面例子中的不等式,都是x[i] - x[j] <= a[k],可以进行移项,成为x[i] <= x[j] + a[k],我们令a[k] = w(j, i),dis[i]=x[i],并使i=v,j=u,那么原始就变为:dis[u]+w(u,v)>=dis[v],于是可以联想到最短路模型中的一部分代码
但是好像不等号方向刚好相反,但其实这并不矛盾,上面的代码要实现的是使dis[u]+w(u,v)>dis[v],而对于不等式,我们进行建边的操作:对于每个不等式 x[i] - x[j] <= a[k],对结点 j 和 i 建立一条 j -> i的有向边,边权为a[k],求x[n-1] - x[0] 的最大值就是求 0 到n-1的最短路,两者刚好吻合。所以求解差分约束问题就转化为了最短路问题。

差分约束

求解不等式组,当把不等式整理成 $d[a]+w<=d[b]$ 时,我们求最长路。整理成 $d[a]+w>=d[b]$ 时,我们求最短路。在一个未知数定死的情况下,最短路求得是最大值,最长路求得是最小值。

candies

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
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,cnt;
struct Edge
{
int u,v,w,nxt;
}edge[150005];
int fir[30004];
void addedge(int u,int v,int w)
{
//edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=fir[u];
fir[u]=cnt++;
}
struct node
{
int u;
int d;
node(int u,int d):u(u),d(d){}
bool operator < (const node &a) const
{
return d>a.d;
}
};
bool used[30004];
int dis[30004];
void dijkstra()
{
priority_queue<node> que;
while(!que.empty()) que.pop();
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[1]=0;
que.push(node(1,dis[1]));
memset(used,0,sizeof(used));
while(!que.empty())
{
int u=que.top().u;
que.pop();
if(used[u]) continue;
used[u]=1;
for(int i=fir[u]; i!=-1; i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(used[v]) continue;
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
que.push(node(v,dis[v]));
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
memset(fir,-1,sizeof(fir));
cnt=0;
for(int i=1; i<=m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
dijkstra();
printf("%d\n",dis[n]);
}
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
int n,m,ans;
int used[N];
int cnt[N];//cnt[i] 结点[i,n]构成的最大团的数量
int group[N];
bool edge[N][N];
bool dfs(int u,int dep){
for(int i=u+1; i<=n; i++){
if(cnt[i]+dep<=ans)return 0;
if(edge[u][i]){
int j=0;
for(j=0; j<dep; j++){
if(!edge[i][used[j]])break;
}
if(j==dep){
used[dep]=i;
if(dfs(i,dep+1))return 1;
}
}
}
if(dep>ans){
for(int i=0; i<dep; i++)group[i]=used[i];
ans=dep;
return 1;
}
return 0;
}
void maxclique(){
memset(used,0,sizeof(used));
for(int i=n; i>=1; i--){//n为点数
used[0]=i;
dfs(i,1);
cnt[i]=ans;
}
}

最大独立集

补图的最大团

Java

Posted on 2019-08-17 | Edited on 2019-09-21 | In 模板 | Views:

Java

temple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.*;
import java.util.*;
import java.math.*;

public class Main{
public static void main(String[] args) throws IOException{
Scanner sc =new Scanner(System.in);
int a [] = new int[12];
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
sc.close();
}
}

IO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//System.in,只能读取单个字符
char i = (char) System.in.read();

//BufferedReader类和InputStreamReader类,读取一个字符串
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String str = null;
str = reader.readLine();
System.out.println("your value is :"+str);
int n = Integer.parseInt(reader.readLine());

//Scanner
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的姓名:");
String name = sc.nextLine(); //next()方法是不接收空格的,在接收到有效数据前,所有的空格或者tab键等输入被忽略,若有有效数据,则遇到这些键退出。nextLine()可以接收空格或者tab键,其输入应该以enter键结束。
System.out.println("请输入你的年龄:");
int age = sc.nextInt();
System.out.println("请输入你的工资:");
float salary = sc.nextFloat();
System.out.println("你的信息如下:");
System.out.println("姓名:"+name+"\n"+"年龄:"+age+"\n"+"工资:"+salary);

BigInteger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String temp1 = "-1000000000000000000000000000000000000";
BigInteger bg1 = new BigInteger(temp1); //注意初始化的方式,使用字符串来初始化
System.out.println(bg1.abs()); //绝对值方法 object.abs()
String temp2 = "100000000000000000000000000";
BigInteger bg2 = new BigInteger(temp2);
System.out.println(bg1.add(bg2)); //加法 object.add(BigInteger b)
System.out.println(bg1.subtract(bg2)); //减法 返回为 bg1 - bg2 (this - param)
System.out.println(bg1.multiply(bg2)); //乘法 返回 bg1 * bg2
System.out.println(bg1.divide(bg2)); //除法 返回bg1 / bg2
System.out.println(bg1.mod(bg2)); //取模运算 返回的是 bg1%bg2 (this mod param)
System.out.println(bg1.gcd(bg2)); //直接封装好了 求解bg1,bg2 的最大公约数
int temp5 = 5;
System.out.println(bg2.pow(temp5)); //乘方运算 注意这个方法的参数是基本类型int
System.out.println(bg2.compareTo(bg1)); // 比较方法 结果为1,bg2大;0,相等;-1,bg1大
System.out.println(bg3.equals(bg4)); //返回结果为true
//在BigDecimal更直观,例如0.1 与0.10 ,equal返回false 而compareTo则是正确的结果。

BigDecimal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String temp1 = "1.2222222222222222222222222";
BigDecimal bd1 = new BigDecimal(temp1);
String temp2 = "2.333333333333333333333333";
BigDecimal bd2 = new BigDecimal(temp2);
System.out.println(bd1.add(bd2)); // 加法
System.out.println(bd1.add(bd2).doubleValue()); //3.5555555555555554 这里用了一个方法将结果转化为double类型了
System.out.println(bd2.subtract(bd1)); //减法 输出 1.1111111111111111111111108
System.out.println(bd2.subtract(bd1).doubleValue()); //输出 1.1111111111111112
System.out.println(bd2.multiply(bd1)); //乘法
System.out.println(bd2.divide(bd1, 5, RoundingMode.HALF_UP));//除法应该注意很有可能会有除不尽的情况,这时候会有异常抛出,所以要传入控制参数
System.out.println(bd1.compareTo(bd2)); //比较方法
BigDecimal bd3 = new BigDecimal("1.20");
BigDecimal bd4 = new BigDecimal("1.2");
System.out.println(bd3.compareTo(bd4)); //返回0表示相等
System.out.println(bd3.equals(bd4));//返回的是false 是错误的,所以比较的时候使用compareTo()方法

vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vector<E> v = new Vector<E>();
//下标访问
v.elementAt(int index);
//遍历
for(Iterator<E> iter=v.iterator(); ite.hasNext() ;) {
System.out.println(ite.next());
}

Iterator<E> ite=S_vec.iterator();
while(ite.hasNext()) {
System.out.println(ite.next());
}

Enumeration<E> e=v.elements();
while(e.hasMoreElements()) {
System.out.println(e.nextElement());
}
123…9
PerpEternal

PerpEternal

81 posts
12 categories
27 tags
Links
  • GuYF's Blog
© 2019 PerpEternal