Hexo

  • Beranda

  • Arsip

「kuangbin带你飞」专题十七 AC自动机

Diposting di 2019-04-24 | Edited on 2018-09-23

传送门

A.HDU2222 Keywords Search

模板题。给出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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
struct Trie{
int next[maxm][26],fail[maxm],end[maxm];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]++;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<26;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
void debug(){
for(int i=0;i<L;i++){
printf("id =%3d,fail = %3d,end = %3d ,chi = [",i,fail[i],end[i]);
for(int j=0;j<26;j++)
printf("%2d",next[i][j]);
printf("]\n");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<ac.query(buf)<<endl;
}
return 0;
}

B.HDU2896 病毒侵袭

和A题一样的模板题,不过要求我们计算长串中出现了那些字符串,在ac自动机的end数组改成是哪一个字符串的编号然后在查询的时候用used数组记录一下既可。
注意是字符串不仅仅包括字母所以数组开开到128

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
struct Trie{
int next[maxm][128],fail[maxm],end[maxm];
bool used[550];
int root,L;
int newnode(){
for(int i=0;i<128;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]]==-1)
next[now][buf[i]]=newnode();
now=next[now][buf[i]];
}
end[now]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<128;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<128;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
bool query(char buf[],int n,int id){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=false;
bool flag=false;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]=true;
flag=true;
}
temp=fail[temp];
}
}
if(!flag)return false;
else{
cout<<"web "<<id<<":";
for(int i=1;i<=n;i++)
if(used[i])cout<<" "<<i;
cout<<endl;
return true;
}
}
void debug(){
for(int i=0;i<L;i++){
printf("id =%3d,fail = %3d,end = %3d ,chi = [",i,fail[i],end[i]);
for(int j=0;j<26;j++)
printf("%2d",next[i][j]);
printf("]\n");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
int n;
cin>>n;
ac.init();
for(int i=1;i<=n;i++){
cin>>buf;
ac.insert(buf,i);
}
int m;
ac.build();
cin>>m;
int sum=0;
for(int i=1;i<=m;i++){
cin>>buf;
if(ac.query(buf,n,i))sum++;
}
cout<<"total: "<<sum<<endl;
return 0;
}

C.HDU3065 病毒侵袭持续中

注意:这题是多组数据,但是题面上没写很坑~!

这题和B题一样的题意只不过题面很坑

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;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e8+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
char word[1100][55];
struct Trie{
int next[maxm][128],fail[maxm],end[maxm];
int used[1100];
int root,L;
int newnode(){
for(int i=0;i<128;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]]==-1)
next[now][buf[i]]=newnode();
now=next[now][buf[i]];
}
end[now]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<128;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<128;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
bool query(char buf[],int n){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]++;
}
temp=fail[temp];
}
}
for(int i=1;i<=n;i++){
if(used[i]){
cout<<word[i]<<": "<<used[i]<<endl;
}
}
}
void debug(){
for(int i=0;i<L;i++){
printf("id =%3d,fail = %3d,end = %3d ,chi = [",i,fail[i],end[i]);
for(int j=0;j<128;j++)
printf("%2d",next[i][j]);
printf("]\n");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
int n;
while(cin>>n){
ac.init();
for(int i=1;i<=n;i++){
cin>>word[i];
ac.insert(word[i],i);
}
int m;
ac.build();
// ac.debug();
cin>>buf;
ac.query(buf,n);
}
return 0;
}

D.ZOJ3430 Detect the Virus

题意

给你N个病毒(大小不超过64bytes子串)然后给你M个长串(大小不超过2048byte)问你长串没处理中有几个没处理的子串;

注意:
这里的子串和长串都是经过题目要求处理后的串
处理要求是原本的串的每个字符都是二进制长度为八位,然后合并在一起六位二进制组成一个新的字符,如果末尾缺了两位就添加一个’=’字符
例如:假设ab的八位二进制为10101000 01100101
现在合并成101010 000110 0101
变成(F,P,E)
因为后面的0101缺少了两位所以补一个’=’所以最终字符串为FPE=;

解法

这题的难点在于把字符串根据题目要求模拟成变化前的样子,然后就是AC自动机板子题;
我们可以把开始的字符串去掉’=’然后把字符串的每个字符变成数字(这样好变成二进制的数字处理)然后把
1.第一个数字取后面六位,第二个数字取前面两位;</br >
2.第二个数字取后面四位,第三个数字取前面四位;</br >
3.第三个数字取后面两位,第四个数字取前面六位;</br >
这样就四个数字一组组成三个八位的数字;

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2048*10;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=512*64;
struct Trie{
int next[maxm][260],fail[maxm],end[maxm];
int used[1100];
int root,L;
int newnode(){
for(int i=0;i<260;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(int buf[],int id,int len){
//int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]]==-1)
next[now][buf[i]]=newnode();
now=next[now][buf[i]];
}
end[now]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<260;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<260;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void query(int buf[],int n,int len){
//int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]++;
}
temp=fail[temp];
}
}
int sum=0;
for(int i=1;i<=n;i++){
if(used[i]){
sum++;
}
}
cout<<sum<<endl;
}
void debug(){
for(int i=0;i<L;i++){
printf("id =%3d,fail = %3d,end = %3d ,chi = [",i,fail[i],end[i]);
for(int j=0;j<260;j++)
printf("%2d",next[i][j]);
printf("]\n");
}
}
};
int fun(char ch){
if(ch>='A'&&ch<='Z')return ch-'A';
if(ch>='a'&&ch<='z')return ch-'a'+26;
if(ch>='0'&&ch<='9')return ch-'0'+52;
if(ch=='+')return 62;
if(ch=='/')return 63;
}
char buf[maxn];
int aa[maxn];
int after[maxn];
int gao(int n){
int t=0; ///00011010->01101000
for(int i=0;i<n;i+=4){ ///00000110->00000000 按照题意合并成八位的01101000
aa[t++]=((after[i]<<2)|(after[i+1]>>4));///取前一个的后六位 和后一个数的前两位 组成一个新的八位字符
///因为前面i把i+1的前两个取走了,所有i+1现在只有六个中的后四个和i+2的前四个匹配;
if(i+2<n)aa[t++]=((after[i+1]<<4&0xff)|(after[i+2]>>2));
///因为前面i+1把i+2的前四个取走了,所有i+2现在只有六个中的两个和i+2的前六个匹配;
if(i+3<n)aa[t++]=((after[i+2]<<6&0xff)|(after[i+3]));
}
return t;
}
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int n;
while(cin>>n){
ac.init();
//cout<<"haha"<<endl;
for(int i=1;i<=n;i++){
cin>>buf;
// cout<<"here:?"<<endl;
int len=strlen(buf);
while(buf[len-1]=='=')len--;
for(int j=0;j<len;j++)after[j]=fun(buf[j]);
int k=gao(len);
ac.insert(aa,i,k);
}
int m;
ac.build();
cin>>m;
while(m--){
cin>>buf;
int len=strlen(buf);
while(buf[len-1]=='=')len--;
for(int j=0;j<len;j++)after[j]=fun(buf[j]);
int k=gao(len);
ac.query(aa,n,k);
}
cout<<endl;
}
return 0;
}

E.POJ2778 DNA Sequence

题意

给你N个病毒,问你有长度为L的DNA中有多少种是没有病毒的

思路

所有病毒串构造一个AC自动机,这个AC自动机可以看作一张有向图,图上的每个顶点就是Trie树上的结点,每个结点都可以看作是某个病毒串的前缀,Trie树的根则是空字符串。
而从根出发,在AC自动机上跑,经过k次转移到达某个结点,这个结点所代表的病毒串前缀可以看作长度为k的字符串的后缀,如果接下去跑往ATCG四个方向转移,就能到达新的结点,转移到新的长k+1字符串的后缀。
这样带着一个后缀状态的转移就能绕开病毒串,所以病毒串末尾的结点要标记,后缀存在病毒串的结点也要标记(这个在计算结点fail的时候就能处理),转移时就不能转移到被标记的结点。
接下来,题目的数据范围是10个长度10的病毒串,所以Trie树中最多101左右个结点,那么AC自动机整个转移就可以构建一张101*101的邻接矩阵,矩阵i行j列的权值是结点i转移到结点j的方案数。
而进行k次转移,从结点i转移到结点j的方案数是这个矩阵的k次幂,这个结论离散数学的图论有..
所以,长度n的字符串的方案数,就是转移n次根结点能到所有结点的方案和就是答案。就是计算矩阵的n次幂,统计根所在行的数字和,n的达到20亿用矩阵快速幂即可。

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
对于agc、c而言,如果我zou过 a-g-c-d 这个路径。
root
/ \
a c
/
g
/
c
/
d
由上面这个图可知 左边的d 和 右边的c都是危险节点。 但漏掉了左边上的c
所以如果fail指针指向那个节点是危险节点的话,那么当前节点也是危险节点
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
const ll mod=100000;
const int maxn=10*10+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct Matrix{
unsigned long long mat[maxn][maxn];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}
Matrix operator *(const Matrix &b)const{
Matrix ret=Matrix(n);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
ret.mat[i][j]+=(mat[i][k]*b.mat[k][j])%mod;
ret.mat[i][j]%=mod;
}
}
}
return ret;
}
unsigned long long pow_m(unsigned long long a,int n){
unsigned long long ret=1,tmp=a;
while(n){
if(n&1)ret*=tmp;
tmp*=tmp;
n>>=1;
}return ret;
}
Matrix pow_M(Matrix a,ll n){
Matrix ret=Matrix(a.n);
for(int i=0;i<a.n;i++)ret.mat[i][i]=1;
Matrix tmp=a;
while(n){
if(n&1)ret=ret*tmp;
tmp=tmp*tmp;
n>>=1;
}
return ret;
}
};
struct Trie{
int next[maxn][4],fail[maxn],id['Z'+1];
bool end[maxn];
int root,L;
int newnode(){
for(int i=0;i<4;++i)next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
id['A']=0;id['T']=1;id['C']=2;id['G']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][id[buf[i]]]==-1)next[now][id[buf[i]]]=newnode();
now=next[now][id[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]])end[now]=true;
for(int i=0;i<4;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
Matrix getMatrix(){
Matrix ret=Matrix(L);
for(int i=0;i<L;i++){
for(int j=0;j<4;j++){
if(end[next[i][j]]==false&&end[i]==false)
ret.mat[i][next[i][j]]++;
}
}
return ret;
}

};
Trie ac;
char buf[15];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
ll L;
cin>>n>>L;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
Matrix mat=ac.getMatrix();
mat=mat.pow_M(mat,L);
ll ans=0;
for(int i=0;i<mat.n;i++){
ans=(ans+mat.mat[0][i])%mod;
}
cout<<ans<<endl;
return 0;
}

F.HDU2243 考研路茫茫――单词情结

题意

跟上题一样,不过这题是让你求包括的个数,而且不仅仅是长度为L而且长度小于L的也要包括进去;可以通过上题很快求出不包括的,然后求和,在求出全部的26^1+…26^L的个数减去不包括的就是答案;

思路

通过矩阵求出全部长度的可能和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
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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll mod=2147483648LL;
const ll maxn=5*6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct Matrix{
unsigned long long mat[maxn][maxn];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}
Matrix operator *(const Matrix &b)const{
Matrix ret=Matrix(n);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
ret.mat[i][j]+=(mat[i][k]*b.mat[k][j]);
}
}
}
return ret;
}
unsigned long long pow_m(unsigned long long a,int n){
unsigned long long ret=1,tmp=a;
while(n){
if(n&1)ret*=tmp;
tmp*=tmp;
n>>=1;
}return ret;
}
Matrix pow_M(Matrix a,ll n){
Matrix ret=Matrix(a.n);
for(int i=0;i<a.n;i++)ret.mat[i][i]=1;
Matrix tmp=a;
while(n){
if(n&1)ret=ret*tmp;
tmp=tmp*tmp;
n>>=1;
}
return ret;
}
};
struct Trie{
int next[maxn][26],fail[maxn];
bool end[maxn];
int root,L;
int newnode(){
for(int i=0;i<26;++i)next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]])end[now]=true;
for(int i=0;i<26;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
Matrix getMatrix(){
Matrix ret=Matrix(L+1);
for(int i=0;i<L;i++){
for(int j=0;j<26;j++){
if(end[next[i][j]]==false)
ret.mat[i][next[i][j]]++;
}
}
for(int i=0;i<L+1;i++)ret.mat[i][L]=1;
return ret;
}
};
Trie ac;
char buf[15];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
ll L;
while(cin>>n>>L){
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
Matrix mat=ac.getMatrix();
mat=mat.pow_M(mat,L);
ull res=0;
for(int i=0;i<mat.n;i++)res+=mat.mat[0][i];
res--;
ull ans=0;
Matrix all=Matrix(2);all.mat[0][0]=26;all.mat[0][1]=all.mat[1][1]=1;
/*
26 1 × Sn -> Sn+1
0 1 × 26 -> 26
*/
all=all.pow_M(all,L);
ans=all.mat[0][1]*26;

cout<<ans-res<<endl;
}
return 0;
}

G.POJ - 1625 Censored!

题意

n,m,p;n代表总共有n个字母,m代表字符串的长度为m,p代表病毒字符串的个数;题目让你求的是不包含病毒的字符串长度为m的个数为多少。

题解

较麻烦的题,但是不要被代码长度吓到了
先讲一讲思路,给出了可用字母和不可用单词,
那么首先想到将不可用单词建立AC自动机。
那么什么情况下会用到禁止使用的单词呢?
我们将Trie树中间被标记的点叫做危险结点。
在AC自动机中不断遍历,如果遇到危险结点,就是不合法的
考虑如何遍历,这里需要用到AC自动机建立失败指针的一个技巧:
若当前结点无儿子结点,把失败指针处的儿子拿过来。
如果当前结点有儿子结点,把失败指针处的危险标记拿过来,详细见代码。
这样就方便了后面找后继结点。

不过没有取模操作,需要大数。

大数不可以矩阵快速幂吗?内存不够……

使用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
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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll gcd(ll a,ll b){while(b){ll t=a%b;a=b;b=t;}return a;}
ll lcm(ll a,ll b){return a*b/__gcd(a,b);}
map<char,int>mp;
int N,M,P;
struct Matrix{
int mat[110][110];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}

};
struct Trie{
int next[110][256],fail[110];bool end[110];
int L,root;
int newnode(){
for(int i=0;i<256;i++){
next[L][i]=-1;
}
end[L++]=false;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][mp[buf[i]]]==-1)
next[now][mp[buf[i]]]=newnode();
now=next[now][mp[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<256;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]]==true)end[now]=true;
for(int i=0;i<256;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
Matrix getMatrix(){
Matrix res=Matrix(L);
for(int i=0;i<L;i++)
for(int j=0;j<N;j++)
if(end[next[i][j]]==false)res.mat[i][next[i][j]]++;
return res;
}
};
struct BigInt{
const static int mod=10000;
const static int DLEN=4;
int a[600],len;
BigInt(){
memset(a,0,sizeof(a));
len=1;
}
BigInt(int v){
memset(a,0,sizeof(a));
len=0;
do{
a[len++]=v%mod;
v/=mod;
}while(v);
}
BigInt(const char s[]){
memset(a,0,sizeof(a));
int L=strlen(s);
len=L/DLEN;
if(L%DLEN)len++;
int index=0;
for(int i=L-1;i>=0;i-=DLEN){
int t=0;
int k=i-DLEN+1;
if(k<0)k=0;
for(int j=k;j<=i;j++)
t=t*10+s[j]-'0';
a[index]=t;
}
}
BigInt operator +(const BigInt &b)const{
BigInt res;
res.len=max(len,b.len);
for(int i=0;i<res.len;i++){
res.a[i]+=((i<len)?a[i]:0)+((i<b.len)?b.a[i]:0);
res.a[i+1]+=res.a[i]/mod;
res.a[i]%=mod;
}
if(res.a[res.len]>0)res.len++;
return res;
}
BigInt operator *(const BigInt &b)const
{
BigInt res;
for(int i = 0; i < len;i++)
{
int up = 0;
for(int j = 0;j < b.len;j++)
{
int temp = a[i]*b.a[j] + res.a[i+j] + up;
res.a[i+j] = temp%mod;
up = temp/mod;
}
if(up != 0)
res.a[i + b.len] = up;
}
res.len = len + b.len;
while(res.a[res.len - 1] == 0 &&res.len > 1)res.len--;
return res;
}
void output()
{
printf("%d",a[len-1]);
for(int i = len-2;i >=0 ;i--)
printf("%04d",a[i]);
printf("\n");
}
};
char buf[1010];
BigInt dp[2][110];
Trie ac;
int main()
{
while(scanf("%d%d%d",&N,&M,&P)==3){
gets(buf);
gets(buf);
mp.clear();
int len=strlen(buf);
for(int i=0;i<len;i++)mp[buf[i]]=i;
ac.init();
for(int i=0;i<P;i++){
gets(buf);
ac.insert(buf);
}

ac.build();
Matrix a=ac.getMatrix();
int now=0;
dp[now][0]=1;
for(int i=1;i<a.n;i++)dp[now][i]=0;
for(int i=0;i<M;i++){
now^=1;
for(int j=0;j<a.n;j++)dp[now][j]=0;
for(int j=0;j<a.n;j++){
for(int k=0;k<a.n;k++){
if(a.mat[j][k]>0){
dp[now][k]=dp[now][k]+dp[now^1][j]*a.mat[j][k];
}
}
}
}
BigInt ans=0;
for(int i=0;i<a.n;i++)ans=ans+dp[now][i];

ans.output();
}
return 0;
}

H.HDU - 2825 Wireless Password

题意

给你一堆(不超过10个)长度不超过10的单词,然后问你长度为N句子并且包含K个不同单词的个数有多少个;

题解

先利用ac自动机求出各种状态,(总共不超过1010)个,然后dp枚举长度,状态,和已有个数,*已有个数可以用状态压缩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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[110][110][maxn];
struct Trie{
int next[110][26],fail[110],end[110];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]|=(1<<id);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
end[now]|=end[fail[now]];
for(int i=0;i<26;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
};
Trie ac;
int num[maxn];
int slove(int n,int m,int k){
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=0;i<n;i++){//长度
for(int j=0;j<ac.L;j++){//状态
for(int l=0;l<(1<<m);l++){//个数
if(dp[i][j][l]){
for(int p=0;p<26;p++){
int nowj=ac.next[j][p];
int nowl=l|(ac.end[nowj]);
dp[i+1][nowj][nowl]=(dp[i+1][nowj][nowl]+dp[i][j][l])%mod;
}
}
}
}
}
ll sum=0;
for(int i=0;i<ac.L;i++){
for(int j=0;j<(1<<m);j++){
if(num[j]>=k){
sum=(sum+dp[n][i][j])%mod;
}
}
}
return sum;
}
int n,m,k;
char buf[150];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(num,0,sizeof(num));
for(int i=0;i<maxn;i++){
for(int j=0;j<=10;j++){
if(i&(1<<j))num[i]++;
}
}
while(cin>>n>>m>>k){
if(n==0&&m==0&&k==0)break;
ac.init();
for(int i=0;i<m;i++){
cin>>buf;
ac.insert(buf,i);
}
ac.build();
cout<<slove(n,m,k)<<endl;
}
return 0;
}

I.HDU - 2296 Ring

题意

1
给定m个不同的单词,单词由小写字母组成,每个单词都有自身的价值。现在你要设计一个字符串,字符串的长度不能超过n。如果字符串里面包含了某个单词,就可以获得相应的价值,价值可以重复计算。单词可以相互重叠。

题解

模板AC自动机,不过加了个字符串储存 不过不知道这是不是就是传说中的trie图

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[55][1100];
string ss[55][1100];
int money[150];
int cmp(string a,string b){
if(a.length()==b.length())return a<b;
else return a.length()<b.length();
}
int mycmp(string a,string b){
if(a.length()==b.length())return a<b;
else return a.length()<b.length();
}
struct Trie{
int next[1100][26],fail[1010],end[1010];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id,int money){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]=money;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
end[now]+=end[fail[now]];
for(int i=0;i<26;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
string slove(int n,int m){
for(int i=0;i<=n;i++)for(int j=0;j<L;j++)ss[i][j]="";
dp[0][0]=0;
int ma=0;
for(int i=0;i<n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]!=-inf){
for(int k=0;k<26;k++){
if(next[j][k]!=-1){
int nex=next[j][k];
string nowstr=ss[i][j]+char(k+'a');
int nowmoney=dp[i][j]+end[nex];
if(nowmoney>dp[i+1][nex]||(nowmoney==dp[i+1][nex]&&mycmp(nowstr,ss[i+1][nex]))){
dp[i+1][nex]=nowmoney;
ss[i+1][nex]=nowstr;
if(dp[i+1][nex]>ma){
ma=dp[i+1][nex];
}
}
}
}
}
}
}
vector<string>ve;
for(int i=0;i<=n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]==ma){
ve.push_back(ss[i][j]);
}
}
}
sort(ve.begin(),ve.end(),cmp);
return ve[0];
}
};
Trie ac;
int n,m,k;
char buf[150][150];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
cin>>n>>m;
ac.init();
for(int i=0;i<m;i++)cin>>buf[i];
for(int i=0;i<m;i++)cin>>money[i],ac.insert(buf[i],i,money[i]);
memset(dp,-inf,sizeof(dp));
ac.build();
cout<<ac.slove(n,m)<<endl;
}
return 0;
}

J.HDU - 2457 DNA repair

题意

给你N个病毒串,然后给你一个长度为N的基因串,让你替换成没有病毒的基因串,求最小的替换次数(每次只能替换一个字符)

题解

DP I ,J I: 当前串的长度,J,结尾的位置

直接DP扫过去,用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
109
110
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[1050][1050];
char buf[1500];
struct Trie{
int next[1100][4],fail[1010];
bool end[1010];
char s['Z'+1];
int root,L;
int newnode(){
for(int i=0;i<4;i++)
next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
s['A']=0;s['G']=1;s['T']=2;s['C']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][s[buf[i]]]==-1)
next[now][s[buf[i]]]=newnode();
now=next[now][s[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]])end[now]=true;
for(int i=0;i<4;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void slove(int n){
memset(dp,inf,sizeof(dp));
dp[0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]!=inf){
for(int k=0;k<4;k++){
int nex=next[j][k];
if(end[nex])continue;
if(s[buf[i]]==k){
dp[i+1][nex]=min(dp[i+1][nex],dp[i][j]);
}
else{
dp[i+1][nex]=min(dp[i+1][nex],dp[i][j]+1);
}
}
}
}
}
int mi=inf;
for(int i=0;i<L;i++){
if(dp[n][i]<mi)mi=dp[n][i];
}
if(mi==inf)cout<<-1<<endl;
else cout<<mi<<endl;
}
};
Trie ac;
int n;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t=0;
while(cin>>n){
if(n==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<"Case "<<++t<<": ";
int len=strlen(buf);
ac.slove(len);
}
return 0;
}

K.ZOJ - 3228 Searching the String

题意

先给你一个字符串,然后给你若干个子串,0代表能够重叠,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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=600010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
char buf[100010];
int ans[maxn][2];
struct Trie{
int next[maxn][26],fail[maxn],deep[maxn],last[maxn];
int end[100010];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
deep[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
deep[next[now][buf[i]-'a']]=deep[now]+1;
now=next[now][buf[i]-'a'];
}
end[id]=now;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<26;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void slove(char buf[]){
for(int i=0;i<L;i++){
ans[i][0]=ans[i][1]=0;
last[i]=-1;
}
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
ans[temp][0]++;
if(i-last[temp]>=deep[temp]){
ans[temp][1]++;
last[temp]=i;
}
temp=fail[temp];
}
}
}

};
Trie ac;
int n;
char s[10];
int flag[100010];
void neww(){
int n, cas = 1;
while (scanf("%s", buf) != EOF) {
scanf("%d", &n);
ac.init();
for (int i = 0; i < n; i++) {
scanf("%d%s", &flag[i], s);
ac.insert(s, i);
}
ac.build();
ac.slove(buf);
printf("Case %d\n", cas++);
for (int i = 0; i < n; i++)
printf("%d\n", ans[ac.end[i]][flag[i]]);
printf("\n");
}
}
int main()
{
neww();
return 0;
}

L.HDU - 3341 Lost’s revenge

题意

给出N个模式串(len<=10)和一个目标长串(len<=40)求将目标串重新排列后所能包含的模式串个数

题解

首先重排列,所以目标串中的ACGT个数不能变化

刚开始想开个五维数组来存取状态,但是空间会爆炸;

所以搜了一下发现可以用模拟进制来把 ACGT的个数用一个数字表达

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=15000;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[505][maxn];
struct Trie{
int next[505][4],fail[505],end[505];
int ok['Z'];
int root,L;
int newnode(){
for(int i=0;i<4;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;ok['A']=0;ok['G']=1;ok['C']=2;ok['T']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=ok[buf[i]];
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}end[now]++;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
end[now]+=end[fail[now]];
for(int i=0;i<4;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int slove(char buf[]){
int len=strlen(buf);
int num[4]={0,0,0,0};
memset(dp,-inf,sizeof(dp));
for(int i=0;i<len;i++){
num[ok[buf[i]]]++;
}
dp[0][0]=0;
int bit[4];
bit[0]=1;
bit[1]=(num[0]+1);
bit[2]=(num[1]+1)*(num[0]+1);
bit[3]=(num[2]+1)*(num[1]+1)*(num[0]+1);
for(int i=0;i<=num[0];i++)
for(int j=0;j<=num[1];j++)
for(int k=0;k<=num[2];k++)
for(int l=0;l<=num[3];l++){
int nowsta=i*bit[0]+j*bit[1]+k*bit[2]+l*bit[3];
for(int p=0;p<L;p++){
if(dp[p][nowsta]!=inf){
for(int kk=0;kk<4;kk++){
if(i==num[0]&&kk==0)continue;
else if(j==num[1]&&kk==1)continue;
else if(k==num[2]&&kk==2)continue;
else if(l==num[3]&&kk==3)continue;
int nexsta=nowsta+bit[kk];
int nex=next[p][kk];
dp[nex][nexsta]=max(dp[nex][nexsta],dp[p][nowsta]+end[nex]);
}
}
}
}
int ans=num[0]*bit[0]+num[1]*bit[1]+num[2]*bit[2]+num[3]*bit[3];
int ma=0;
for(int i=0;i<L;i++)ma=max(dp[i][ans],ma);
return ma;
}
};
char buf[45];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
int cnt=1;
while(cin>>n){
if(n==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<"Case "<<cnt++<<": ";
cout<<ac.slove(buf)<<endl;
}
return 0;
}

M.HDU - 3247 Resource Archiver

题意

给定n个文本串,m个病毒串,文本串重叠部分可以合并,但合并后不能含有病毒串,问所有文本串合并后最短多长。

思路

把病毒和文本出串塞入AC自动机中,然后把每个文本串的结点 和另一个文本串的结点的最短路求出来,在用状态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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=60000+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct Trie{
int next[maxn][2],fail[maxn];
int end[maxn],dp[1025][11];
int mat[11][11];
int pos[11];
int dis[maxn];
int root,L;
int cnt;
int newnode(){
for(int i=0;i<2;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=buf[i]-'0';
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
if(id==-1||end[now]==-1)end[now]=-1;
else end[now]|=(1<<id);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<2;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]]==-1)end[now]=-1;
else end[now]|=end[fail[now]];
for(int i=0;i<2;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void bfs(int id){
queue<int>Q;
Q.push(pos[id]);
memset(dis,-1,sizeof(dis));
dis[pos[id]]=0;
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<2;i++){
if(end[next[now][i]]>=0&&dis[next[now][i]]<0){
dis[next[now][i]]=dis[now]+1;
Q.push(next[now][i]);
}
}
}
for(int i=0;i<cnt;i++)
mat[id][i]=dis[pos[i]];
return;
}
void slove(int n){
pos[0]=0;
cnt=1;
for(int i=0;i<L;i++)
if(end[i]>0)pos[cnt++]=i;
for(int i=0;i<cnt;i++){
bfs(i);
}
for(int i=0;i<(1<<n);i++)
for(int j=0;j<cnt;j++)
dp[i][j]=inf;
dp[0][0]=0;
for(int i=0;i<(1<<n);i++)
for(int j=0;j<cnt;j++)
if(dp[i][j]!=inf){
for(int k=0;k<cnt;k++){
if(mat[j][k]<0)continue;
dp[i|end[pos[k]]][k]=min(dp[i|end[pos[k]]][k],dp[i][j]+mat[j][k]);
}
}
int ans=inf;
for(int i=0;i<cnt;i++)ans=min(ans,dp[(1<<n)-1][i]);
cout<<ans<<endl;
return;
}
};
char buf[55000];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,m;
while(cin>>n>>m){
if(n==0&&m==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf,i);
}
for(int i=0;i<m;i++){
cin>>buf;
ac.insert(buf,-1);
}
ac.build();
ac.slove(n);
}
return 0;
}

N.ZOJ - 3494 BCD Code

题意

跟数位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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+9;
const int maxn=2200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
ll dp[300][maxn];
int num[300];
char nu[10][5]={"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001"};
struct Trie{
int next[maxn][2],fail[maxn];
bool end[maxn];
int root,L;
int newnode(){
for(int i=0;i<2;i++)next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=buf[i]-'0';
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<2;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]])end[now]=true;
for(int i=0;i<2;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
ll dfs(int pos,int in,bool zero,bool limit){
if(pos==-1)return 1;
if(dp[pos][in]!=-1&&!limit)return dp[pos][in];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
int now=in;
if(zero&&i==0)ans=(ans+dfs(pos-1,in,zero&&i==0,limit&&i==up))%mod;
else{
bool flag=0;
for(int j=0;j<4;j++){
int ne=nu[i][j]-'0';
now=next[now][ne];
if(end[now]){
flag=1;break;
}
}
if(!flag)ans=(ans+dfs(pos-1,now,zero&&i==0,limit&&i==up))%mod;
}
}
if(!limit&&!zero)dp[pos][in]=ans;
return ans;
}
ll Ac(char buf[]){
int len=strlen(buf);
for(int i=0;i<len;i++){
num[i]=int(buf[len-1-i]-'0');
}
return dfs(len-1,0,true,true);
}
};

char buf[22];
Trie ac;
char a[250],b[250];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
ac.init();
int n;
cin>>n;
memset(dp,-1,sizeof(dp));
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>a>>b;
int len=strlen(a);
while(a[len-1]=='0'){
a[len-1]='9';
len--;
}
a[len-1]--;
cout<<((ac.Ac(b)-ac.Ac(a)+mod)%mod)<<endl;
}
return 0;
}

O.HDU - 4758 Walk Through Squares

题意

给你一个n*m的网格,现在你要从(1,1)走到(n,m),每次只能向右走或者向下走,走完后会形成一个包含R,D的序列,这个序列必须要包含题目给出的两个字符串,问有多少种方案。

题解

由于要从(1,1)走到(n,m),所以这个形成的字符串包含R和D的数量是一定的。

现在考虑dp i j k sta,表示包含两种串的组合状态,走了i个D,走了j个R,走到了k这个AC自动机的节点的方案数,串1和串2的状态sta 然后dp一下就行了。复杂度为O(3×n×m×len(s1)×len(s2))

按理说应该是N个D M个R 可是这样我就错了,换一下才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
109
110
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[110][110][210][4];
struct Trie{
int next[maxn][2],fail[maxn];
int end[maxn];
int ID['Z'+1];
int root,L;
int newnode(){
for(int i=0;i<2;i++)next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
ID['R']=0;ID['D']=1;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=ID[buf[i]];
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
end[now]=(1<<id);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<2;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
end[now]|=end[fail[now]];
for(int i=0;i<2;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void slove(int n,int m){
memset(dp,0,sizeof(dp));
dp[0][0][0][0]=1;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<L;k++)
for(int sta=0;sta<(1<<2);sta++){
if(dp[i][j][k][sta]){
for(int p=0;p<2;p++){
if(p==0){//右
dp[i+1][j][next[k][p]][sta|end[next[k][p]]]+=dp[i][j][k][sta];
dp[i+1][j][next[k][p]][sta|end[next[k][p]]]%=mod;
}
else{
dp[i][j+1][next[k][p]][sta|end[next[k][p]]]+=dp[i][j][k][sta];
dp[i][j+1][next[k][p]][sta|end[next[k][p]]]%=mod;
}
}
}
}
int ans=0;
for(int i=0;i<L;i++){
ans+=dp[n][m][i][3];
ans%=mod;
}
cout<<ans<<endl;
}
};

char buf[110];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
ac.init();
for(int i=0;i<2;i++){
cin>>buf;
ac.insert(buf,i);
}
ac.build();
ac.slove(n,m);
}
return 0;
}

P.HDU - 4511 小明系列故事――女友的考验

题意中文题

题解

设dp i j 表示到第i个点,自动机状态到j的最小步数,注意的是因为我们是根据不合法的路径构造自动机的,所以我们是不能走到某一条路径的末尾的,根据这点来记录我们的终止条件

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const double inf=1e20;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
pp p[101];
double dp[55][1000];
double dis(pp a,pp b){
return sqrt((double)(1.0*a.first-b.first)*(double)(1.0*a.first-b.first)+(double)(1.0*a.second-b.second)*(double)(1.0*a.second-b.second));
}
int n;
struct Trie{
int next[maxn][55],fail[maxn];
int end[maxn];
int root,L;
int newnode(){
for(int i=1;i<=n;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(int buf[],int k){
int now=root;
for(int i=0;i<k;i++){
if(next[now][buf[i]]==-1){
next[now][buf[i]]=newnode();
}
now=next[now][buf[i]];
}
end[now]=1;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=1;i<=n;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
end[now]|=end[fail[now]];
for(int i=1;i<=n;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void slove(){
for(int i=1;i<=n;i++)for(int j=0;j<L;j++)dp[i][j]=inf;
dp[1][next[root][1]]=0;
for(int i=1;i<n;i++)
for(int j=0;j<L;j++)
if(dp[i][j]!=inf){
for(int k=i+1;k<=n;k++){
int now=next[j][k];
if(end[now])continue;
dp[k][now]=min(dp[k][now],dp[i][j]+dis(p[i],p[k]));
}
}
double ans=inf;
for(int i=0;i<L;i++)ans=min(ans,dp[n][i]);
if(ans==inf)cout<<"Can not be reached!"<<endl;
else printf("%.2f\n",ans);
}
};

int buf[110];
Trie ac;
int main()
{
/*std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);*/
int m;
while(cin>>n>>m){
if(n==0&&m==0)break;
for(int i=1;i<=n;i++)cin>>p[i].first>>p[i].second;
ac.init();
while(m--){
int k;
cin>>k;
for(int i=0;i<k;i++)cin>>buf[i];
ac.insert(buf,k);
}
ac.build();
ac.slove();
}
return 0;
}

Q.附加题1 牛客网暑期ACM多校训练营(第九场)- Typing practice

题意

有n(n<=4)个长度为len的字符串,以及一个长度为len2的操作串。每一次你将选取操作串中长度为i(0<=i<=len2)的前缀,问你最少在这个前缀后加多少个字符,使得新字符串的后缀中能够至少出现这n个字符串中的一个。

题解

因为题目中设计多个串的匹配一个长串的问题,我们可以考虑使用AC自动机进行处理。
再考虑题目中要求我们求出匹配串的后缀凑出模式串的最小长度,因此,我们可以将这样的一个问题转化成:在一个Trie图中,处于第i个结点的字符到达任意一个模式串终点j的最短路。
因此,我们只需要利用AC自动机,将利用失配指针的性质,先将整张Trie图建立出来,并将每一个结点到达任意终点的最短路用bfs求出即可。

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=450000;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}

struct Trie{
int next[maxn][26],fail[maxn];
bool end[maxn];
int ans[maxn];
vector<int>ve[maxn];
set<int>st;
int root,L;
int newnode(){
for(int i=0;i<26;i++)next[L][i]=-1;
ans[L]=inf;
end[L++]=false;
return L-1;
}
void init(){
L=0;
st.clear();
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1){
next[now][buf[i]-'a']=newnode();
}
now=next[now][buf[i]-'a'];
}
end[now]=true;
ans[now]=0;
st.insert(now);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]]){
end[now]=true;
ans[now]=0;
st.insert(now);
}
for(int i=0;i<26;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void slove(char buf[]){
for(int i=0;i<L;i++){
for(int j=0;j<26;j++){
ve[next[i][j]].push_back(i);
}
}
queue<int>q;
for(auto i:st){
q.push(i);
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<ve[now].size();i++){
if(ans[ve[now][i]]>ans[now]+1){
ans[ve[now][i]]=min(ans[ve[now][i]],ans[now]+1);
q.push(ve[now][i]);
}
}
}
int len=strlen(buf);
stack<int>ss;
int now=root;
cout<<ans[now]<<endl;
for(int i=0;i<len;i++){
if(buf[i]=='-'&&ss.empty()){
now=root;
}
else if(buf[i]=='-'){
now=ss.top();
ss.pop();
}
else{
ss.push(now);
now=next[now][buf[i]-'a'];
}
cout<<ans[now]<<endl;
}
}
};
char buf[110000];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
cin>>n;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
ac.slove(buf);
return 0;
}

「kuangbin带你飞」专题十五 数位DP

Diposting di 2019-04-24 | Edited on 2018-09-18

传送门

A.CodeForces - 55D Beautiful numbers

题意

一个正整数是 漂亮数 ,当且仅当它能够被自身的各非零数字整除。我们不必与之争辩,只需计算给定范围中有多少个漂亮数。

思路

因为问你的是一段区间内有多少数能整除他的所有非零数位

1-9,1,一定能被任何正整数整除,1-9的最小公倍数为2520

而1-2520中真正是1-9中的最小公倍数的只有48个

dp i j k:因为dp25,2520,[2520]开不下,所以我们要进行适当离散化

index[]数组标记1-2520,哪些是真正的最小公倍数,是第几个

所以dp[i][j][k]:长度为i的数,该数对2520取模为j,它的数位和的最小公倍数是第k个->index[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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const int mod=2520;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){if(b==0)return a;else return gcd(b,a%b);}
int lcm(int a,int b){return (a*b)/gcd(a,b);}
int islcm[mod+10];
int num[200];
ll dp[200][2550][50];
void init(){
int num=0;
for(int i=1;i<=2520;i++){
if(2520%i==0)islcm[i]=++num;
}
}
ll dfs(int pos,int nownum,int nowlcm,bool limit){
if(pos==-1)return nownum%nowlcm==0;
if(!limit&&dp[pos][nownum][islcm[nowlcm]]!=-1)return dp[pos][nownum][islcm[nowlcm]];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
int nowsum=(nownum*10+i)%mod;
int nowl=nowlcm;
if(i)nowl=lcm(nowl,i);
ans+=dfs(pos-1,nowsum,nowl,limit&&i==num[pos]);
}
if(!limit)dp[pos][nownum][islcm[nowlcm]]=ans;
return ans;
}
ll ac(ll x){
ll pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,1,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
init();
int t;
cin>>t;
while(t--){
ll n,m;
cin>>n>>m;
cout<<ac(m)-ac(n-1)<<endl;
}
return 0;
}

B.HDU - 4352 XHXJ’s LIS

题意

问你一个longlong范围内(a,b)中每一位的数字组成的最长严格递增子序列(LIS)长度为K的个数

题意

首先 关系到数字和位数 数位DP

然后LIS 最长递增子序列利用二分查找当前递增子序列中是否有大于它的数,如果有替换最小的哪一个,如果没有就加入长度+1;可以利用状态压缩保存当前的最长递增子序列的类型;因为数字最多十个所以类型最多长度为10,用二进制的1表示当前位数的这个数在最长递增子序列中;

然后注意前导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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int k;
int num[21];
ll dp[21][1<<11][11];
int numcnt(int x){
int cnt=0;
for(int i=0;i<10;i++){
if(x&(1<<i))cnt++;
}
return cnt;
}
int numchange(int x,int p){
for(int i=p;i<=9;i++){
if(x&(1<<i))return (x^(1<<i))|(1<<p);
}
return x|(1<<p);
}
ll dfs(int pos,int nownum,bool limit,bool zero){
if(pos==-1)return numcnt(nownum)==k;
if(!limit&&dp[pos][nownum][k]!=-1)return dp[pos][nownum][k];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,(zero&&i==0)?0:numchange(nownum,i),limit&&i==num[pos],zero&&i==0);
}
if(!limit)dp[pos][nownum][k]=ans;
return ans;
}
ll ac(ll x){
ll pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,true,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
ll a,b;
int cnt=0;
while(t--){
cin>>a>>b>>k;
cout<<"Case #"<<++cnt<<": "<<ac(b)-ac(a-1)<<endl;
}
return 0;
}

C.HDU - 2089 不要62

题意

中文题

思路

数位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
50
51
52
53
54
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[20];
int dp[20][2];
int dfs(int pos,int pre,int sta,bool first){
if(pos==-1)return 1;
if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
int up=first?num[pos]:9;
int ans=0;
for(int i=0;i<=up;i++){
if(pre==6&&i==2)continue;
if(i==4)continue;
ans+=dfs(pos-1,i,i==6,first&&i==num[pos]);
}
if(!first)dp[pos][sta]=ans;
return ans;
}
int ac(int x){
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,m;
memset(dp,-1,sizeof(dp));
while(cin>>n>>m&&(n&&m)){
cout<<ac(m)-ac(n-1)<<endl;
}
return 0;
}

D.HDU - 3555 Bomb

题意

求1-n中出现49的数的个数,不要62的反例

思路1

不要62的反解 注意ac(n)算的是0-n之前的数其中包括了0 因为多减去了一个0所以结果要再加上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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[50];
ll dp[50][2];
ll dfs(int pos,int pre,int sta,bool first){
if(pos==-1)return 1;
if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
int up=first?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
if(pre==4&&i==9)continue;
ans+=dfs(pos-1,i,i==4,first&&i==num[pos]);
}
if(!first)dp[pos][sta]=ans;
return ans;
}
ll ac(ll x){
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
ll n;
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
while(t--){
cin>>n;
cout<<n-ac(n)+1<<endl;
}
return 0;
}

思路2

正着做 DP[第几个数]//[这个数这个位是不是4]///[这个数前面存不存在49]

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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[200];
ll dp[200][2][2];
ll dfs(int pos,int pre,int sta,bool first,bool ok){
if(pos==-1){
if(ok)return 1;
else
return 0;
}
if(!first&&dp[pos][sta][ok]!=-1)return dp[pos][sta][ok];
int up=first?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,i,i==4,first&&i==num[pos],pre==4&&i==9||ok);
}
if(!first)dp[pos][sta][ok]=ans;
return ans;
}
ll ac(ll x){
ll pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true,false);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
while(t--){
ll n;
cin>>n;
cout<<ac(n)<<endl;
}
return 0;
}

E.POJ - 3252 Round Numbers

题意

求L到R中二进制里面0的数量大于等于1的数量的个数

思路

二进制的数位DP,但是注意这个有关前导0,如果有前导零的话遇到0就不能加上0的数量;

dp【第几位】【0的数量】【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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[200];
ll dp[200][200][200];
ll dfs(int pos,int num0,int num1,bool limit,bool zero){
if(pos==-1)return num0>=num1;
if(!limit&&dp[pos][num0][num1]!=-1)return dp[pos][num0][num1];
int up=limit?num[pos]:1;
ll ans=0;
for(int i=0;i<=up;i++){
if(i)ans+=dfs(pos-1,num0,num1+1,limit&&i==num[pos],zero&&i==0);
else ans+=dfs(pos-1,num0+(zero?0:1),num1,limit&&i==num[pos],zero&&i==0);
}
if(!limit)dp[pos][num0][num1]=ans;
return ans;
}
ll ac(ll x){
ll pos=0;
while(x){
num[pos++]=x&1;
x/=2;
}
return dfs(pos-1,0,0,true,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
ll n,m;
cin>>n>>m;
cout<<ac(m)-ac(n-1)<<endl;
return 0;
}

F.HDU - 3709 Balanced Number

题意

求范围内的平衡数字的个数,从数字到枢轴的距离是它与枢轴之间的偏移。然后可以计算左右部分的扭矩。如果它们是相同的,它是平衡的。平衡数字必须与其某些数字处的枢轴平衡。例如,4139是一个平衡数字,其中枢轴固定为3.对于左侧部分和右侧部分,扭矩分别为4 2 + 1 1 = 9和9 * 1 = 9。

思路

枚举枢轴的位置(1-len)然后和加起来,易知一个数最多只能有一个枢轴,假设有一个枢轴 后移动枢轴位置的,那么 平衡数字不可能会变成0.

然后再减去前导0的数比如00 000 0000

如果中间的平衡数的值小于0了那么这个数的这个位置为枢轴的肯定没有平衡数;比如前面已经4359以3为枢轴 左边是4*1 右边是5 ->4-5=-1 在过去值肯定比-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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[21];
ll dp[21][21][2200];
ll dfs(int pos,int on,int nownum,bool limit){
if(pos==-1)return nownum==0;
if(nownum<0)return 0;
if(!limit&&dp[pos][on][nownum]!=-1)return dp[pos][on][nownum];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,on,nownum+(pos+1-on)*i,limit&&i==num[pos]);
}
if(!limit)dp[pos][on][nownum]=ans;
return ans;
}
ll ac(ll x){
if(x<0)return 0;
ll pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
ll ans=0;
for(int i=1;i<=pos;i++)ans+=dfs(pos-1,i,0,1);
return ans-(pos-1);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
ll a,b;
int cnt=0;
while(t--){
cin>>a>>b;
cout<<ac(b)-ac(a-1)<<endl;
}
return 0;
}

G.HDU - 3652 B-number

题意

要13并且能被13整除的数;模板题,要标记当前余的数和前面的那个数!!和是否有13

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[11];
int dp[11][15][2][15];
int dfs(int pos,int pre,bool flag,int nownum,bool limit){
if(pos==-1)return (flag)&&(nownum==0);
if(dp[pos][nownum][flag][pre]!=-1&&!limit)return dp[pos][nownum][flag][pre];
int up=limit?num[pos]:9;
int ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,i,flag||(pre==1&&i==3),(nownum*10+i)%13,limit&&(i==num[pos]));
}
if(!limit)dp[pos][nownum][flag][pre]=ans;
return ans;
}
int ac(int x){
int ans=0;
while(x){
num[ans++]=x%10;
x/=10;
}
return dfs(ans-1,0,false,0,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
memset(dp,-1,sizeof(dp));
while(cin>>n){
cout<<ac(n)<<endl;
}
return 0;
}

H.HDU - 4734 F(x)

题意

一个A 一个B,给出一个F(x)的定义,让你求出0-B中有多少个F(x)<=F(a)

思路

一开始是想直接存每个数的F(x)然后和F(a)比较,不过这样就不能记忆化搜索了,后来发现直接存F(a)-F(x)的差值就行了如果差值小于0就直接返回 ,而且F(a)的最大值只有20736左右

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;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[11];
int dp[11][20736];
int dfs(int pos,bool limit,int sum){
if(pos==-1)return sum>=0;
if(sum<0)return 0;
if(dp[pos][sum]!=-1&&!limit)return dp[pos][sum];
int up=limit?num[pos]:9;
int ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,limit&&(i==num[pos]),sum-i*(1<<pos));
}
if(!limit)dp[pos][sum]=ans;
return ans;
}
int ac(int x,int sum){
int ans=0;
while(x){
num[ans++]=x%10;
x/=10;
}
return dfs(ans-1,true,sum);
}
int getA(int x){
int ans=0;
while(x){
num[ans++]=x%10;
x/=10;
}
int sum=0;
for(int i=0;i<ans;i++){
sum+=(1<<i)*num[i];
}
return sum;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
int cnt=0;
while(t--){
int a,b;
cin>>a>>b;
int sum=getA(a);
cout<<"Case #"<<++cnt<<": ";
cout<<ac(b,sum)<<endl;
}
return 0;
}

I.ZOJ - 3494 BCD Code

题意

给定N个01串,再给定区间[a,b],问区间[a,b]里面有多少个数转化成BCD码之后不包含任何前面给出01串。1 <= A <= B <= 10^200

题解

正好最近都在做AC自动机,一看到不包括前面的01串马上想到自动机….首先把前面的N个01串扔进AC自动机里面求出坏点,表示当前当前点可以走那些数字,然后判断0-9的BCD码有没有接触到不能走的点

注意数字太大要用字符串存取

这题真有趣,真好AC自动机和数位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
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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+9;
const int maxn=1500;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
struct Trie{
int next[2010][2],fail[2010];bool end[2010];
int L,root;
int newnode(){
for(int i=0;i<2;i++){
next[L][i]=-1;
}
end[L++]=false;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'0']==-1)
next[now][buf[i]-'0']=newnode();
now=next[now][buf[i]-'0'];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<2;i++)
if(next[root][i]==-1)next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
if(end[fail[now]]==true)end[now]=true;
for(int i=0;i<2;i++)
if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}

}
};
Trie ac;
int num[250];
ll dp[250][2005];
ll dfs(int pos,int sta,bool limit,bool zero){
if(pos==-1)return !zero;
if(!zero&&!limit&&dp[pos][sta]!=-1)return dp[pos][sta];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
if(zero&&i==0)ans=(ans+dfs(pos-1,sta,limit&&i==up,zero&&i==0)+mod)%mod;
else{
int nex=sta,x=i;
bool flag=true;
for(int j=(1<<3);j;j>>=1){
int bit=x/j;x=x-bit*j;
nex=ac.next[nex][bit];
if(ac.end[nex]){
flag=false;break;
}
}
if(flag)ans=(ans+dfs(pos-1,nex,limit&&i==up,zero&&i==0)+mod)%mod;
}
}
if(!zero&&!limit)dp[pos][sta]=ans;
return ans;
}
ll acc(char x[]){
int len=strlen(x);
for(int i=0;i<len;i++)num[i]=x[len-1-i]-'0';
return dfs(len-1,0,true,true);
}
char str[220];
char st[220],ed[220];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
ac.init();
memset(dp,-1,sizeof(dp));
for(int i=0;i<n;i++){
cin>>str;
ac.insert(str);
}
ac.build();
cin>>st>>ed;
int lena=strlen(st),p=lena-1;
while(st[p]=='0')st[p--]='9';
st[p]=st[p]-1;
cout<<((acc(ed)-acc(st)+mod)%mod)<<endl;
}
return 0;
}

J.HDU - 4507 吉哥系列故事――恨7不成妻

题意

求指定范围内与7不沾边的所有数的平方和。结果要mod 10^9+7

思路

与7不沾边的数需要满足三个条件。

①不出现7

②各位数和不是7的倍数

③这个数不是7的倍数

这三个条件都是基础的数位DP。

但是这题要统计的不是符合条件个数,而是平方和。

也就是说在DP时候,要重建每个数,算出平方,然后求和。

需要维护三个值(推荐使用结构体), 假定dfs推出返回的结构体是next,当前结果的结构体是ans

①符合条件数的个数 cnt

②符合条件数的和 sum

③符合添加数的平方和 sqsum

其中①是基础数位DP。②next.sum+(10^len×i)×ans.cnt,其中(10^len×i)×ans.cnt代表以len为首位的这部分数字和。

③首先重建一下这个数,(10^len×i+x),其中x是这个数的后面部分,则平方和就是(10^len×i)^2+x^2+2×10^len×i×x,其中x^2=next.sqsum

整体还要乘以next.cnt,毕竟不止一个。

这样sqsum+=next.sqsum

sqsum+=(2×10^len×i×x)×next.cnt=(2×10^len×i)×next.sum(神奇的化简)

sqsum+=(10^len×i)^2×next.cnt

mod之后统计函数也有个小陷阱,那就是f(r)在mod之后有可能小于f(l-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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[20];
ll ten[20];
struct node{
ll cnt,sqr,sum;
bool vis;
node(ll a=0,ll b=0,ll c=0,bool d=0):cnt(a),sqr(b),sum(c),vis(d){}
}dp[20][11][11];
node dfs(int pos,ll mod1,ll mod2,bool limit){
if(pos==-1){
if(mod1%7==0||mod2%7==0)return node(0);
return node(1);
}
if(dp[pos][mod1][mod2].vis&&!limit)return dp[pos][mod1][mod2];
int up=limit?num[pos]:9;
node now;
for(int i=0;i<=up;i++){
if(i==7)continue;
node tmp=dfs(pos-1,(mod1+i)%7,(mod2*10+i)%7,limit&&i==num[pos]);
ll aa=i*ten[pos]%mod;
now.cnt=(now.cnt+tmp.cnt)%mod;
now.sum=(now.sum+aa*tmp.cnt%mod+tmp.sum)%mod;
now.sqr=(aa*aa%mod*tmp.cnt%mod+2*aa*tmp.sum%mod+tmp.sqr+now.sqr)%mod;
}
if(!limit){
dp[pos][mod1][mod2]=now;
dp[pos][mod1][mod2].vis=1;
}
return now;
}
ll ac(ll x){
int ans=0;
while(x){
num[ans++]=x%10;
x/=10;
}
node ret=dfs(ans-1,0,0,1);
return ret.sqr%mod;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
ten[0]=1;for(int i=1;i<=18;i++)ten[i]=ten[i-1]*10;
while(t--){
ll a,b;
cin>>a>>b;
cout<<(ac(b)-ac(a-1)+mod)%mod<<endl;
}
return 0;
}

K.SPOJ - BALNUM Balanced Numbers

题意

问[L, R]内有多少数字,满足每个奇数都出现了偶数次,每个偶数都出现了奇数次(没有出现的数不考虑)

题解1

用三进制来表示状态,0表示没出现,1表示出现奇数次,2表示出现偶数次。

然后就是裸的数位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
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<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[25];
int three[10];
ll dp[25][maxn];
int isok(int sta){
for(int i=0;i<=9;i++,sta/=3){
int k=sta%3;
if(!k)continue;
if((i&1)&&(k&1))return 0;
if(((i&1)==0)&&(k==2))return 0;
}
return 1;
}

int change(int sta,int i){
int nu[10];
for(int j=0;j<=9;j++,sta/=3)nu[j]=sta%3;
if(nu[i]==0)nu[i]=1;
else nu[i]=3-nu[i];
for(int j=9;j>=0;j--)sta=sta*3+nu[j];
return sta;
}
ll dfs(int pos,int sta,bool first,bool zero){
if(pos==-1)return isok(sta);
if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
int up=first?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
if(zero&&i==0)
ans+=dfs(pos-1,sta,first&&i==num[pos],zero&&i==0);
else
ans+=dfs(pos-1,change(sta,i),first&&i==num[pos],zero&&i==0);
}
if(!first)dp[pos][sta]=ans;
return ans;
}
ll ac(ll x){
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,true,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
while(t--){
ll m,n;
cin>>n>>m;
cout<<ac(m)-ac(n-1)<<endl;
}
return 0;
}

解法2

可以多出一阶存1-9是否出现过 不过数组不能开到1<<11 要开到1<<10+50左右不然会超时

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
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1024+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[25];
ll dp[25][maxn][maxn];
int isok(int sta,int haha){
for(int i=0;i<=9;i++){
if((haha&(1<<i))==0)continue;
if(i&1){//奇数出现偶数次
if(sta&(1<<i)){
return 0;
}
}
else{
if((sta&(1<<i))==0){
return 0;
}
}
}
return 1;
}
int change(int sta,int i){
return sta^(1<<i);
}
ll dfs(int pos,int sta,int haha,bool first,bool zero){
if(pos==-1)return isok(sta,haha);
if(!first&&dp[pos][sta][haha]!=-1)return dp[pos][sta][haha];
int up=first?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
ll now=change(sta,i);
if(zero&&i==0)
ans+=dfs(pos-1,sta,haha,first&&i==num[pos],zero&&i==0);
else
ans+=dfs(pos-1,change(sta,i),haha|(1<<i),first&&i==num[pos],zero&&i==0);
}
if(!first)dp[pos][sta][haha]=ans;
return ans;
}
ll ac(ll x){
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,0,true,true);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(dp,-1,sizeof(dp));
int t;
cin>>t;
while(t--){
ll m,n;
cin>>n>>m;
cout<<ac(m)-ac(n-1)<<endl;
}
return 0;
}

「kuangbin带你飞」专题十四 数论基础

Diposting di 2019-04-24 | Edited on 2019-02-17

传送门

A - Bi-shoe and Phi-shoe(欧拉函数的性质)

题意

给出一些数字,对于每个数字找到一个欧拉函数值大于等于这个数的数,求找到的所有数的最小和。

思路

考察了欧拉函数的简单性质,即满足欧拉函数(k)>=N的最小数为N+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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2e6+1000;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int phi[maxn];
int prime[maxn];
int tot;
vector<int>ppp;
void phi_table(int n){
phi[1]=1;
tot=0;
for(int i=2;i<=n;i++){
if(!check[i]){prime[tot++]=i;phi[i]=i-1;ppp.push_back(i);}
for(int j=0;j<tot;j++){
if(i*prime[j]>n)break;
check[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
phi_table(2000000);
int t;
int cnt=1;
cin>>t;
while(t--){
int n;cin>>n;
vector<int>ve;
ll ans=0;
for(int i=1;i<=n;i++){
int a;
cin>>a;
ans+=ppp[lower_bound(ppp.begin(),ppp.end(),a+1)-ppp.begin()];
}
cout<<"Case "<<cnt++<<": "<<ans<<" Xukha"<<endl;
}
return 0;
}

C - Aladdin and the Flying Carpet(唯一分解定理)

题意

给一对数字 a,b ,a是一个长方形的面积,问有多少种整数的边的组合可以组成面积为a的长方形,要求最短的边不得小于b

思路

唯一分解定理

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+1000;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int phi[maxn];
int prime[maxn];
int tot;
void phi_table(int n){
phi[1]=1;
tot=0;
for(int i=2;i<=n;i++){
if(!check[i]){prime[tot++]=i;phi[i]=i-1;}
for(int j=0;j<tot;j++){
if(i*prime[j]>n)break;
check[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
ll getnum(ll n){
ll ans=1,num=0;
for(int i=0;i<tot&&prime[i]*prime[i]<=n;i++){
if(n%prime[i]==0){
num=0;
while(n%prime[i]==0){
n/=prime[i];
num++;
}
ans*=(num+1);
}
}
if(n>1)ans*=2;
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
phi_table(1000000+50);
while(t--){
ll a,b;
cin>>a>>b;
if(a<=b*b){
cout<<"Case "<<cnt++<<": ";
cout<<0<<endl;
continue;
}
ll ans=getnum(a)/2;
for(ll i=1;i<b;i++){
if(a%i==0)ans--;
}
cout<<"Case "<<cnt++<<": ";
cout<<ans<<endl;
}
return 0;
}

D - Sigma Function (平方数和平方数×2的约数和是奇数 )

题意

求1-n中的因子和为偶数的个数是多少

思路

平方数和平方数*2的约数和是奇数

而且——-平方数*2的个数就等于sqrt(n/2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+1000;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
while(t--){
ll ans;
cin>>ans;
ans-=floor(sqrt(ans))+floor(sqrt(ans/2));
cout<<"Case "<<cnt++<<": ";
cout<<ans<<endl;
}
return 0;
}

E - Leading and Trailing(求 n^k 前3项和后3项)

思路

后三项不需要想就知道是快速幂了

但是前三项需要推一下

我们知道任意数可以转化成 X = 10^( x + y ) (x为整数,y为小数)

其中 10^x 来控制的是源数字 10 100.。。这样的东西,而具体这个数字等于多少,全靠10^y ,

那么 我们就可知道 10^y 就是我们要求的前n个数字还不会炸 long long (用double的话末尾消去,很适合)

这样我们就能保证前7位可知, 如果要前三位 只需要 10^(y) * 100 就好了。

由于这道题数据卡的不是太死。。限时 2s ,那么不用快速幂去搞前三位。。似乎没事。

fmod 是一个特殊函数 fmod(a,b) (a , b 为 浮点型) 得出的结果是 a / b 得出的结果的小数。。

距离 fmod( 4, 3 ) 结果为 0.3333333 ,那么我们这样 fmod( x , 1 ) 就是默认取他的小数点位

那么 对于 X^k = 10^x * 10^y

x + y = k lg X ,那么 y = fmod( klg X, 1.0 )

然后再*100就是前三位了。。。

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+1000;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int pow_mod(int x,int n,int mod){
int res=1;
while(n){
if(n&1)res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}

int main()
{
/*std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);*/
int t;
int cnt=1;
cin>>t;
while(t--){
int n,k;
cin>>n>>k;
int ans1=pow(10.0,fmod(k*log10(n*1.0),1))*100;
int ans2=pow_mod(n%1000,k,1000);
printf("Case %d: %03d %03d\n",cnt++,ans1,ans2);
}
return 0;
}

F - Goldbach`s Conjecture (线性筛)

题意

T组询问,每组询问是一个偶数n
验证哥德巴赫猜想
回答n=a+b
且a,b(a<=b)是质数的方案个数

思路

注意不要被卡内存就行

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e7+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int prime[700000];
int tot;
void getprime(){
tot=0;
for(int i=2;i<maxn;i++){
if(!check[i]){
prime[tot++]=i;
}
for(int j=0;j<tot;j++){
if(i*prime[j]>=maxn)break;
check[i*prime[j]]=true;
if(i%prime[j]==0)break;
}
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
getprime();
while(t--){
int n;
cin>>n;
int ans=0;
for(int i=0;i<tot&&prime[i]*2<=n;i++){
if(!check[n-prime[i]])ans++;
}
cout<<"Case "<<cnt++<<": ";
cout<<ans<<endl;
}
return 0;
}

G - Harmonic Number (II)(整除分块)

题意

求f(n)=n/1+n/2…..n/n,其中n/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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e7+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;

int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
while(t--){
ll n;
cin>>n;
ll ans=0;
for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}
cout<<"Case "<<cnt++<<": ";
cout<<ans<<endl;
}
return 0;
}

H - Pairs Forming LCM (唯一分解定理)

题意

求有多少组 ( i,j )
使 lcm(i, j) = n and (i ≤ j).
(1 ≤ n ≤ 10^14)

思路

图片来源

img

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e7+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int prime[1000000];
int tot;
void getprime(){
tot=0;
for(int i=2;i<maxn;i++){
if(!check[i]){
prime[tot++]=i;
}
for(int j=0;j<tot;j++){
if(i*prime[j]>=maxn)break;
check[i*prime[j]]=true;
if(i%prime[j]==0)break;
}
}
}
ll getnum(ll n){
ll ans,sum;
ans=0;
sum=1;
for(int i=0;i<tot&&prime[i]*prime[i]<=n;i++){
if(n%prime[i]==0){
ans=0;
while(n%prime[i]==0){
n/=prime[i];
ans++;
}
sum*=(2*ans+1);
}
}
if(n>1)sum*=3;
return sum;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
getprime();
while(t--){
ll n;
cin>>n;
cout<<"Case "<<cnt++<<": ";
cout<<getnum(n)/2+1<<endl;
}
return 0;
}

I - Harmonic Number (欧拉常数 /稀疏打表求调和级数)

题意

t组数据,每组一个n 求 1+1/2+1/3+1/4 ……+1/n的和

思路

直接100个一组打表求前1e7项

或者直接套公式

r=0.57721566490153286060651209(r就是欧拉常数)。

img

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
double num[maxn];
void init(){
num[0]=0;
num[1]=1.0;
double ans=1.0;
for(int i=2;i<=100000000;i++){
ans+=1.0/(i*1.0);
if(i%100==0)num[i/100]=ans;
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
init();
while(t--){
int n;
cin>>n;
int k=n/100;
double ans=num[k];
for(int i=k*100+1;i<=n;i++)ans+=1.0/(i*1.0);
cout<<"Case "<<cnt++<<": ";
cout<<fixed<<setprecision(10);
cout<<ans<<endl;
}
return 0;
}

J - Mysterious Bacteria (唯一分解定理,有符号整数只有31位,负数指数只能是奇数)

题意

给你一个整数n(可能为负数),让你求满足a^p=n的最大的p

思路

当n是正数时,直接对n进行素因子分解,在对它的素因子的个数进行gcd,比如12=2^2*3,gcd(2,1)就是最大的p;

当n是负数时,则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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
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;
for(int i=1;i<=prime[0]&&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 main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
getprime();
while(t--){
ll n;
cin>>n;
bool flag=0;
if(n<0){
flag=1;n=-n;
}
int tot=getfactors(n);
ll gc=factor[0][1];
for(int i=0;i<tot;i++){
gc=__gcd(gc,factor[i][1]);
}
if(flag){
while(gc%2==0)gc/=2;
}
cout<<"Case "<<cnt++<<": ";
cout<<gc<<endl;
}
return 0;
}

K - Large Division (同余模定理)

题意

给你一个大数A问你是否可以被一个b整除

思路

直接同余模

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;

int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;

while(t--){
string a;ll b;
cin>>a>>b;
ll ans=0;
if(b<0)b=-b;
int len=a.length();
for(int i=0;i<len;i++){
if(a[i]=='-')continue;
ans=(ans*10+a[i]-'0')%b;
}
cout<<"Case "<<cnt++<<": ";
if(ans==0)cout<<"divisible"<<endl;
else
cout<<"not divisible"<<endl;
}
return 0;
}

L - Fantasy of a Summation(快速幂)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll a[maxn];
ll pow_mod(ll a,ll n,ll mod){
ll res=1;
while(n){
if(n&1)res=(res*a)%mod;
a=(a*a)%mod;
n>>=1;
}
return res;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
while(t--){
int n;ll ans=0,mo,k;
cin>>n>>k>>mo;
for(int i=1;i<=n;i++)cin>>a[i],ans=(ans+a[i])%mo;
cout<<"Case "<<cnt++<<": ";
cout<<(ans*(pow_mod(n,k-1,mo)*k)%mo)%mo<<endl;
}
return 0;
}

M - Help Hanzo (大区间素数筛选)

题意

给出T个实例,T<=200,给出[a,b]区间,问这个区间里面有多少个素数?(1 ≤ a ≤ b < 231, b - a ≤ 100000)

思路

首先先把筛素数改成筛非素数

然后先用sqrt(b)里面的素数去筛a-b之间的非素数 剩下的就都是素数了

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
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;
}
}
}
bool flag[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;
getprime();
while(t--){
ll a,b;
cin>>a>>b;
memset(flag,0,sizeof(flag));
int dis=b-a;

for(int i=1;i<=prime[0]&&prime[i]*prime[i]<=b;i++){
for(int j=a/prime[i]*prime[i];j<=b;j+=prime[i])
if(j>=a&&j>prime[i])flag[j-a]=true;

}
ll ans=0;
for(int i=0;i<=dis;i++)
if(!flag[i])ans++;
if(a==1)ans--;
cout<<"Case "<<cnt++<<": ";
cout<<ans<<endl;
}
return 0;
}

N - Trailing Zeroes (III) (二分)

题意

N!后面有Q个0,给你Q,求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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e9+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;

int check(int mid){
int k=0;
while(mid){
k+=mid/5;
mid/=5;
}
return k;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int cnt=1;
cin>>t;

while(t--){
int q;
cin>>q;
int l=-1,r=maxn,ans=-1;
while(l<=r){
int mid=(l+r)/2;
int k=check(mid);
if(k==q){
ans=mid;
r=mid-1;
}
else if(k<q){
l=mid+1;
}
else if(k>q){
r=mid-1;
}
}
cout<<"Case "<<cnt++<<": ";
if(ans==-1)cout<<"impossible"<<endl;
else
cout<<ans<<endl;
}
return 0;
}

O - GCD - Extreme (II) (欧拉函数的应用)

题意

求sum(gcd(i,j),1<=i<j<=n)1<n<4000000

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=4e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
ll phi[maxn];
int prime[maxn];
int tot;
ll sum[maxn];
void phi_table(int n){
phi[1]=0;
tot=0;
for(int i=2;i<=n;i++){
if(!check[i]){prime[tot++]=i;phi[i]=i-1;}
for(int j=0;j<tot;j++){
if(i*prime[j]>n)break;
check[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(int i=1;i<=n;i++){
phi[i]+=phi[i-1];
sum[i]+=sum[i-1]+i;
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
phi_table(4000000);
int n;
while(cin>>n&&n){
ll ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=phi[(n/l)]*(sum[r]-sum[l-1]);
}
cout<<ans<<endl;
}
return 0;
}

R - 青蛙的约会 (exgcd)

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
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=4e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
///返回 d=gcd(a,b); 和对应于等式 ax+by=d 中的 x,y
ll extend_gcd(ll a,ll b,ll &x,ll &y){
if(a==0&&b==0)return -1;
if(b==0){x=1;y=0;return a;}
ll d=extend_gcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
ll xx,yy,m,n,l;
cin>>xx>>yy>>m>>n>>l;
ll a=(m-n);
ll b=l;
ll c=yy-xx;
ll x,y;
ll d=extend_gcd(a,b,x,y);
if(c%d){
cout<<"Impossible"<<endl;
}
else{
x*=(c/d);
x=(x%l+l)%l;
cout<<x<<endl;
}
return 0;
}

S - C Looooops (exgcd)

思路

满足下列方程

ax0+by0=gcd(a,b);

如果c%gcd==0 那么此方程有解,否则没有解

若有解

方程两边同时乘以 c/gcd(a,b) 得 (a×c/gcd(a,b))×x0+(bc/gcd(a,b))y0=c;

这时得出方程的一个解 x1=x0×c/gcd(a,b) y1=y0×c/gcd(a,b)

求最小整数解 意思把x1变到减少到不能减少为止 也就是把x0 减少到不能减少为止

若x0减小x,那么方程左边 整体会减少 (ac/gcd(a,b))x 此时 y0 需要增加相应的数使得等式平衡

而假设 y0增加了y 总体增加了 (bc/gcd(a,b))y 此时 (ac/gcd(a,b))x==(ac/gcd(a,b))y

而且x,y为整数 我们可以得到 x/y==b/gcd(a,b) / a/gcd(a,b)

这时 x每次减少 b/gcd(a,b) y只需增加 a/gcd(a,b) 就可以使得等式平衡。 那为什么我们不约掉gcd(a,b)?

因为x越小,我们得到的最小整数解就会越小。。。。

这时我们让x0不断减 x (x=b/gcd(a,b)) 直到 x0-ix>=0 && x0-(i+1)x<0 (i为减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
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=4e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
///返回 d=gcd(a,b); 和对应于等式 ax+by=d 中的 x,y
ll extend_gcd(ll a,ll b,ll &x,ll &y){
if(a==0&&b==0)return -1;
if(b==0){x=1;y=0;return a;}
ll d=extend_gcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
ll aa,bb,cc,kk;
while(cin>>aa>>bb>>cc>>kk){
if(aa==0&&bb==0&&cc==0&&kk==0)break;
ll a=cc;
ll b=1LL<<kk;
ll c=bb-aa;
ll x,y;
ll d=extend_gcd(a,b,x,y);
if(c%d){
cout<<"FOREVER"<<endl;
}
else{
ll x0=x*(c/d)%b; ///变成特解
cout<<(x0%(b/d)+(b/d))%(b/d)<<endl;
}
}
return 0;
}

U - Primes (水)

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
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2e5+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int prime[maxn];
void init(){
check[1]=true;
for(int i=2;i<maxn;i++){
if(!check[i])prime[++prime[0]]=i;
for(int j=1;j<=prime[0]&&prime[j]*i<maxn;j++){
check[prime[j]*i]=true;
if(i%prime[j]==0)break;
}
}
check[2]=true;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
int tot=1;
init();
while(cin>>n&&(n>0)){
cout<<tot++<<": ";
if(check[n]){
cout<<"no"<<endl;
}
else cout<<"yes"<<endl;
}
return 0;
}

X - Farey Sequence (欧拉函数)

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<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
bool check[maxn];
int phi[maxn];
int prime[maxn];
int tot;
ll sum[maxn];
void phi_table(int n){
phi[1]=1;
tot=0;
for(int i=2;i<=n;i++){
if(!check[i]){prime[tot++]=i;phi[i]=i-1;}
for(int j=0;j<tot;j++){
if(i*prime[j]>n)break;
check[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(int i=2;i<=n;i++){
sum[i]=sum[i-1]+phi[i];
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
int tot=1;
phi_table(maxn-1);
while(cin>>n&&(n>0)){
cout<<sum[n]<<endl;
}
return 0;
}

「kuangbin带你飞」专题十二 基础DP

Diposting di 2019-04-24 | Edited on 2018-09-06

传送门

A.HDU1024 Max Sum Plus Plus

题意

给你N个数,然后你分成M个不重叠部分,并且这M个不重叠部分的和最大.

思路

动态规划最大m字段和,dp数组,dp[i][j]表示以a[j]结尾的,i个字段的最大和

两种情况:1.第a[j]元素单独作为第i个字段
        2.第a[j]元素和前面的字段共同当做第i个字段

得到状态转移方程:dp[i][j]=max( dp[i][j-1]+a[j] , max(dp[i-1][t])+a[j]);

但是实际情况是,时间复杂度和空间复杂度都是相当的高,所以要进行时间和空间的优化:
    将每次遍历的时候的max(dp[i-1][t]) 用一个数组d储存起来,这样就能省去寻找max(dp[i-1][t])的时间,
    这样状态转移方程就变成了 dp[i][j]=max( dp[i][j-1]+a[j] , d[j-1]+a[j]), 会发现dp数组的可以
    省去一维,因为每次都是和前一次的状态有关,所以可以记录前一次状态,再用一个变量tmp记录下dp[i][j-1],
    这样方程就变成了 dp[j]=max( num+a[j] , d[j-1]+a[j]);这样就可以化简一下就是:
    dp[j]= max( num , d[j-1])+a[j];
    在之后还要保存前面m-1的情况的最大状态,d[j-1]=ma;等于只有M-1组而且到这个位置时候的值,注意不能最大,因为最大虽然值大但是不保证是M-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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int a[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int m,n;
while(cin>>m>>n){
for(int i=1;i<=n;i++)cin>>a[i];
int ma=0;
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++){
int num=0;
for(int j=1;j<=i;j++)num+=a[j];
ma=num;
for(int j=i+1;j<=n;j++){
num=max(num,dp[j-1])+a[j];//前面的和,自己组成一个
dp[j-1]=ma;
ma=max(ma,num);
}
}
cout<<ma<<endl;
}
return 0;
}

B.HDU1029 Ignatius and the Princess IV

题意

给你n个数字,你需要找出出现至少(n+1)/2次的数字 现在需要你找出这个数字是多少?

方法一

直接用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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
map<int,int>mp;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
int a;
mp.clear();
for(int i=0;i<n;i++){
cin>>a;
mp[a]++;
}
map<int,int>::iterator it;
int maid,ma=0;
for(it=mp.begin();it!=mp.end();it++){
if(it->second>=(n+1)/2){
maid=it->first;
}
}
cout<<maid<<endl;
}
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
int k,sum=0,a;
while(n--){
cin>>a;
if(!sum)k=a,sum++;
else if(k==a)sum++;
else{
sum--;
}
}
cout<<k<<endl;
}
return 0;
}

方法三

排序后输出中间位置的数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
cout<<a[(n+1)/2]<<endl;
}
return 0;
}

C.HDU1069 Monkey and Banana

题意

三维的最长带全严格上升子序列N个无限个数的三维方体,三维方体可以任意旋转(长宽高可以替换),现在让你求出这些正方体可以搭的最高高度,要求上面的矩形的长和宽必须严格小于下面的矩形的长宽,类似与三维的最长带全严格上升子序列

思路

一对长宽高可以构造出六种不同的正方体(长:三个取一个,宽:两个取一个,高:被固定了);然后根据长来排序,长相同根据宽排序,高不影响不管,依次判断最小面的是哪一个矩形,然后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
50
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct node{
int x,y,z;
node(int x,int y,int z):x(x),y(y),z(z){};
};
int cmp(node a,node b){
if(a.x==b.x)return a.y<b.y;
else return a.x<b.x;
}
vector<node>ve;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
int t=1;
while(cin>>n&&n){
ve.clear();
for(int i=0;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
ve.push_back(node(a,b,c));ve.push_back(node(a,c,b));
ve.push_back(node(b,a,c));ve.push_back(node(b,c,a));
ve.push_back(node(c,a,b));ve.push_back(node(c,b,a));
}
int len=ve.size();
sort(ve.begin(),ve.end(),cmp);
int sum=0;
for(int i=0;i<len;i++){
int ma=0;
// cout<<"i="<<i<<" ";
// cout<<ve[i].x<<" "<<ve[i].y<<" "<<ve[i].z<<endl;
for(int j=0;j<i;j++){
if(ve[i].x>ve[j].x&&ve[i].y>ve[j].y&&ve[j].z>ma){
ma=ve[j].z;
}
}
ve[i].z=ma+ve[i].z;
sum=max(sum,ve[i].z);
}
cout<<"Case "<<t++<<": maximum height = "<<sum<<endl;
}
return 0;
}

D.HDU1074 Doing Homework

题意

有n个任务,每个任务有一个截止时间,超过截止时间一天,要扣一个分。
求如何安排任务,使得扣的分数最少,多种可能输出字典序最小的

思路

状压DP! 观察到N最大只有15,跟南京网络赛的E一样思路,想到了暴力枚举每个状态;然后枚举每一个在这状态完成的任务,假设他没完成让他在只有这个任务没完成的情况下完成的情况,因为题目要求是字典序小的先输出,所以同样的情况先枚举字典序大的,如果枚举完大的后,发现小的更好,那就可以直接替换了

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int last[16];
int need[16];
int pre[maxn];
int used[maxn];
string s[16];
void init(){
memset(dp,inf,sizeof(dp));
memset(used,0,sizeof(used));
}
void out(int x){
if(!x)return;
out(x^(1<<pre[x]));
cout<<s[pre[x]]<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
init();
for(int i=0;i<n;i++){
cin>>s[i]>>last[i]>>need[i];
}
dp[0]=0;
for(int i=1;i<(1<<n);i++){
for(int j=n-1;j>=0;j--){
if(!(i&(1<<j)))continue;
int pp=i^(1<<j);
int time=used[pp]+need[j]-last[j];
if(time<0)time=0;
if(dp[i]>dp[pp]+time){
dp[i]=dp[pp]+time;
used[i]=used[pp]+need[j];
pre[i]=j;
}
}
}
cout<<dp[(1<<n)-1]<<endl;
out((1<<n)-1);
}
return 0;
}

E.HDU1087 Super Jumping! Jumping! Jumping!

题意

求最大递增子序列的权值和

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[1100];
ll dp[1100];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n&&n){
for(int i=0;i<n;i++)cin>>a[i],dp[i]=a[i];
ll sum=0;
ll ma=0;
for(int i=0;i<n;i++){
ll num=0;
for(int j=0;j<i;j++){
if(a[i]>a[j]&&num<dp[j]){
num=dp[j];
}
}
dp[i]=a[i]+num;
ma=max(dp[i],ma);
}
cout<<ma<<endl;
}
return 0;
}

F.HDU1114 Piggy-Bank

题意

有一个存钱罐,给出它的重量和装满硬币的重量,然后给出里面装的硬币的种类数,并给出每种硬币的面值和重量,求在给定重量的条件下硬币的最小价值

思路

根据重量从0开始推到给定的重量,每个从这个重量之前的重量找一个硬币的差距,然后选出一个最小值;默认为inf

完全背包:必须装满给出的重量,因此要使dp[0]=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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int p[maxn],w[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
int e,f;
cin>>e>>f;
int num=f-e;
int n;
cin>>n;
memset(dp,inf,sizeof(dp));
for(int i=0;i<n;i++){
cin>>p[i]>>w[i];
}
for(int i=0;i<=num;i++){
for(int j=0;j<n;j++){
if(w[j]>i){/*cout<<"one "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
else if(dp[i-w[j]]==inf&&w[j]!=i){/*cout<<"two "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
else{
int k=dp[i-w[j]]==inf?p[j]:dp[i-w[j]]+p[j];
dp[i]=min(dp[i],k);
// cout<<"三 "<<dp[i]<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;
}
}
//cout<<"dp["<<i<<"]="<<dp[i]<<endl;
}
if(dp[num]==inf)cout<<"This is impossible."<<endl;
else cout<<"The minimum amount of money in the piggy-bank is "<<dp[num]<<"."<<endl;
}
return 0;
}
//完全背包的代码;
#include <iostream>
using namespace std;
#define INF 2000000
int main()
{
int t;
int n;
int w,wa,wb;
int value[505],weight[505];
int dp[10005];
while(cin>>t)
{
while(t--)
{
cin>>wa>>wb;
w = wb-wa;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>value[i]>>weight[i];
}
for(int i=0;i<=w;i++)dp[i] = INF;
dp[0] = 0;
for(int i=0;i<n;i++)
{
for(int j=weight[i];j<=w;j++)
{
if(dp[j]>(dp[j-weight[i]]+value[i]))dp[j] = dp[j-weight[i]]+value[i];
}
}
if(dp[w]>=INF)cout<<"This is impossible."<<endl;
else{
cout<<"The minimum amount of money in the piggy-bank is ";
cout<<dp[w]<<"."<<endl;
}
}
}
return 0;
}

G.HDU1176 免费馅饼

题意

中文题

思路

二维DP,反向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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll dp[maxn][12];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n&&n){
memset(dp,0,sizeof(dp));
int m=0;
for(int i=0;i<n;i++){
int a,b;
cin>>a>>b;
dp[b][a]++;
if(m<b)m=b;
}
for(int i=m-1;i>=0;i--){
for(int j=0;j<=10;j++){
if(j==0){
dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
}
else if(j==10){
dp[i][j]+=max(dp[i+1][j],dp[i+1][j-1]);
}
else{
dp[i][j]+=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));
}
}
}
cout<<dp[0][5]<<endl;
}
return 0;
}

H.HDU1260 Tickets

题意

现在有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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int a[2005],b[2005];
int dp[2][3];
int main()
{
// std::ios::sync_with_stdio(false);
// std::cin.tie(0);
// std::cout.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++)cin>>b[i];
dp[1][1]=inf;dp[1][0]=a[1];//0是自己买,1是跟前面的买
for(int i=2;i<=n;i++){
dp[i%2][0]=min(dp[(i+1)%2][0],dp[(i+1)%2][1])+a[i];
dp[i%2][1]=dp[(i+1)%2][0]+b[i]-a[i-1];
}
int time=min(dp[n%2][0],dp[n%2][1]);
int h=8,m=0,s=0;
h+=time/3600;time%=3600;m=time/60;time%=60;s=time;
h%=24;
int flag=0;
if(h<=12)flag=0;
else if(h>12){
flag=1;
}
printf("%02d:%02d:%02d %s\n",h,m,s,flag?"pm":"am");
}
return 0;
}

I.HDU1257 最少拦截系统

思路

本质上是求最长严格上升子序列,模拟成题意,就是

1.第一颗炮弹塞进数组中 拦截系统+1;

2.第二个炮弹大于第一颗炮弹,那就只能拦截系统+1;但是这个炮弹还能打比它小的导弹所以塞进数组中

3.如果第三个炮弹小于等于前面已经射出去的最高的炮弹,那就说明它可以被前面用过的炮弹打中,但是最优的策略是在前面用过的炮弹中选出一个大于它最小的炮弹用来替换,说明这个炮弹现在的位置在这了,如果等于最好,(但是有点题目是不能等于,比如最大不严格上升子序列,那就只能用upper_bound)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
vector<int>ve;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
ve.clear();
int len=0;
for(int i=0;i<n;i++){
int a;
cin>>a;
if(len==0||ve[len-1]<a){//如果不严格的话把小于搞成<=a 说明这个炮弹等于它高度的它也打不中,只能换新炮弹了
ve.push_back(a);
len++;
}
else{
int j=lower_bound(ve.begin(),ve.end(),a)-ve.begin();//找出最小最接近的用过的炮弹,数组是递增的。如果是不严格就改成upper
ve[j]=a;
}
}
cout<<len<<endl;

}
return 0;
}

J.HDU1160 FatMouse’s Speed

题意

找到一个最多的老鼠序列,使得序列中的老鼠的体重满足递增,相应老鼠的速度满足递减。

思路

先按体重递增进行sort排序,然后按照体重找到最长递减子序列即可,用动态规划做比较简单。状态f[i]表示前i个老鼠中的最长递减子序列长度,状态转移方程为f[i] = max{f[j], mice[j].speed > mice[i].speed} + 1, 最后找出最大的f[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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
struct node{
int w,s,id;
}my[1005];
int cmp(node a,node b){
if(a.w==b.w)return a.s>b.s;
else return a.w<b.w;
}
int dp[1005];
int pre[1005];
void out(int x){
if(!x)return;
out(pre[x]);
cout<<my[x].id<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n=1;
while(cin>>my[n].w>>my[n].s){
my[n].id=n;
n++;
}
n=n-1;
for(int i=1;i<=n;i++)dp[i]=1;
sort(my+1,my+n+1,cmp);
int maid,malen=0;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(my[j].w<my[i].w&&my[j].s>my[i].s&&dp[i]<dp[j]+1){
dp[i]=dp[j]+1;
pre[i]=j;
if(dp[i]>malen)maid=i,malen=dp[i];
}
}
}
cout<<malen<<endl;
out(maid);
return 0;
}

K.POJ1015 Jury Compromise

题意

1
2
3
4
5
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的|D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

输出:

选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值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
#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int dp[21][801];
vector<int> path[21][801];

int main()
{
int times=1;
int subtraction[201],_plus[201];
int n,m,i,j,k;
while(~scanf("%d%d",&n,&m) && n && m)
{
for(i=0;i<m;++i)
for(j=0;j<801;++j)
path[i][j].clear();
memset(dp,-1,sizeof(dp));
int d,p;
for(i = 0; i < n; i++)
{
cin>>d>>p;
subtraction[i] = d-p;
_plus[i] = d+p;
}
int fix = 20*m;
dp[0][fix] = 0;
for(k = 0; k < n; k++)
for(i = m-1; i >= 0; i--)
{
for(j = 0; j < 2*fix; j++)
{
if(dp[i][j] >= 0)
{
if(dp[i+1][j+subtraction[k]] <= dp[i][j] + _plus[k])
{
dp[i+1][j+subtraction[k]] = dp[i][j] + _plus[k];
path[i+1][j+subtraction[k]] = path[i][j];
path[i+1][j+subtraction[k]].push_back(k);
}
}
}
}
for(i = 0; dp[m][fix+i] == -1 && dp[m][fix-i] == -1; i++);
int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
int sumD = ( dp[m][fix+temp] + temp )/2;
int sumP = ( dp[m][fix+temp] - temp )/2;
printf( "Jury #%d\n", times++ );
printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumD,sumP);
for( i=0; i < m; i++ )
printf( " %d", path[m][fix+temp][i]+1);
printf( "\n\n" );

}
return 0;
}

L.POJ1458 Common Subsequence

题意

最长公共子序列

思路

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e3+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int dp[maxn][maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
char a[1500],b[1500];
while(cin>>a+1>>b+1){
int n,m;
n=strlen(a+1);m=strlen(b+1);
int k=max(n,m);
memset(dp,0,sizeof(dp));
int ma=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}

M.POJ1661 Help Jimmy

题意

1
2
3
4
5
6
7
中文题,小人从最高点跳下

1.可以跳到板子上
2.可以直接跳到地上
3.下落的距离不能超过MAX
4.可以在板子上左右移动,但是要花费时间;
5.问从起点落到地面的最小值

思路

1
2
3
4
5
6
1.可以从下枚举上去到起点
2.对于每一个板子,都可以落到它下面的符合条件的板子(可以正好接住)
3.对于每一个板子,都可以从左边或者右边落下,分别枚举左右边即可
4.假设DP(i-0)表示这个板子从左边落到地面花费的时间
DP(i-1)表示这个板子从右边左到地面花费的时间
一直枚举到最上面的起点即可,复杂度O(n^2)
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
//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int x,y,MAX,n;
struct node{
int l,r;
int h;
}my[maxn];
int cmp(node a,node b){
return a.h<b.h;
}
int dp[maxn][2];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
cin>>n>>x>>y>>MAX;
my[0].l=-inf;my[0].r=inf;my[0].h=0;
for(int i=1;i<=n;i++)cin>>my[i].l>>my[i].r>>my[i].h;
my[n+1].l=x;my[n+1].r=x;my[n+1].h=y;
sort(my,my+n+1,cmp);
memset(dp,inf,sizeof(dp));
dp[1][0]=dp[1][1]=my[1].h;
dp[0][0]=dp[0][1]=0;
for(int i=1;i<=n+1;i++){
for(int j=0;j<i;j++){
if(my[j].l<=my[i].l&&my[j].r>=my[i].l&&my[i].h-my[j].h<=MAX){
if(j==0){
dp[i][0]=my[i].h;
}
else{
dp[i][0]=min(dp[j][0]+my[i].l-my[j].l,dp[j][1]+my[j].r-my[i].l)+my[i].h-my[j].h;
}
}
if(my[j].l<=my[i].r&&my[j].r>=my[i].r&&my[i].h-my[j].h<=MAX){
if(j==0){
dp[i][1]=my[i].h;
}
else{
dp[i][1]=min(dp[j][0]+my[i].r-my[j].l,dp[j][1]+my[j].r-my[i].r)+my[i].h-my[j].h;
}
}
}
}
cout<<min(dp[n+1][0],dp[n+1][1])<<endl;
}
return 0;
}

N.POJ2533 Longest Ordered Subsequence

题意

最长递增子序列

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
vector<int>ve;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
vector<int>ve;
int len=0;
for(int i=0;i<n;i++){
int a;
cin>>a;
if(!len||a>ve[len-1]){
ve.push_back(a);
len++;
}
else{
ve[lower_bound(ve.begin(),ve.end(),a)-ve.begin()]=a;
}
}
cout<<len<<endl;
}
return 0;
}

O.POJ3186 Treats for the Cows

题意

给出n个数字v(i),每次你可以取出最左边的数字或者取出最右边的数字,一共取n次取完。假设你第i次取的数字是x,那么你可以获得i*x的价值。现在你需要规划取数顺序,使得总价值和最大。

思路

区间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
//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[2001][2001];
int v[2001];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n){
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>v[i];dp[i][i]=v[i];
}
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
dp[i][j]=max(dp[i+1][j]+v[i]*(n-j+i),dp[i][j-1]+v[j]*(n-j+i));
}
}
cout<<dp[1][n]<<endl;
}
return 0;
}

P.HDU1078 FatMouse and Cheese

题意

1
2
3
4
5
6
7
8
9
10
11
有一种游戏是的玩法是这样的:
有一个n*n的格子,每个格子有一个数字。
遵循以下规则:
1. 玩家每次可以由所在格子向上下左右四个方向进行直线移动,每次移动的距离不得超过m
2. 玩家一开始在第一行第一列,并且已经获得该格子的分值
3. 玩家获得每一次移动到的格子的分值
4. 玩家下一次移动到达的格子的分值要比当前玩家所在的格子的分值要大。
5. 游戏所有数字加起来也不大,保证所有数字的和不会超过int型整数的范围
6. 玩家仅能在n*n的格子内移动,超出格子边界属于非法操作
7. 当玩家不能再次移动时,游戏结束
现在问你,玩家所能获得的最大得分是多少?

思路

记忆化搜索,把每次搜索的结果存在DP里面 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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m;
int mp[110][110];
int dp[110][110];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int dfs(int x,int y){
if(dp[x][y]!=-1)return dp[x][y];///说明这个点已经被用过了;
else{
int ans=0;
for(int i=1;i<=m;i++){///步数限制
for(int j=0;j<4;j++){///方向限制
int xx=x+dx[j]*i;
int yy=y+dy[j]*i;
if(xx>=1&&xx<=n&&yy>=1&&yy<=n){
if(mp[xx][yy]>mp[x][y])
ans=max(ans,dfs(xx,yy));
}
}
}
dp[x][y]=mp[x][y]+ans;
}
return dp[x][y];
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
while(cin>>n>>m&&!(n==-1&&m==-1)){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
cin>>mp[i][j];
}
memset(dp,-1,sizeof(dp));
dfs(1,1);
cout<<dp[1][1]<<endl;
}
return 0;
}

Q.HDU2859 Phalanx

题意

给你一个矩阵,只由小写或大写字母构成。求出它的最大对称子矩阵的边长。

其中对称矩阵是一个kk的矩阵,它的元素关于从左下角到右上角的对角线对称。
例如下面这个3
3的矩阵是对称矩阵:
cbx
cpb
zcc

思路

dp ij 表示以点i,j为左下角的最大对称矩阵;发现以下规律

1.对于矩阵的最上面和最右边 i=1或者j=n的点为左下角的对称矩阵最大为1(就是它自己)

2.然后根据1可以得知,第二行最大的矩阵大小为2;并且最大的矩阵比第一个矩阵要多一个左边和右边的大小;

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[1100][1100];
int n;
char s[1100][1100];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
while(cin>>n&&n){
for(int i=1;i<=n;i++)cin>>s[i]+1;
int ma=1;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)dp[1][i]=dp[i][n]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==1||j==n)continue;
int xx=i,yy=j,num=0;
while(xx>=1&&yy<=n&&s[xx][j]==s[i][yy]){
num++;
xx--;yy++;
}
int pre=dp[i-1][j+1];
if(num>=pre+1)dp[i][j]=pre+1;
else dp[i][j]=num;
ma=max(ma,dp[i][j]);
// cout<<"i="<< i<<" "<<"j="<<j<<" "<<ma<<" "<<dp[i][j]<<endl;
}
}
cout<<ma<<endl;
}
return 0;
}

R.POJ3616 Milking Time

题意

M个时间段,每个时间段有V价值,在每个时间段之间必须隔着R的时间,求最大价值和

思路

把时间段的起点排序一下,然后对于每个时间段判断前面的时间段间隔满不满足题意。然后选出价值最大的

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<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m,R;
struct node{
int l,r;
int money;
}my[maxn];
int cmp(node a,node b){
return a.l<b.l;
}
int dp[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
while(cin>>n>>m>>R){
for(int i=1;i<=m;i++){
cin>>my[i].l>>my[i].r>>my[i].money;
}
sort(my+1,my+1+m,cmp);
int ans=0;
for(int i=1;i<=m;i++){
int ma=0;
for(int j=1;j<i;j++){
if(my[j].r+R<=my[i].l&&ma<dp[j]){
ma=dp[j];
}
}
dp[i]=ma+my[i].money;
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
return 0;
}

S.POJ3666 Making the Grade

题意

一个序列A,让你把它变成不严格递减或者不严格递增的序列B,花费是

思路

构造DP【i】【j】表示前面i个元素组成的不严格递减和不严格递增序列,其中J是这个序列中的最大值;

然后通过DP【i-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
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2200;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
//const int inf=0x3f3f3f3f;
ll a[maxn];
ll dp[maxn][maxn];
int n;
int cnt=0;
vector<ll>ve;
ll abs(ll a){
if(a>=0)return a;
else return -a;
}
int cmp(int a,int b){return a>b;}
ll ac(){
for(int i=1;i<=n;i++){
ll mi=dp[i-1][0];
for(int j=0;j<cnt;j++){
mi=min(mi,dp[i-1][j]);
dp[i][j]=abs(a[i]-ve[j])+mi;
}
}
ll ans=dp[n][0];
for(int i=0;i<cnt;i++)ans=min(ans,dp[n][i]);
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],ve.push_back(a[i]);
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());
cnt=ve.size();
ll ans1=ac();
sort(ve.begin(),ve.end(),cmp);
ll ans2=ac();
ll anw=min(ans1,ans2);
cout<<anw<<endl;
return 0;
}

Hello World

Diposting di 2019-04-24 | Edited on 2018-08-23

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Hello hexo luowent

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

字符串哈希专题

Diposting di 2019-04-24 | Edited on 2018-11-16

传送门

A.POJ - 1200 A - Crazy Search

摘要 哈希进制转换

题意

一个字符串分成长度为N的字串。且不同的字符不会超过NC个。问总共有多少个不同的子串

思路

以nc作为进制,把一个子串化为这个进制下的数,再用哈希判断

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
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e7+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
const ull Seed_Pool[]={146527,19260817};
const ull Mod_Pool[]={1000000009,998244353};
struct Hash{
ull SEED,MOD;
vector<ull>p,h;
Hash(){}
Hash(const char* s,const int& seed_index,const int& mod_index){
SEED=Seed_Pool[seed_index];
MOD=Mod_Pool[mod_index];
int n=strlen(s);
p.resize(n+1),h.resize(n+1);
p[0]=1;
for(int i=1;i<=n;i++)p[i]=p[i-1]*SEED%MOD;
for(int i=1;i<=n;i++)h[i]=(h[i-1]*SEED%MOD+s[i-1])%MOD;
}
ull get(int l,int r){return (h[r]-h[l]*p[r-l]%MOD+MOD)%MOD;}
ull substr(int l,int m){return get(l,l+m);}
};
set<ull>st;
char s[16000005];
bool a[maxn*2];
int ha[256];
int main()
{
int n,nc;
cin>>n>>nc>>s;
int cnt=0;
memset(ha,-1,sizeof(ha));
memset(a,false,sizeof(a));
int len=strlen(s);
for(int i=0;i<len;i++){
if(ha[s[i]]==-1)ha[s[i]]=cnt++;
}
int res=0;
for(int i=0;i+n<=len;i++){
int sum=0;
for(int j=i;j<i+n;j++){
sum*=nc;
sum+=ha[s[j]];
}
if(!a[sum])res++,a[sum]=true;
}
cout<<res<<endl;
return 0;
}

C.POJ - 2774 Long Long Message

两个字符串最长子串长度

题意

求两个字符串的最长子串长度

题解

二分长度,然后把字符串A的长度mid的哈希值塞入数组,再在字符串B的数组中二分查找长度为mid

复杂度为O(logn×N×logN)

也可以直接用后缀数组的height

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    <cstring>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
const ull seed=19260817;
struct Hash{
vector<ull>p,h;
Hash(){}
Hash(const string& s){
int n=s.length();
p.resize(n+1),h.resize(n+1);
p[0]=1;
for(int i=1;i<=n;i++)p[i]=p[i-1]*seed;
for(int i=1;i<=n;i++)h[i]=(h[i-1]*seed+s[i-1]);
}
ull get(int l,int r){return(h[r]-h[l]*p[r-l]);}
ull substr(int l,int m){return get(l,l+m);}
}A,B;
int n,m;
bool ok(int mid){
vector<ull>ve;
for(int i=0;i<=n-mid;i++){
ve.push_back(A.substr(i,mid));
}
sort(ve.begin(),ve.end());
for(int i=0;i<=m-mid;i++){
if(binary_search(ve.begin(),ve.end(),B.substr(i,mid))){
return true;
}
}
return false;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
string a,b;
cin>>a>>b;
n=a.length(),m=b.length();
if(n>m){swap(a,b);swap(n,m);}
A=Hash(a);B=Hash(b);
int l=0,r=n;
int haha=0;
while(r-l>=0){
int mid=(r+l)>>1;
// cout<<mid<<endl;
if(ok(mid)){
haha=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<haha<<endl;
return 0;
}

D.URAL - 1989 Subpalindromes

线段树/树状数组和哈希应用 判断回文

题意

给定一个字符串(长度<=100000),有两个操作。 1:改变某个字符。 2:判断某个子串是否构成回文串。

题解

把字符串正向,方向插入线段树和树状数组中,然后单点修改,区间查值, 如果正向和方向值一样,那就是回文了

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
//线段树
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
#define lson (x<<1)
#define rson ((x<<1)|1)
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
ull bit[maxn];
string s;
struct node{
int l,r;
ull sum1,sum2;
}my[maxn<<2];
int n;
void pushup(int x){
my[x].sum1=my[lson].sum1+my[rson].sum1;
my[x].sum2=my[lson].sum2+my[rson].sum2;
}
void build(int x,int l,int r){
my[x].l=l;my[x].r=r;
if(my[x].l==my[x].r){
my[x].sum1=bit[l-1]*(s[l-1]-'a');
my[x].sum2=bit[n-l]*(s[l-1]-'a');
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(x);
}
ull one,two;
void update(int x,int pos,int val){
if(my[x].l==my[x].r){
my[x].sum1=bit[pos-1]*val;
my[x].sum2=bit[n-pos]*val;
return;
}
int mid=(my[x].l+my[x].r)>>1;
if(pos<=mid)
update(lson,pos,val);
else
update(rson,pos,val);
pushup(x);
}
void query(int x,int l,int r){
if(my[x].l>=l&&my[x].r<=r){
one+=my[x].sum1;
two+=my[x].sum2;
return;
}
int mid=(my[x].l+my[x].r)>>1;
if(l<=mid)query(lson,l,r);
if(r>mid)query(rson,l,r);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>s;n=s.length();int t;
cin>>t;
bit[0]=1;
for(int i=1;i<maxn;i++)bit[i]=bit[i-1]*19260817;
build(1,1,n);
while(t--){
char s[50];
cin>>s;
if(s[0]=='p'){
int x,y;
cin>>x>>y;
one=0;two=0;
query(1,x,y);
if((x-1)>(n-y))two*=bit[(x-1)-(n-y)];
else one*=bit[(n-y)-(x-1)];
if(one==two)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else{
int x;char ch;
cin>>x>>ch;
update(1,x,ch-'a');
}
}
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
//树状数组
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
ull bit[maxn],c[maxn][2];
int n,len;
inline int lowbit(int x){
return x&(-x);
}
void update(int x,ull val,int flag){
while(x<maxn){
c[x][flag]+=val;
x+=lowbit(x);
}
}
ull sum(int x,int flag){
ull cnt=0;
while(x){
cnt+=c[x][flag];
x-=lowbit(x);
}
return cnt;
}
string s;
string str;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
bit[0]=1;
for(int i=1;i<maxn;i++)bit[i]=bit[i-1]*19260817;
while(cin>>str){
len=str.length();
memset(c,0,sizeof(c));
for(int i=0;i<len;i++){
update(i+1,(str[i]-'a'+1)*bit[i],0);
update(i+1,(str[len-i-1]-'a'+1)*bit[i],1);
}
cin>>n;
int l,r;
while(n--){
cin>>s;
if(s[0]=='p'){
cin>>l>>r;
ull a=(sum(r,0)-sum(l-1,0))*bit[len-r];
ull b=(sum(len-l+1,1)-sum(len-r,1))*bit[l-1];
if(a==b)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else{
int w;
char ch;
cin>>w>>ch;
update(w,(ch-str[w-1])*bit[w-1],0);
update(len-w+1,(ch-str[w-1])*bit[len-w],1);
str[w-1]=ch;
}
}
}
return 0;
}

E.CodeForces - 580E Kefa and Watch

线段树+哈希

题意

给你一个长度为n的字符串s,有两种操作:

1 L R C : 把s[l,r]全部变为c;

2 L R d : 询问s[l,r]是否是周期为d的重复串。

题解

n最大为1e5,且m+k最大也为1e5,这就要求操作1和操作2都要采用logn的算法,所以用线段树.

对于更新操作,使用区间更新就可解决。

主要是如何在logn的时间内完成询问操作.

我们采用线段树维护hash值的方法.

结合于类似KMP的性质,我们发现,字符串[l,r]有长度为w的循环节,只需要使得[l,r-w]=[l+w,r]即可。证明过程看这里

这题的hash不同于普通的字符串hash,因为涉及到动态修改,所以需要预先处理出所有的base,在修改的时候直接用.

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
ull seed=19260817;
//ull seed=10;
ull s[maxn];
ull fs[maxn];
char ss[maxn];
void init(){
s[0]=1;fs[0]=1;
for(int i=1;i<maxn;i++)s[i]=(s[i-1]*seed)%mod;
for(int i=1;i<maxn;i++)fs[i]=(fs[i-1]+s[i])%mod;
/* for(int i=0;i<5;i++){
cout<<i<<"\t"<<s[i]<<"\t"<<fs[i]<<endl;
}*/
}
struct node{
int l,r;
int lazy;
int ok;
ull num;
}my[maxn<<2];
void pushup(int x){
int mid=(my[x].l+my[x].r)>>1;
// printf("x==%d x<<1=%d x<<1|1=%d my[x<<1].num=%llu my[x<<1|1].num=%llu s==%d \n",x,x<<1,x<<1|1,my[x<<1].num,my[(x<<1)|1].num,s[my[x].r-mid]);
my[x].num=(my[x<<1].num*s[my[x].r-mid]+my[(x<<1|1)].num)%mod;
// cout<<"x=="<<x<<" my[x].num"<<my[x].num<<endl;
}
void pushdown(int x){
if(my[x].lazy){
int mid=(my[x].l+my[x].r)>>1;
my[x<<1].lazy=my[(x<<1)|1].lazy=my[x].lazy;
my[x<<1].ok=my[x<<1|1].ok=my[x].ok;
my[x<<1].num=(fs[mid-my[x].l]*my[x].ok)%mod;
my[(x<<1)|1].num=(fs[my[x].r-mid-1]*my[x].ok)%mod;
my[x].lazy=0;
}
}
void build(int x,int l,int r){
my[x].l=l;my[x].r=r;my[x].lazy=0;
if(my[x].l==my[x].r){
my[x].num=ss[l-1]-'0';
// printf("my[%d].num=%d\n",x,my[x].num);
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build((x<<1)|1,mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int k){
if(my[x].l>=l&&my[x].r<=r){
my[x].num=(fs[my[x].r-my[x].l]*k)%mod;
my[x].ok=k;
my[x].lazy=1;
return;
}
pushdown(x);
int mid=(my[x].l+my[x].r)>>1;
if(l<=mid)update(x<<1,l,r,k);
if(r>mid)update(x<<1|1,l,r,k);
pushup(x);
}
ull query(int x,int l,int r){
if(my[x].l>=l&&my[x].r<=r)return my[x].num;
pushdown(x);
int mid=(my[x].l+my[x].r)>>1;
if(l>mid)return query(x<<1|1,l,r);
else if(r<=mid)return query(x<<1,l,r);
else{
ull t1=query(x<<1,l,r);
ull t2=query(x<<1|1,l,r);
int k=min(r,my[x].r)-mid;
return (t1*s[k]+t2)%mod;
}
pushup(x);
}
void pri(int n){
for(int i=1;i<=n*4;i++){
printf("my[%d].num=%llu\n",i,my[i].num);
}
}
int main()
{
/* std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);*/
init();
int n,q,t;
scanf("%d%d%d",&n,&q,&t);
q+=t;
scanf("%s",ss);
int len=strlen(ss);
build(1,1,len);
// pri(len);
for(int i=0;i<q;i++){
int op,l,r,d;
scanf("%d%d%d%d",&op,&l,&r,&d);
if(op==1)update(1,l,r,d);
else {
if(d==r-l+1){
printf("YES\n");
continue;
}
ull one=query(1,l,r-d);
// cout<<"one="<<one<<endl;
ull two=query(1,l+d,r);
// cout<<"two="<<two<<endl;
if(one==two)printf("YES\n");
else printf("NO\n");
}
}

return 0;
}

H.HDU - 1686 Oulipo

哈希水题,求模式串出现次数

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
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e7+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
const ull Seed_Pool[]={146527,19260817};
const ull Mod_Pool[]={1000000009,998244353};
struct Hash{
ull SEED,MOD;
vector<ull>p,h;
Hash(){}
Hash(const string& s,const int& seed_index,const int& mod_index){
SEED=Seed_Pool[seed_index];
MOD=Mod_Pool[mod_index];
int n=s.length();
p.resize(n+1),h.resize(n+1);
p[0]=1;
for(int i=1;i<=n;i++)p[i]=p[i-1]*SEED%MOD;
for(int i=1;i<=n;i++)h[i]=(h[i-1]*SEED%MOD+s[i-1])%MOD;
}
ull get(int l,int r){return (h[r]-h[l]*p[r-l]%MOD+MOD)%MOD;}
ull substr(int l,int m){return get(l,l+m);}
};
int main()
{
int t;
ios::sync_with_stdio(false);
cin>>t;
while(t--){
string s;
cin>>s;
int n=s.length();
Hash aa=Hash(s,0,0);
ull a=aa.substr(0,n);
cin>>s;
int nn=s.length();
aa=Hash(s,0,0);
//cout<<"aa="<<a<<endl;
int sum=0;
for(int i=0;i+n<=nn;i++){
if(aa.substr(i,n)==a){
//cout<<aa.substr(i,n)<<endl;
sum++;
}
}
cout<<sum<<endl;
}
return 0;
}

Educational Codeforces Round 62 (Rated for Div. 2)

Diposting di 2019-04-24 | Edited on 2019-03-24

传送门

D - Minimum Triangulation (区间DP,结论题)

思路

一眼看出结论,但是实际上它是个区间DP题,枚举区间$l-r$中的一个点$k$ 然后继续做一个三角形

E - Palindrome-less Arrays (DP)

思路

首先没有大于1的奇数的回文那就是没有长度为3的回文,可以发现只要没有长度为3的回文那就肯定不会有大于长度为3的奇数回文 所以我们就可以把问题拆成奇数和偶数的两个串 要求任意相邻两个不相同的个数

首先如果不是-1答案就已经固定了,如果是-1就考虑这种-1的长度的情况

$1.$如果全部都是-1例如$xxxxx$ 那么我们就直接得到这种有$k*pow(k-1,n-1)$

$2.$如果是$axxxx$或者$xxxxa$那么我们就可以直接得到$pow(k-1,n)$

$3.$如果是$axxxa$ 那么假设答案就是$dp[len][1]$

$4.$如果是$axxxxb$ 那么假设就是$dp[len][0]$

首先定义$dp[len][0]$ 为长度为$len$的以上$3/4$情况

明显的如果$len=1$那么$dp[len][1]=k-1$ and $dp[len][0]=k-2$

现在考虑$dp[len+1][1]$ 就相当于现在已经有了一个$axxxxa$

对于第一个$x$ 它肯定不是$a$ 那么他就可能是$k$中除了$a$之外的$k-1$个,所以假设它是$b$

那么现在就是$axxxb$ 发现这个答案我们之前是不是求过了是不是就是$dp[len][0]$ 啊

然后$b$有$k-1$中情况所以$dp[len][1]=(k-1)*dp[len-1][0]$

另一种同理

Educational Codeforces Round 55 (Rated for Div. 2)

Diposting di 2019-04-24 | Edited on 2018-11-30

传送门

A. Vasya and Book (傻逼题)

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 t;
cin>>t;
while(t--){
ll n,x,y,d;
cin>>n>>x>>y>>d;

ll one,two,three;
one=two=three=inf;
if((n-y)%d==0){
one=(n-x)/d;
if((n-x)%d)one++;
one+=(n-y)/d;
}
else one=inf;
if((y-1)%d==0){
two=(x-1)/d;
if((x-1)%d)two++;
two+=(y-1)/d;
}
else two=inf;
if(abs(y-x)%d==0){
three=abs(y-x)/d;
}
else three=inf;
ll ans=min(one,min(two,three));
if(ans==inf)cout<<-1<<endl;
else cout<<ans<<endl;
}
return 0;
}

B.Vova and Trophies (前后缀处理一下)

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
char s[maxn];
int pre[maxn];
int erp[maxn];
bool okpre[maxn];
bool okrep[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
cin>>n;
cin>>s+1;
int on=0;

for(int i=1;i<=n;i++){
if(s[i]=='G')pre[i]=pre[i-1]+1;
else pre[i]=0;
if(s[i]=='G')on++;
}
for(int i=n;i>=1;i--){
if(s[i]=='G')erp[i]=erp[i+1]+1;
else erp[i]=0;
}
/*for(int i=1;i<=n;i++){
cout<<pre[i]<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++){
cout<<erp[i]<<" ";
}
cout<<endl;*/
int mi=0;
for(int i=1;i<=n;i++){
int k=pre[i-1]+erp[i+1];
if(k!=on)k++;
mi=max(mi,k);
// cout<<"i=="<<i<<" "<<mi<<endl;
}
cout<<mi<<endl;
return 0;
}

C.Multi-Subject Competition (算每个数量的贡献)

题意

题意是给出n个人的专长(m种)si和对应的熟练度ri,挑出一部分人组成一个团体,要求团体中每个不同的专长对应的人数一致。求使得团队熟练度总和最大是多少

题解

一开始写了个假算法n方的复杂度 水过去了,结果比赛一结束自己就感觉自己算法会被卡,一觉醒来发现真被hack了

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int s[maxn];
int r[maxn];
vector<int>ve[maxn];
int cmp(int a,int b){
return a>b;
}
vector<ll>num[maxn];
ll ans[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
ve[a].push_back(b);
}
int k=0;
for(int i=1;i<=m;i++){
sort(ve[i].begin(),ve[i].end(),cmp);
}
ll ma=0;
for(int i=1;i<=m;i++){
int k=0;
for(int j=0;j<ve[i].size();j++){
k+=ve[i][j];
if(k<=0)break;
ans[j+1]+=k;
ma=max(ma,ans[j+1]);
}
}
cout<<ma<<endl;
return 0;
}

D.Maximum Diameter Graph (满足出度限制的直径最大)

题意

给出n个点以及他们的度数,询问是否可以构造出一幅无自环无重边的无向图且使得图的直径尽可能的大。

题解

答案肯定是一个树,所以构建一个树,然后把度为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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct node{
int id,in;
bool operator < (const node &a)const{
return this->in>a.in;
}
}my[550];
vector<int>ve[550];
void add(int a,int b){
ve[a].push_back(b);
ve[b].push_back(a);
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,i,j,m=0;
cin>>n;
for(i=0;i<n;i++)my[i].id=i,cin>>my[i].in;
sort(my,my+n);
int len=0;
for(i=1;i<n;i++){
my[i].in--;my[i-1].in--;
add(my[i].id,my[i-1].id);
m++;len++;
if(my[i].in==0)break;
}
int mid=i++;
int ele=0;
while(i<n){
bool flag=false;
for(j=0;j<mid;j++){
if(my[j].in>0){
ele=1;flag=1;my[j].in--;my[i].in--;
add(my[j].id,my[i].id);
i++;m++;
break;
}
}
if(!flag)break;
}
len+=ele;
if(i<n){puts("NO");}
else{
cout<<"YES "<<len<<endl;
cout<<m<<endl;
for(i=0;i<n;i++){
for(auto k:ve[i]){
if(k>i)cout<<i+1<<" "<<k+1<<endl;
}
}
}
return 0;
}

E.Increasing Frequency (贪心求区间最大差值)

题意

给一个长度为n的序列,并且确定一个数c。你可以任选一个区间[l,r],对该区间+k,k可以为负数你可以任选一个区间[l,r],对该区间+k,k可以为负数,使得最后的n个数中,等于c的数字的个数最多。问最多有多少个这样的数?

题解

题目让我们把一个区间的值变换;那最优的方法就是选一个区间 把区间不是c的值变成c同时c又会变成其他值;

所以就是找一个区间里面不是C的值减去C的值+区间外面C的值最大;

那就是怎么找这个区间了,我们可以把前面如果小于C个数的值的个数变成C的个数,然后再加上当前个数。这样就相当于修改了一个值,接下来的区间如果大于C的个数,那就相当于修改了两个;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int n,c;
cin>>n>>c;
int ans=0;
while(n--){
int now;
cin>>now;
if(now==c)num[c]++;
else{
if(num[now]<=num[c])num[now]=num[c];
num[now]++;
ans=max(ans,num[now]-num[c]);
}
}
cout<<ans+num[c]<<endl;

F.Speed Dial

题意

题解

G.Petya and 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e4+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct Edge{
int to,next;
ll v;
}e[maxn];
int head[maxn],cnt=1,n,m,u,v,S,T,d[maxn],t,w,q[maxn],x;ll ans;
void ins(int u,int v,ll w){e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].v=w;}
void insert(int u,int v,ll w){ins(u,v,w);ins(v,u,0);}
bool bfs(){
memset(d,-1,sizeof(d));t=0;w=1;q[0]=S;d[S]=0;
while(t!=w){
x=q[t++];for(int i=head[x];i;i=e[i].next)if(e[i].v&&d[e[i].to]==-1)d[e[i].to]=d[x]+1,q[w++]=e[i].to;
}return d[T]!=-1;
}
ll dfs(int x,ll f){
if(x==T)return f;ll w,used=0;
for(int i=head[x];i;i=e[i].next)if(e[i].v&&d[e[i].to]==d[x]+1){
w=dfs(e[i].to,min(f-used,e[i].v));
used+=w;e[i].v-=w;e[i^1].v+=w;if(used==f)return f;
}if(!used)d[x]=-1;return used;
}
ll dinic(ll ans=0){while(bfs())ans+=dfs(S,inf);return ans;}

int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n>>m;T=n+m+1;
for(int i=1;i<=n;i++)cin>>u,insert(m+i,T,u);
for(int i=1;i<=m;i++)cin>>u>>v>>w,ans+=w,insert(S,i,w),insert(i,u+m,inf),insert(i,v+m,inf);
cout<<ans-dinic()<<endl;
return 0;
}

个人模板整理

Diposting di 2019-04-24 | Edited on 2019-04-28

数据结构

字符串处理

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
40
41
42
43
44
45
46
/*
* next[] 的含义:x[i-next[i]...i-1]=x[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;
}
}
/*
* kmpNext[i] 的意思:next'[i]=next[next[...[next[i]]]](直到 next'[i]<0 或者 x[next'[i]]!=x[i])
* 这样的预处理可以快一些
*/
void preKmp(char x[],int m,int kmpNext[]){
int i,j;
j=kmpNext[0]=-1;
i=0;
while(i<m){
while(-1!=j && x[i]!=x[j])j=kmpNext[j];
if(x[++i]==x[++j])kmpNext[i]=kmpNext[j];
else kmpNext[i]=j;
}
}
/*
返回x 在 y 中出现的次数,可以重叠
*/
int mynext[maxn];
int kmp(char x[],int m,char y[],int n){ // x是模式串 ,y是主串
int i,j;
int ans=0;
kmp_pre(x,m,mynext);
i=j=0;
while(i<n){
while(-1!=j&&y[i]!=y[j])j=mynext[j];
i++;j++;
if(j>=m){
ans++;
j=mynext[j];
}
}
return ans;
}

e-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
40
/*
* 扩展KMP算法
*/
// next[i]:x[i...m-1] 与x[0...m-1]的最长公共前缀
void pre_EKMP(char x[],int m,int next[]){
next[0]=m;
int j=0;
while(j+1<m && x[j] == x[j+1])j++;
next[1]=j;
int k=1;
for(int i=2;i<m;i++){
int p=next[k]+k-1;
int L=next[i-k];
if(i+L < p+1)next[i]=L;
else{
j=max(0,p-i+1);
while( i+j < m && x[i+j] == x[j])j++;
next[i]=j;
k=i;
}
}
}
void EKMP(char x[],int m,int y[],int n,int next[],int extend[]){
pre_EKMP(x,m,next);
int j=0;
while(j < n && j < m && x[j] == y[j])j++;
extend[0]=j;
int k=0;
for(int i = 1;i < n;i++){
int p = extend[k]+k-1;
int L=next[i-k];
if(i+L < p+1)extend[i]=L;
else{
j=max(0,p-i+1);
while(i+j < n && j < m && y[i+j] == x[j])j++;
extend[i]=j;
k=i;
}
}
}

Z-算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//求每个位置匹配前缀的长度的最大值  s[0…z[i]-1]=s[i…i+z[i]-1]
vector<int> ZF(const string &s){
vector<int>z(s.size());
int l=0,r=1;
for(int i=1;i<(int)s.size();i++){
if(r>i)z[i]=min(z[i-l],r-i);
if(z[i]+i>=r){
l=i,r=z[i]+i;
while(r<(int)s.size()&&s[r]==s[r-l])r++;
z[i]=r-l;
}
}
return z;
}

最大最小表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int getminmax(bool flag,string s){
int i=0,j=1,k=0;
int len=s.length();
while(i<len&&j<len&&k<len){
int t=s[(i+k)%len]-s[(j+k)%len];
if(t==0)k++;
else{
if(!flag){
if(t>0)i=i+k+1;
else j=j+k+1;
}
else{
if(t>0)j=j+k+1;
else i=i+k+1;
}
if(i==j)j++;
k=0;
}
}
return i<j?i:j;
}

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
25
26
27
28
29
char Ma[maxn*2];
int Mp[maxn*2]; ///需要-1才是最长回文的长度
int Manacher(char s[],int len){
int l=0,ans=0;
Ma[l++]='$';
Ma[l++]='#';
for(int i=0;i<len;i++){
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0;
for(int i=0;i<l;i++){
Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
while(Ma[i+Mp[i]]==Ma[i-Mp[i]])Mp[i]++;
if(i+Mp[i]>mx){
mx=i+Mp[i];
id=i;
}
ans+=Mp[i]/2;
}
return ans;///计算共有多少回文串
}
/*
* abaaba
* i: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
* Ma[i]: $ # a # b # a # a # b # a #
* Mp[i]: 1 1 2 1 4 1 2 7 2 1 4 1 2 1
*/

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
25
26
27
string Manacher(string s) {
// Insert '#'
string t = "$#";
for (int i = 0; i < s.size(); ++i) {
t += s[i];
t += "#";
}
int mx=0;
// Process t
vector<int> p(t.size(), 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < t.size(); ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
mx=max(mx,p[i]);
}
cout<<mx-1<<endl;
return s.substr((resCenter - resLen) / 2, resLen - 1);
}

字符串哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef unsigned long long ull;
const ull Seed_Pool[] = {146527, 19260817};
const ull Mod_Pool[] = {1000000009, 998244353};
struct Hash
{
ull SEED, MOD;
vector<ull> p, h;
Hash() {}
Hash(const string& s, const int& seed_index, const int& mod_index)
{
SEED = Seed_Pool[seed_index];
MOD = Mod_Pool[mod_index];
int n = s.length();
p.resize(n + 1), h.resize(n + 1);
p[0] = 1;
for (int i = 1; i <= n; i++) p[i] = p[i - 1] * SEED % MOD;
for (int i = 1; i <= n; i++) h[i] = (h[i - 1] * SEED % MOD + s[i - 1]) % MOD;
}
ull get(int l, int r) { return (h[r] - h[l] * p[r - l] % MOD + MOD) % MOD; }
ull substr(int l, int m) { return get(l, l + m); }
};

回文树

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
const int MAXN = 100005 ;  
const int N = 26 ;

struct Palindromic_Tree {
//cnt最后count一下之后是那个节点代表的回文串出现的次数
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。

int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}

void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}

int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}

void add ( int c ) {
c -= 'a' ;
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = next[cur][c] ;
cnt[last] ++ ;
}

void count () {
for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
} ;

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
struct Trie{
int next[maxm][26],fail[maxm],end[maxm];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]++;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<26;i++)
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
void debug(){
for(int i=0;i<L;i++){
printf("id =%3d,fail = %3d,end = %3d ,chi = [",i,fail[i],end[i]);
for(int j=0;j<26;j++)
printf("%2d",next[i][j]);
printf("]\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
74
75
76
77
78
79
80
81
82
83
84
85
86
struct DA{
//sa[i]就表示排名为i的后缀的起始位置的下标
//映射数组rk[i]就表示起始位置的下标为i的后缀的排名
/*height 数组的定义:h[i]表示rk为i的后缀与rk为i−1的后缀的最长公共前缀,可以用sa数组和rk数组来O(n)求得,这要用到一个性质:h[rk[i]]>=h[rk[i−1]]−1。也就是说如果根据rk从前往后求,每次暴力匹配来增加长度,结果是近似递增的,可以证明时间复杂度的正确性。性质的证明详见论文
*/
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
int t1[maxn],t2[maxn],c[maxn];
int rank[maxn],height[maxn],RMQ[maxn],mm[maxn];
int best[20][maxn];
int r[maxn];
int sa[maxn];
int str[maxn];
void da(int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[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(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
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];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
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(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}AA,BB;
//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] 为有效值,rank[n] 必定为 0 无效值
// *sa[] = 8, 3, 4, 5, 0, 6, 1, 7, 2 ;sa[1 n] 为有效值,sa[0] 必定为 n 是 无效值
//*height[]= 0, 0, 3, 2, 3, 1, 2, 0, 1 ;height[2 n] 为有效值

DC3

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
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
int c0(int *r,int a,int b){
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
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(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;

树链剖分

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
int n,q,a[maxn];
vector<int>G[maxn];
int size[maxn],son[maxn],fa[maxn],dep[maxn],top[maxn];
int id[maxn],pre[maxn],cnt=0;
void dfs1(int u,int f,int deep){
size[u]=1;
fa[u]=f;son[u]=0;dep[u]=deep;
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(v==f)continue;
dfs1(v,u,deep+1);
size[u]+=size[v];
if(size[v]>size[son[u]])son[u]=v;
}
}
void dfs2(int u,int root){
id[u]=++cnt;top[u]=root;pre[cnt]=u;
if(son[u])dfs2(son[u],root);
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
int sumv[maxn<<2],maxv[maxn<<2];
inline void pushup(int o){
sumv[o]=sumv[o<<1]+sumv[o<<1|1];
maxv[o]=max(maxv[o<<1],maxv[o<<1|1]);
}
void build(int o,int l,int r){
int mid=(l+r)/2;
if(l==r){
sumv[o]=maxv[o]=a[pre[l]];return;
}
build(o<<1,l,mid);build(o<<1|1,mid+1,r);
pushup(o);
}
void update(int o,int l,int r,int q,int v){
int mid=(l+r)/2;
if(l==r){
sumv[o]=maxv[o]=v;return;
}
if(q<=mid)update(o<<1,l,mid,q,v);
else update(o<<1|1,mid+1,r,q,v);
pushup(o);
}
int querysum(int o,int l,int r,int ql,int qr){
int mid=(l+r)/2,ans=0;
if(ql<=l&&r<=qr)return sumv[o];
if(ql<=mid)ans+=querysum(o<<1,l,mid,ql,qr);
if(qr>mid)ans+=querysum(o<<1|1,mid+1,r,ql,qr);
return ans;
}
int querymax(int o,int l,int r,int ql,int qr){
int mid=(l+r)/2,ans=-inf;
if(ql<=l&&r<=qr)return maxv[o];
if(ql<=mid)ans=max(ans,querymax(o<<1,l,mid,ql,qr));
if(qr>mid)ans=max(ans,querymax(o<<1|1,mid+1,r,ql,qr));
return ans;
}
int qsum(int u,int v){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans+=querysum(1,1,n,id[top[u]],id[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
ans+=querysum(1,1,n,id[v],id[u]);
return ans;
}
int qmax(int u,int v){
int ans=-inf;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans=max(ans,querymax(1,1,n,id[top[u]],id[u]));
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
ans=max(ans,querymax(1,1,n,id[v],id[u]));
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
int Laxt[maxn<<1],Next[maxn<<1],To[maxn<<1],Len[maxn<<1],ppp;

void add(int u,int v,int w){
Next[++ppp]=Laxt[u];Laxt[u]=ppp;To[ppp]=v;Len[ppp]=w;
Next[++ppp]=Laxt[v];Laxt[v]=ppp;To[ppp]=u;Len[ppp]=w;
}
int sz[maxn],d[maxn],vis[maxn],root,sum;
void findroot(int u,int fa){//找重心
sz[u]=1;d[u]=0;
for(int i=Laxt[u];i;i=Next[i]){
int v=To[i];
if(vis[v]||v==fa)continue;
findroot(v,u);
sz[u]+=sz[v];
d[u]=max(d[u],sz[v]);
}
d[u]=max(d[u],sum-sz[u]);
if(d[u]<d[root])root=u;
}
int dep[maxn],a[maxn];
void dfsdeep(int u,int fa){//计算长度
a[++a[0]]=dep[u];
for(int i=Laxt[u];i;i=Next[i]){
int v=To[i];
if(vis[v]||v==fa)continue;
dep[v]=dep[u]+Len[i];
dfsdeep(v,u);
}
}
int L;
int cal(int u,int now){//计算答案
dep[u]=now;a[0]=0;
dfsdeep(u,0);
sort(a+1,a+1+a[0]);
int l=1,r=a[0],ans=0;
while(l<r){
if(a[l]+a[r]<=L)ans+=r-l,l++;
else r--;
}
return ans;
}
int ans;
void solve(int u){//解决者
vis[u]=1;
ans+=cal(u,0);
for(int i=Laxt[u];i;i=Next[i]){
int v=To[i];
if(vis[v])continue;
ans-=cal(v,Len[i]);
sum=sz[v];
root=0;findroot(v,0);
solve(root);
}
}

可持续化字典树

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
int sum[maxn * 35], son[maxn * 35][2],root[maxn];

void insert(int pos,int pre,int &now,int v){

now = ++num;
son[now][0] = son[pre][0];
son[now][1] = son[pre][1];
sum[now] = sum[pre] + 1;
if(pos==-1)
return;
bool k = v & (1 << pos);
insert(pos - 1, son[pre][k], son[now][k], v);
}
int query(int pos,int st,int en,int v){
//cout << "pos=" << pos << " v=" << v << endl;
if(pos<0)
return 0;
int k = !(v & (1 << pos));
int tmp = sum[son[en][k]] - sum[son[st][k]];

if(tmp>0)
return query(pos - 1, son[st][k], son[en][k], v) + (1 << pos);
else
return query(pos - 1, son[st][k ^ 1], son[en][k ^ 1], v);
}
int cnt;

int val[maxn],in[maxn],out[maxn],id[maxn];

void dfs(int u){
in[u]=++cnt;
id[cnt]=u;
insert(30, root[in[u] - 1], root[cnt], val[u]);
for (int i = Laxt[u]; i;i=Next[i]){
dfs(To[i]);
}
out[u] = cnt;
}

主席树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sum[maxn*40],rt[maxn*40];
int lss[maxn*40],rss[maxn*40];
int cnt;
void update(int &o,int pre,int l,int r,int val){
o=++cnt;
lss[o]=lss[pre];rss[o]=rss[pre];
sum[o]=sum[pre];
if(l==r){sum[o]++;return;}
int mid=(l+r)/2;
if(val<=mid)update(lss[o],lss[pre],l,mid,val);
else update(rss[o],rss[pre],mid+1,r,val);
}
int query(int o,int l,int r,int val){
if(l==r){
return sum[o];
}
int mid=(l+r)/2;
if(val<=mid)return query(lss[o],l,mid,val);
else return query(rss[o],mid+1,r,val);
}
update(rt[i],rt[i-1],1,1e9,a[i]);
query(rt[r],1,1e9,gc)
//查询区间这个数的个数

权值线段树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ll sum[maxn*40],val[maxn*40];
int ls[maxn*40],rs[maxn*40];
int cnt;
void update(int &o,int l,int r,int k,int num){
if(!o)o=++cnt;
sum[o]+=k;
val[o]+=1LL*k*num;
val[o]%=mod;
if(l==r)return;
int mid=(l+r)/2;
if(num<=mid)update(ls[o],l,mid,k,num);
else update(rs[o],mid+1,r,k,num);
}
ll query(int o,int l,int r,ll k){
if(l==r)return 1LL*k*l%mod;
int mid=(l+r)/2;
ll res=0;
if(k>sum[ls[o]])res=(val[ls[o]]+query(rs[o],mid+1,r,k-sum[ls[o]]));
else res=query(ls[o],l,mid,k);
return res%mod;
}

图论

前向星

1
2
3
4
5
int Next[maxn<<1],To[maxn<<1],Laxt[maxn<<1];
int cnt;
void add(int u,int v){
Next[++cnt]=Laxt[u];Laxt[u]=cnt;To[cnt]=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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
struct Edge{
int u,v;
};
///割顶 bccno 无意义
int pre[maxn],iscut[maxn],bccno[maxn],dfs_clock,bcc_cut;
vector<int>G[maxn],bcc[maxn];
stack<Edge>S;
void init(int n){
for (int i = 0; i < n; i++) G[i].clear();
}
inline void add_edge(int u, int v) { G[u].push_back(v), G[v].push_back(u); }
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_cut++;bcc[bcc_cut].clear(); ///注意 bcc从1开始
for(;;){
Edge x=S.top();S.pop();
if(bccno[x.u] != bcc_cut){bcc[bcc_cut].push_back(x.u);bccno[x.u]=bcc_cut;}
if(bccno[x.v] != bcc_cut){bcc[bcc_cut].push_back(x.v);bccno[x.v]=bcc_cut;}
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 lowu;
}
void find_bcc(int n){
memset(pre, 0, sizeof(pre));
memset(iscut, 0, sizeof(iscut));
memset(bccno, 0, sizeof(bccno));
dfs_clock = bcc_cut = 0;
for(int i = 0; i < n;i++)
if(!pre[i])dfs(i,-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
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#include<vector>
#define N 201
vector<int>G[N];
int n,m,low[N],dfn[N];
bool is_cut[N];
int father[N];
int tim=0;
void input()
{
scanf("%d%d",&n,&m);
int a,b;
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);/*邻接表储存无向边*/
G[b].push_back(a);
}
}
void Tarjan(int i,int Father)
{
father[i]=Father;/*记录每一个点的父亲*/
dfn[i]=low[i]=tim++;
for(int j=0;j<G[i].size();++j)
{
int k=G[i][j];
if(dfn[k]==-1)
{
Tarjan(k,i);
low[i]=min(low[i],low[k]);
}
else if(Father!=k)/*假如k是i的父亲的话,那么这就是无向边中的重边,有重边那么一定不是桥*/
low[i]=min(low[i],dfn[k]);//dfn[k]可能!=low[k],所以不能用low[k]代替dfn[k],否则会上翻过头了。
}
}
void count()
{
int rootson=0;
Tarjan(1,0);
for(int i=2;i<=n;++i)
{
int v=father[i];
if(v==1)
rootson++;/*统计根节点子树的个数,根节点的子树个数>=2,就是割点*/
else{
if(low[i]>=dfn[v])/*割点的条件*/
is_cut[v]=true;
}
}
if(rootson>1)
is_cut[1]=true;
for(int i=1;i<=n;++i)
if(is_cut[i])
printf("%d\n",i);
for(int i=1;i<=n;++i)
{
int v=father[i];
if(v>0&&low[i]>dfn[v])/*桥的条件*/
printf("%d,%d\n",v,i);
}

}
int main()
{
input();
memset(dfn,-1,sizeof(dfn));
memset(father,0,sizeof(father));
memset(low,-1,sizeof(low));
memset(is_cut,false,sizeof(is_cut));
count();
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
vector<int>G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int>S;
inline void init(int n){
for (int i = 0; i < n; i++) G[i].clear();
}
inline void add_edge(int u, int v) { G[u].push_back(v); }
void dfs(int u){
pre[u]=lowlink[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);
lowlink[u]=min(lowlink[u],lowlink[v]);
}
else if(!sccno[v]){
lowlink[u]=min(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;
memset(sccno,0,sizeof(sccno));
memset(pre,0,sizeof(pre));
for(int i=0;i<n;i++)
if(!pre[i])dfs(i);
}

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
struct TwoSAT{
int n;
vector<int> G[maxn*2];
bool mark[maxn*2];
int S[maxn*2],c;

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

void init(int n){
this->n=n;
for(int i=0;i<n*2;i++)G[i].clear();
memset(mark,0,sizeof(mark));
}
/// x=xval or y= yval;
void add_caluse(int x,int xval,int y,int yval){
x=x*2+xval;
y=y*2+yval;
G[x^1].push_back(y);///如果x为真 那么y必须为假;
G[y^1].push_back(x);///如果y为真 那么x必须为假;
}

bool solve(){
for(int i=0;i<n*2;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;
}
};

最短路

Dijkstra +单源最短路+记录路径O(mlogn)

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
struct Edge{
int from,to,dist;
};
struct HeapNode{
int d,u;
bool operator <(const HeapNode& rhs)const{
return d>rhs.d;
}
};
struct Dijkstra{
int n,m; ///点数和边数 点编号0~N-1
vector<Edge>edges; ///边列表
vector<int>G[maxn]; ///每个节点出发的边编号
bool done[maxn]; /// 是否已永久标号
int d[maxn]; /// s到各个点的距离
int p[maxn]; /// 最短路中的上一条边

void init(int n){
this->n=n;
for(int i=0;i<n;i++)G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int dist){ ///无向图调用两次
edges.push_back((Edge){from,to,dist});
m=edges.size();
G[from].push_back(m-1);
}
void dijkstra(int s){
priority_queue<HeapNode>Q;
for(int i=0;i<n;i++)d[i]=inf;
d[s]=0;
memset(done,0,sizeof(done));
Q.push((HeapNode){0,s});
while(!Q.empty()){
HeapNode x=Q.top();Q.pop();
int u=x.u;
if(done[u])continue;
done[u]=true;
for(int i=0;i<G[u].size();i++){
Edge& e=edges[G[u][i]];
if(d[e.to]>d[u]+e.dist){
d[e.to]=d[u]+e.dist;
p[e.to]=G[u][i];
Q.push((HeapNode){d[e.to],e.to});
}
}
}
}
/// dist[i]为s到i的距离,paths[i]为s到i的最短路径(经过的结点列表,包括s和t)
void GetShortestPaths(int s,int* dist,vector<int>* paths){///paths是二维链表
dijkstra(s);
for(int i=0;i<n;i++){
dist[i]=d[i];
paths[i].clear();
int t=i;
paths[i].push_back(t);
while(t!=s){
paths[i].push_back(edges[p[t]].from);
t=edges[p[t]].from;
}
reverse(paths[i].begin(),paths[i].end());
}
}
};

BellmanFord (单源最短路判负环)O(nm)

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
struct Edge
{
int from, to;
int dist;
Edge() {}
Edge(int u, int v, int d) : from(u), to(v), dist(d) {}
};
struct BellmanFord{
int n,m;
vector<Edge>edges;
vector<int> G[maxn];
bool inq[maxn]; /// 是否在队列中
int d[maxn]; /// s到各个点的距离 double 要改成double类型
int p[maxn]; /// 最短路中的上一条弧
int cnt[maxn]; /// 进队次数
void init(int n){
this->n=n;
for(int i=0;i<n;i++)G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int dist)
{
edges.emplace_back(from, to, dist);
m = edges.size();
G[from].push_back(m - 1);
}
bool bellmanford(int s){
queue<int>Q;
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
for(int i = 0; i < n; i++) { d[i] = 0; inq[0] = true; Q.push(i); } ///如果只判负环用这个
//for(int i=0;i<n;i++)d[i]=inf;
//d[s]=0;inq[s]=true;Q.push(s);
while(!Q.empty()){
int u=Q.front();
Q.pop();
inq[u]=false;
for(auto& id:G[u]){
Edge& e=edges[id];
if(d[u]<inf && d[e.to]>d[u]+e.dist){
d[e.to]=d[u] + e.dist;
p[e.to]=id;
if(!inq[e.to]){
Q.push(e.to);
inq[e.to]=true;
if(++cnt[e.to]>n)return true;
}
}
}
}
return false;
}
};

Floyd 多源最短路 O(n^3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct{
char vertex[maxn]; //顶点表
int edges[maxn][maxn]; //邻接矩阵,可看做边表
int n,e; //图中当前的顶点数和边数
}MGraph;

void Floyd(MGraph g){
int A[maxn][maxn];
int path[maxn][maxn];
int i,j,k,n=g.n;
for(i=0;i<n;i++)
for(j=0;j<n;j++){
A[i][j]=g.edges[i][j];
path[i][j]=-1;
}
for(k=0;k<n;k++)
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(A[i][j]>(A[i][k]+A[k][j])){
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
}

Floyd 判环(龟兔赛跑算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(1)求环

初始状态下,假设已知某个起点节点为节点S。现设两个指针t和h,将它们均指向S。

同时让t和h往前推进,h的速度为t的2倍),直到h无法前进,即到达某个没有后继的节点时,就可以确定从S出发不会遇到环。反之当t与h再次相遇时,就可以确定从S出发一定会进入某个环,设其为环C。(h和t推进的步数差是环长的倍数)

(2)求环的长度

上述算法刚判断出存在环C时,t和h位于同一节点,设其为节点M。仅需令h不动,而t不断推进,最终又会返回节点M,统计这一次t推进的步数,就是环C的长度。

(3)求环的起点

为了求出环C的起点,只要令h仍均位于节点M,而令t返回起点节点S。随后,同时让t和h往前推进,且速度相同。持续该过程直至t与h再一次相遇,此相遇点就是环C的一个起点。

why?

假设出发起点到环起点的距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针(t)移动的总距离i = m + a * n + k,快指针(h)的移动距离为2i,2i = m + b * n + k。其中,a和b分别为t和h在第一次相遇时转过的圈数。让两者相减(快减慢),那么有i = (b - a) * n。即i是圈长度的倍数。

将一个指针移到出发起点S,另一个指针仍呆在相遇节点M处两者同时移动,每次移动一步。当第一个指针前进了m,即到达环起点时,另一个指针距离链表起点为i + m。考虑到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
/// 固定根的最小树型图,邻接矩阵写法
struct MDST{
int n;
int w[maxn][maxn]; ///边权
int vis[maxn]; ///访问标记,仅用来判断无解
int ans; ///计算答案
int removed[maxn]; ///每个点是否被删除
int cid[maxn]; ///所在圈编号
int pre[maxn]; ///最小入边的起点
int iw[maxn]; ///最小入边的权值
int max_cid; ///最大圈编号

void init(int n){
this->n=n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)w[i][j]=inf;
}
void AddEdge(int u,int v,int cost){
w[u][v]=min(w[u][v],cost); ///重边取权值最小的
}

///从s出发能到达多少个结点
int dfs(int s){
int ans=1;
vis[s]=1;
for(int i=0;i<n;i++)if(!vis[i]&&w[s][i]<inf)ans+=dfs(i);
return ans;
}
///从u出发沿着pre指针找圈
bool cycle(int u){
max_cid++;
int v=u;
while(cid[v]!=max_cid){cid[v]=max_cid;v=pre[v];}
return v==u;
}
/// 计算u的最小入弧,入弧起点不得在圈c中
void update(int u){
iw[u]=inf;
for(int i=0;i<n;i++)
if(!removed[i]&&w[i][u]<iw[u]){
iw[u]=w[i][u];
pre[u]=i;
}
}
///根节点为s,如果失败返回false
bool solve(int s){
memset(vis,0,sizeof(vis));
if(dfs(s)!=n)return false;
memset(removed,0,sizeof(removed));
memset(cid,0,sizeof(cid));
for(int u=0;u<n;u++)update(u);
pre[s]=s;iw[s]=0; /// 根结点特殊处理
ans=max_cid=0;
for(;;){
bool have_cycle=false;
for(int u=0;u<n;u++)if(u!=s&&!removed[u]&&cycle(u)){
have_cycle=true;
/// 以下代码缩圈,圈上除了u之外的结点均删除
int v=u;
do{
if(v!=u)removed[v]=1;
ans+=iw[v];
/// 对于圈外点i,把边i->v改成i->u(并调整权值);v->i改为u->i
/// 注意圈上可能还有一个v'使得i->v'或者v'->i存在,因此只保留权值最小的i->u和u->i
for(int i=0;i<n;i++)if(cid[i]!=cid[u]&&!removed[i]){
if(w[i][v]<inf)w[i][u]=min(w[i][u],w[i][v]-iw[v]);
w[u][i]=min(w[u][i],w[v][i]);
if(pre[i]==v)pre[i]=u;
}
v=pre[v];
}while(v!=u);
update(u);
break;
}
if(!have_cycle)break;
}
for(int i=0;i<n;i++)
if(!removed[i])ans+=iw[i];
return true;
}
};

二分图

二分图判断

1
2
3
4
5
6
7
8
9
10
11
12
color[maxn];
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,b))return false;
}
}
return true;
}

二分图最大基数匹配

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
/// 二分图最大基数匹配
struct BPM{
int n,m; /// 左右顶点个数
vector<int>G[maxn]; /// 邻接表
int left[maxn]; /// left[i]为右边第i个点的匹配点编号,-1表示不存在
bool T[maxn]; /// T[i]为右边第i个点是否已标记

int right[maxn]; /// 求最小覆盖用
bool S[maxn]; /// 求最小覆盖用

void init(int n,int m){
this->n=n;
this->m=m;
for(int i=0;i<n;i++)G[i].clear();
}

void AddEdge(int u,int v){
G[u].push_back(v);
}

bool match(int u){
S[u]=true;
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(!T[v]){
T[v]=true;
if(left[v]==-1||match(left[v])){
left[v]=u;
right[u]=v;
return true;
}
}
}
return false;
}
/// 求最大匹配
int solve(){
memset(left,-1,sizeof(left));
memset(right,-1,sizeof(right));
int ans=0;
for(int u=0;u<n;u++){
memset(S,0,sizeof(S));
memset(T,0,sizeof(T));
if(match(u))ans++;
}
return ans;
}
/// 求最小覆盖。X和Y为最小覆盖中的点集
int mincover(vector<int>& X,vector<int>& Y){
int ans=solve();
memset(S,0,sizeof(S));
memset(T,0,sizeof(T));
for(int u=0;u<n;u++)
if(right[u]==-1)match(u);
for(int u=0;u<n;u++)
if(!S[u])X.push_back(u);
for(int v=0;v<n;v++)
if(T[v])Y.push_back(v);
return ans;
}
};

二分图最大权匹配(KM算法)

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
int g[150][150];  ///存图
int nx,ny; /// 两边点数
bool visx[maxn],visy[maxn];
int slack[maxn];
int linker[maxn]; ///y中各点匹配状态
int lx[maxn],ly[maxn]; /// x,y中的点标号
bool dfs(int x){
visx[x]=true;
for(int y=0;y<ny;y++){
if(visy[y])continue;
int tmp=lx[x]+ly[y]-g[x][y];
if(tmp==0){
visy[y]=true;
if(linker[y]==-1||dfs(linker[y])){
linker[y]=x;return true;
}
}
else if(slack[y]>tmp)slack[y]=tmp;
}
return false;
}
int KM(){
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=0;i<nx;i++){
lx[i]=-inf;
for(int j=0;j<ny;j++){
if(g[i][j]>lx[i])lx[i]=g[i][j];
}
}
for(int x=0;x<nx;x++){
for(int i=0;i<ny;i++)slack[i]=inf;
while(true){
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x))break;
int d=inf;
for(int i=0;i<ny;i++)
if(!visy[i]&&d>slack[i])d=slack[i];
for(int i=0;i<nx;i++)
if(visx[i])lx[i]-=d;
for(int i=0;i<ny;i++)
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
int res=0;
for(int i=0;i<ny;i++)
if(linker[i]!=-1)res+=g[linker[i]][i];
return res;
}

网络流

Edge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Edge
{
int from, to, cap, flow;
Edge(int u, int v, int c, int f)
: from(u), to(v), cap(c), flow(f) {}
};
// ---
// 费用流
// ---
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) {}
};

EdmondKarp

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
const int maxn = "Edit";
struct EdmonsKarp //时间复杂度O(v*E*E)
{
int n, m;
vector<Edge> edges; //边数的两倍
vector<int> G[maxn]; //邻接表,G[i][j]表示节点i的第j条边在e数组中的序号
int a[maxn]; //起点到i的可改进量
int p[maxn]; //最短路树上p的入弧编号
void init(int n)
{
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.emplace_back(from, to, cap, 0);
edges.emplace_back(to, from, 0, 0); //反向弧
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
int Maxflow(int s, int t)
{
int flow = 0;
for (;;)
{
memset(a, 0, sizeof(a));
queue<int> q;
q.push(s);
a[s] = INF;
while (!q.empty())
{
int x = q.front();
q.pop();
for (int i = 0; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (!a[e.to] && e.cap > e.flow)
{
p[e.to] = G[x][i];
a[e.to] = min(a[x], e.cap - e.flow);
q.push(e.to);
}
}
if (a[t]) break;
}
if (!a[t]) break;
for (int u = t; u != s; u = edges[p[u]].from)
{
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
flow += a[t];
}
return flow;
}
};

Dinic

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
const int maxn = "Edit";
struct Dinic
{
int n, m, s, t; //结点数,边数(包括反向弧),源点编号和汇点编号
vector<Edge> edges; //边表。edge[e]和edge[e^1]互为反向弧
vector<int> G[maxn]; //邻接表,G[i][j]表示节点i的第j条边在e数组中的序号
bool vis[maxn]; //BFS使用
int d[maxn]; //从起点到i的距离
int cur[maxn]; //当前弧下标
void init(int n)
{
this->n = n;
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.emplace_back(from, to, cap, 0);
edges.emplace_back(to, from, 0, 0);
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while (!q.empty())
{
int x = q.front();
q.pop();
for (int i = 0; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[x] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a)
{
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++)
{ //从上次考虑的弧
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
this->s = s, this->t = t;
int flow = 0;
while (BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
vector<int>Mincut(){ /// call this after maxflow
vector<int>ans;
for(int i=0;i<edges.size();i++){
Edge& e=edges[i];
if(vis[e.from]&&!vis[e.to]&&e.cap>0)ans.push_back(i);
}
return ans;
}
};

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
113
114
115
116
const int maxn = "Edit";
struct ISAP
{
int n, m, s, t; //结点数,边数(包括反向弧),源点编号和汇点编号
vector<Edge> edges; //边表。edges[e]和edges[e^1]互为反向弧
vector<int> G[maxn]; //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[maxn]; //BFS使用
int d[maxn]; //起点到i的距离
int cur[maxn]; //当前弧下标
int p[maxn]; //可增广路上的一条弧
int num[maxn]; //距离标号计数
void init(int n)
{
this->n = n;
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.emplace_back(from, to, cap, 0);
edges.emplace_back(to, from, 0, 0);
int m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
int Augumemt()
{
int x = t, a = INF;
while (x != s)
{
Edge& e = edges[p[x]];
a = min(a, e.cap - e.flow);
x = edges[p[x]].from;
}
x = t;
while (x != s)
{
edges[p[x]].flow += a;
edges[p[x] ^ 1].flow -= a;
x = edges[p[x]].from;
}
return a;
}
void BFS()
{
memset(vis, 0, sizeof(vis));
memset(d, 0, sizeof(d));
queue<int> q;
q.push(t);
d[t] = 0;
vis[t] = 1;
while (!q.empty())
{
int x = q.front();
q.pop();
int len = G[x].size();
for (int i = 0; i < len; i++)
{
Edge& e = edges[G[x][i] ^ 1];
if (!vis[e.from] && e.cap > e.flow)
{
vis[e.from] = 1;
d[e.from] = d[x] + 1;
q.push(e.from);
}
}
}
}
int Maxflow(int s, int t)
{
this->s = s;
this->t = t;
int flow = 0;
BFS();
memset(num, 0, sizeof(num));
for (int i = 0; i < n; i++)
if (d[i] < INF) num[d[i]]++;
int x = s;
memset(cur, 0);
while (d[s] < n)
{
if (x == t)
{
flow += Augumemt();
x = s;
}
int ok = 0;
for (int i = cur[x]; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (e.cap > e.flow && d[x] == d[e.to] + 1)
{
ok = 1;
p[e.to] = G[x][i];
cur[x] = i;
x = e.to;
break;
}
}
if (!ok) //Retreat
{
int m = n - 1;
for (int i = 0; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (e.cap > e.flow) m = min(m, d[e.to]);
}
if (--num[d[x]] == 0) break; //gap优化
num[d[x] = m + 1]++;
cur[x] = 0;
if (x != s) x = edges[p[x]].from;
}
}
return flow;
}
};

MinCost MaxFlow

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 maxn = "Edit";
struct MCMF
{
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
int inq[maxn]; //是否在队列中
int d[maxn]; //bellmanford
int p[maxn]; //上一条弧
int a[maxn]; //可改进量
void init(int n)
{
this->n = n;
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap, int cost)
{
edges.emplace_back(from, to, cap, 0, cost);
edges.emplace_back(to, from, 0, 0, -cost);
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BellmanFord(int s, int t, int& flow, ll& 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; // 当没有可增广的路时退出
flow += a[t];
cost += (ll)d[t] * (ll)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, ll& cost)
{
int flow = 0;
cost = 0;
while (BellmanFord(s, t, flow, cost));
return flow;
}
};

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
struct LCA{
int n;
int fa[maxn]; ///父亲数组
int cost[maxn]; ///和父亲的费用
int L[maxn]; ///层次(根节点为0)
int anc[maxn][logmaxn]; /// anc[p][i]是结点p的第2^i级父亲。anc[i][0] = fa[i]
int maxcost[maxn][logmaxn]; /// maxcost[p][i]是i和anc[p][i]的路径上的最大费用
void preprocess(){
for(int i=0;i<n;i++){
anc[i][0]=fa[i];maxcost[i][0]=cost[i];
for(int j=1;(1<<j)<n;j++)anc[i][j]=-1;
}
for(int j=1;(1<<j)<n;j++)
for(int i=0;i<n;i++)
if(anc[i][j-1]!=-1){
int a=anc[i][j-1];
anc[i][j]=anc[a][j-1];
maxcost[i][j]=max(maxcost[i][j-1],maxcost[a][j-1]);
}
}
/// 求p到q的路径上的最大权
int query(int p,int q){
int tmp,log,i;
if(L[p]<L[q])swap(p,q);
for(log=1;(1<<log)<=L[p];log++);log--;
int ans=-inf;
for(int i=log;i>=0;i--)
if(L[p]-(1<<i)>=L[q]){ans=max(ans,maxcost[p][i]);p=anc[p][i];}

if(p==q)return ans; ///LCA为p

for(int i=log;i>=0;i--)
if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i]){
ans=max(ans,maxcost[p][i]);p=anc[p][i];
ans=max(ans,maxcost[q][i]);q=anc[q][i];
}
ans=max(ans,cost[p]);
ans=max(ans,cost[q]);
return ans; ///LCA为fa[p](它也等于fa[q])
}
};

倍增简单写法

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
int mi[maxn][22],fa[maxn][22],dep[maxn];
void dfs(int now,int pre,int w,int deep){
fa[now][0]=pre;
mi[now][0]=w;
if(pre==0)mi[now][0]=inf,fa[now][0]=now;
dep[now]=deep;
for(auto i:ve[now]){
if(i.to==pre)continue;
dfs(i.to,now,i.w,deep+1);
}
}
void init(){
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++){
int p=fa[j][i-1];
fa[j][i]=fa[p][i-1];
mi[j][i]=min(mi[j][i-1],mi[p][i-1]);
}
}
int lca(int p,int q){
if(dep[p]<dep[q])swap(p,q);
int res=inf;
for(int i=20;i>=0;i--)
if(dep[fa[p][i]]>=dep[q])
res=min(res,mi[p][i]),p=fa[p][i];
if(p==q)return res;
for(int i=20;i>=0;i--){
if(fa[p][i]!=fa[q][i]){
res=min(res,mi[p][i]);
res=min(res,mi[q][i]);
p=fa[p][i];q=fa[q][i];
}
}
return min(res,min(mi[p][0],mi[q][0]));
}

数学

一些公式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//平方和公式 sigm(i^2)=n*(n+1)*(2n+1)/6
//皮克定理 多边形面积S = 多边形内整数点的个数 n + 多边形边上整数点的个数 / 2 - 1
//边界的点数用gcd(a.x-b.x,a.y-b.y)计算
//当O点为原点时,根据向量的叉积计算公式,各个三角形的面积计算如下:
//S_OAB = 0.5*(A_x*B_y - A_y*B_x) 【(A_x,A_y)为A点的坐标】
//S_OBC = 0.5*(B_x*C_y - B_y*C_x)
/*
斯特林公式
int digit_stirling(int n)///求n!的位数
{
double PI=acos(double(-1));// PI的值=反余弦函数 -1.0为Pi, 1为0。
double e=exp(double(1));//e的值
return floor(log10(sqrt(2*PI*n))+n*log10(n/e))+1;
}
*/
//G[X]表示:1-X的数字和的总和;

//根据G[10^X]=45*X*10^(X-1);所以G[10^18]=45*18*10^17;
//等差数列求和:Sn=n*a1+n(n-1)d/2或Sn=n(a1+an)/2
//等比数列求和:Sn=a1*(1-q^n)/(1-q)=(a1-an*q)/(1-q)

莫比乌斯反演

组合数计数

1
2
3
4
5
6
7
8
    inv[0]=p[0]=1;
for(int i=1;i<=n;i++)
p[i]=p[i-1]*i%mod,inv[i]=ksm(p[i],mod-2);
}
ll cal(int n,int m){
if(n<m)return 0LL;
return p[n]*inv[n-m]%mod*inv[m]%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
ll pow_mod(ll x, ll n, ll mod){
ll res=1;
while(n){
if(n&1)res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll inv(ll a,ll p){return pow_mod(a,p-2,p);}
ll cal(ll a,ll b,ll p){
if(a<b)return 0;
if(b>a-b)b=a-b;
ll fz=1,fm=1;
for(ll i=0;i<b;i++){
fz=fz*(a-i)%p;
fm=fm*(i+1)%p;
}
return fz*inv(fm,p)%p;
}

ll lucas(ll a,ll b,ll p){
if(b==0)return 1;
return cal(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}

逆元

如果mod不是素数 那么a关于mod的逆元是 a^(phi(mod)-1)

ExGcd求逆元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///返回 d=gcd(a,b); 和对应于等式 ax+by=d 中的 x,y
ll extend_gcd(ll a,ll b,ll &x,ll &y){
if(a==0&&b==0)return -1;
if(b==0){x=1;y=0;return a;}
ll d=extend_gcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
///ax=1(mod n)
ll mod_reverse(ll a,ll n){
ll x,y;
ll d=extend_gcd(a,n,x,y);
if(d==1)return (x%n+n)%n;
else return -1;
}

费马小定理

1
2
3
4
5
6
7
8
9
10
11
///a<mod 并且 p为素数
ll pow_mod(ll x, ll n, ll mod){
ll res=1;
while(n){
if(n&1)res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll inv(ll a,ll p){return pow_mod(a,p-2,p);}

逆元线性筛

1
2
inv[1]=1;
for (int i = 2; i < maxn; i++) inv[i] = inv[p % i] * (p - p / i) % p;

杜教筛

设需要计算的式子为:$\sum_{i=1}^{n}f(i)(f(i)为积性函数)$

我们构造两个积性函数$h$和$g$。使得$h=f∗g$

现在我们开始求$\sum_{i=1}^{n}h(i)$

记$S(n)=\sum_{i=1}^{n}f(i)$

$\sum_{i=1}^{n}h(i)=\sum_{i=1}^{n}\sum_{d|i}g(d)\cdot f(\frac{i}{d})\\\to =\sum_{d=1}^{n}g(d)\cdot\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f({i})$

$\to \sum_{i=1}^{n}h(i)=\sum_{d=1}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)$

接着,我们将右边式子的第一项给提出来,可以得到:

$\sum_{i=1}^{n}h(i)=g(1)\cdot S(n)+\sum_{d=2}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)$

$\to g(1)S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor) $

其中$h(i)=(f*g)(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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=6e6+50;
unordered_map<int,int>m;
unordered_map<ll,ll>p;
bool vis[maxn];
int mu[maxn],sum_mu[maxn],phi[maxn];
ll sum_phi[maxn];
int cnt,prime[maxn];

void init(int N){
phi[1]=mu[1]=1;
for(int i=2;i<=N;i++){
if(!vis[i]){
prime[++cnt]=i;
mu[i]=-1;phi[i]=i-1;
}
for(int j=1;j<=cnt;j++){
if(i*prime[j]>N)break;
vis[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
mu[i*prime[j]]=-mu[i];
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(int i=1;i<=N;i++){
sum_mu[i]=sum_mu[i-1]+mu[i];
sum_phi[i]=sum_phi[i-1]+phi[i];
}
}
int getmu(int x){
if(x<=6e6)return sum_mu[x];
if(m[x])return m[x];
int ans=1;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
ans-=(r-l+1)*getmu(x/l);
}
return m[x]=ans;
}
ll getphi(ll x){
if(x<=6e6)return sum_phi[x];
if(p[x])return p[x];
ll ans=x*(x+1)/2;
for(ll l=2,r;l<=x;l=r+1){
r=x/(x/l);
ans-=(r-l+1)*getphi(x/l);

}
return p[x]=ans;
}
int main(){
int t,n;
cin>>t;
init(6e6);
while(t--){
cin>>n;
cout<<getphi(n)<<" "<<getmu(n)<<endl;
}
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
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;
for(int i=1;i<=prime[0]&&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;
}

欧拉函数

欧拉降幂

A^Bmod C=A^(B mod phji(C)) %C gcd(a,c)==1

A^B mod C=A^B %C (B<phi(C))

A^B mod C=A^(B mod phi(C)+phi(C))%C (B>=phi(C))

得出单个数的欧拉函数 O(sqrt n)

1
2
3
4
5
6
7
8
9
10
11
12
ll euler(ll n)
{
ll rt = n;
for (int i = 2; i * i <= n; i++)
if (n % i == 0)
{
rt -= rt / i;
while (n % i == 0) n /= i;
}
if (n > 1) rt -= rt / n;
return rt;
}

筛法欧拉函数

1
2
3
4
5
6
7
8
9
10
11
12
const int N = "Edit";
int phi[N] = {0, 1};
void caleuler()
{
for (int i = 2; i < N; i++)
if (!phi[i])
for (int j = i; j < N; j += i)
{
if (!phi[j]) phi[j] = j;
phi[j] = phi[j] / i * (i - 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
bool check[maxn];
int phi[maxn];
int prime[maxn];
int tot;
void phi_and_prime_table(int N){
memset(check,false,sizeof(check));
phi[1]=1;
tot=0;
for(int i=2;i<=N;i++){
if(!check[i]){
prime[tot++]=i;
phi[i]=i-1;
}
for(int j=0;j<tot;j++){
if(i*prime[j]>N)break;
check[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else{
phi[i*prime[j]]=phi[i]*(prime[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
int prime[maxn];
bool notprime[maxn];
int mu[maxn];
void getMobius(int N){
mu[1]=1; ///μ函数的1是特殊情况
int tot=0;
for(int i=2;i<=N;i++){
if(!notprime[i]){
prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++){
if(i*prime[j]>N)break;
notprime[i*prime[j]]=true;
if(i%prime[j]==0){
mu[i*prime[j]]=0; ///i中已经包括了prime[j]
break;
}
else{
mu[i*prime[j]]=-mu[i];
}
}
}
}

模线性方程

中国剩余定理

1
2
3
4
5
6
7
8
9
10
11
///要求m 两两互质 X=ri(mod mi) 引用返回通解 X=re+k*mo
void crt(ll r[],ll m[],ll n,ll &re,ll &mo){
mo=1,re=0;
for(int i=0;i<n;i++)mo*=m[i];
for(int i=0;i<n;i++){
ll x,y,tm=mo/m[i];
ll d=exgcd(tm,m[i],x,y);
re=(re+tm*x*r[i])%mo;
}
re=(re+mo)%mo;
}

ExCRT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//mi可以不两两互质
bool excrt(ll r[], ll m[], ll n, ll &re, ll &mo)
{
ll x, y;
mo = m[0], re = r[0];
for (int i = 1; i < n; i++)
{
ll d = exgcd(mo, m[i], x, y);
if ((r[i] - re) % d != 0) return 0;
x = (r[i] - re) / d * x % (m[i] / d);
re += x * mo;
mo = mo / d * m[i];
re %= mo;
}
re = (re + mo) % mo;
return 1;
}

大步小步算法 a^x=b(mod n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 求解模方程a^x=b(mod n)。n 为素数。无解返回-1
int log_mod(int a, int b) {
int m, v, e = 1, i;
m = (int)sqrt(MOD);
v = inv(pow_mod(a, m));
map <int,int> x;
x[1] = 0;
for(i = 1; i < m; i++){ e = mul_mod(e, a); if (!x.count(e)) x[e] = i; }
for(i = 0; i < m; i++){
if(x.count(b)) return i*m + x[b];
b = mul_mod(b, v);
}
return -1;
}

扩展大步小步(gcd!=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
int ex_BSGS(int A,int B,int C)
{
if(B==1) return 0;
int k=0,tmp=1,d;
while(1)
{
d=__gcd(A,C);
if(d==1) break;
if(B%d) return -1;
B/=d; C/=d;
tmp=1LL*tmp*(A/d)%C;
k++;
if(tmp==B) return k;
}
map<int,int>mp;
mp.clear();
int mul=B;
mp[B]=0;
int m=ceil(sqrt(1.0*C));
for(int j=1;j<=m;++j)
{
mul=1LL*mul*A%C;
mp[mul]=j;
}
int am=fastpow(A,m,C);
mul=tmp;
for(int j=1;j<=m;++j)
{
mul=1LL*mul*am%C;
if(mp.count(mul)) return j*m-mp[mul]+k;
}
return -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
struct Linear_Basis{
long long d[65],p[65];
int cnt;
Linear_Basis(){
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}

bool ins(long long val){
for (int i=62;i>=0;i--) {
if (val&(1LL<<i)){
if (!d[i]){
d[i]=val;
break;
}
val^=d[i];
}
}
return val>0;
}

long long query_max(long long D=0LL){
long long ret=D;
for (int i=62;i>=0;i--)
if ((ret^d[i])>ret) ret^=d[i];
return ret;
}

long long query_min(long long D=0LL){
long long ret=D;
for(int i=0;i<=62;i++){
if(d[i]) ret^=d[i];
}
return ret;
}

void rebuild(){
for (int i=1;i<=62;i++)
for (int j=0;j<i;j++)
if (d[i]&(1LL<<j)) d[i]^=d[j];
cnt=0;
for (int i=0;i<=62;i++)
if (d[i]) p[cnt++]=d[i];
}
//第K大
long long kthquery(long long k){
long long ret=0;
if (k>=(1LL<<cnt)) return -1;
for (int i=0;i<=62;i++)
if (k&(1LL<<i)) ret^=p[i];
return ret;
}
Linear_Basis merge(const Linear_Basis &n1,const Linear_Basis &n2){
Linear_Basis ret=n1;
for(int i=62;i>=0;i--)
if(n2.d[i])ret.ins(n2.d[i]);
return ret;
}
};

几何

弧度转换,点绕点旋转

其中顺时针旋转为负,逆时针旋转为正,角度angle是弧度值,如旋转30度转换为弧度为: $angle = pi/180 * 30$。

4、度与角度的转换
根据圆为$360 º$,弧度为$2π$,即 $360º = 2π$

4.1 角度转弧度:$2π / 360 = π / 180 ≈ 0.0174rad$, 即: $度数 (π / 180) = 弧度$
例如:将30º转为弧度rad
$ 30º
(π / 180)= 0.523320 rad $

4.2 弧度转角度: $360 / 2π = 180 / π ≈ 57.3º$, 即: $ 弧度 (180 / π) = 度数$
例如:将$0.523320rad$转为度º
$0.523320rad
(180 / π) = 29.9992352688º $

若o不是原点,则可先将a点坐标转换为相对坐标计算,计算结果再加上o点坐标。

参与计算的a点坐标实际应为 a - 0,最终计算公式如下:

$b.x = ( a.x - o.x)cos(angle) - (a.y - o.y)sin(angle) + o.x$

$b.y = (a.x - o.x)sin(angle) + (a.y - o.y)cos(angle) + o.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
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
#define mp make_pair
typedef long long ll;
const double inf=1e200;
const double eps=1e-8;
const double pi=4*atan(1.0);
struct point{
double x,y;
point(double a=0,double b=0):x(a),y(b){}
};
int dcmp(double x){ return fabs(x)<eps?0:(x<0?-1:1);}
point operator +(point A,point B) { return point(A.x+B.x,A.y+B.y);}
point operator -(point A,point B) { return point(A.x-B.x,A.y-B.y);}
point operator *(point A,double p){ return point(A.x*p,A.y*p);}
point operator /(point A,double p){ return point(A.x/p,A.y/p);}
point rotate(point A,double rad){
return point(A.x*cos(rad)-A.y*sin(rad), A.x*sin(rad)+A.y*cos(rad));
}
bool operator ==(const point& a,const point& b) {
return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}
double dot(point A,point B){ return A.x*B.x+A.y*B.y;}
double det(point A,point B){ return A.x*B.y-A.y*B.x;}
double dot(point O,point A,point B){ return dot(A-O,B-O);}
double det(point O,point A,point B){ return det(A-O,B-O);}
double length(point A){ return sqrt(dot(A,A));}
double angle(point A,point B){ return acos(dot(A,B)/length(A)/length(B));}
double distoline(point P,point A,point B)
{
//点到直线距离
point v1=B-A,v2=P-A;
return fabs(det(v1,v2)/length(v1));
}
double distoseg(point P,point A,point B)
{
//点到线段距离
if(A==B) return length(P-A);
point v1=B-A,v2=P-A,v3=P-B;
if(dcmp(dot(v1,v2))<0) return length(v2);
else if(dcmp(dot(v1,v3))>0) return length(v3);
return fabs(det(v1,v2)/length(v1));
}
double Ployarea(vector<point>p)
{
//多边形面积
double ans=0; int sz=p.size();
for(int i=1;i<sz-1;i++) ans+=det(p[i]-p[0],p[i+1]-p[0]);
return ans/2.0;
}
bool SegmentProperIntersection(point a1,point a2,point b1,point b2) {
//规范相交
double c1=det(a2-a1,b1-a1),c2=det(a2-a1,b2-a1);
double c3=det(b2-b1,a1-b1),c4=det(b2-b1,a2-b1);
return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
}
bool isPointOnSegment(point p,point a1,point a2)
{
//点是否在线段上
return dcmp(det(a1-p,a2-p)==0&&dcmp(dot(a1-p,a2-p))<0);
}
int isPointInPolygon(point p,vector<point>poly)
{
//判断点与多边形的位置关系
int wn=0,sz=poly.size();
for(int i=0;i<sz;i++){
//在边上
if(isPointOnSegment(p,poly[i],poly[(i+1)%sz])) return -1;
int k=dcmp(det(poly[(i+1)%sz]-poly[i],p-poly[i]));
int d1=dcmp(poly[i].y-p.y); int d2=dcmp(poly[(i+1)%sz].y-p.y);
if(k>0&&d1<=0&&d2>0) wn++;
if(k<0&&d2<=0&&d1>0) wn--;
}
if(wn!=0) return 1;//内部
return 0; //外部
}
double seg(point O,point A,point B){
if(dcmp(B.x-A.x)==0) return (O.y-A.y)/(B.y-A.y);
return (O.x-A.x)/(B.x-A.x);
}
pair<double,int>s[110*60];
double polyunion(vector<point>*p,int N){ //有多个才加*,单个不加,有改变的加&
//求多边形面积并
double res=0;
for(int i=0;i<N;i++){
int sz=p[i].size();
for(int j=0;j<sz;j++){
int m=0;
s[++m]=mp(0,0);
s[++m]=mp(1,0);
point a=p[i][j],b=p[i][(j+1)%sz];
for(int k=0;k<N;k++){
if(i!=k){
int sz2=p[k].size();
for(int ii=0;ii<sz2;ii++){
point c=p[k][ii],d=p[k][(ii+1)%sz2];
int c1=dcmp(det(b-a,c-a));
int c2=dcmp(det(b-a,d-a));
if(c1==0&&c2==0){
if(dcmp(dot(b-a,d-c))){
s[++m]=mp(seg(c,a,b),1);
s[++m]=mp(seg(c,a,b),-1);
}
}
else{
double s1=det(d-c,a-c);
double s2=det(d-c,b-c);
if(c1>=0&&c2<0) s[++m]=mp(s1/(s1-s2),1);
else if(c1<0&&c2>=0) s[++m]=mp(s1/(s1-s2),-1);
}
}
}
}
sort(s+1,s+m+1);
double pre=min(max(s[1].first,0.0),1.0),now,sum=0;
int cov=s[0].second;
for(int j=2;j<=m;j++){
now=min(max(s[j].first,0.0),1.0);
if(!cov) sum+=now-pre;
cov+=s[j].second;
pre=now;
}
res+=det(a,b)*sum;
}
}
return -(res/2);
}
point jiaopoint(point p,point v,point q,point w)
{ //p+tv q+tw,点加向量表示直线,求直线交点
point u=p-q;
double t=det(w,u)/det(v,w);
return p+v*t;
}
point GetCirPoint(point a,point b,point c)
{
point p=(a+b)/2; //ad中点
point q=(a+c)/2; //ac中点
point v=rotate(b-a,pi/2.0),w=rotate(c-a,pi/2.0); //中垂线的方向向量
if (dcmp(length(det(v,w)))==0) //平行
{
if(dcmp(length(a-b)+length(b-c)-length(a-c))==0) return (a+c)/2;
if(dcmp(length(b-a)+length(a-c)-length(b-c))==0) return (b+c)/2;
if(dcmp(length(a-c)+length(c-b)-length(a-b))==0) return (a+b)/2;
}
return jiaopoint(p,v,q,w);
}
point MinCircular(point *P,int n)
{
//最小圆覆盖 ,看起来是O(N^3),期望复杂度为O(N)
random_shuffle(P+1,P+n+1); //随机化
point c=P[1]; double r=0; //c 圆心,,//r 半径
for (int i=2;i<=n;i++)
if (dcmp(length(c-P[i])-r)>0) //不在圆内
{
c=P[i],r=0;
for (int j=1;j<i;j++)
if (dcmp(length(c-P[j])-r)>0)
{
c=(P[i]+P[j])/2.0;
r=length(c-P[i]);
for (int k=1;k<j;k++)
if (dcmp(length(c-P[k])-r)>0)
{
c=GetCirPoint(P[i],P[j],P[k]);
r=length(c-P[i]);
}
}
}
//cout<<r<<":"<<c.x<<" "<<c.y<<endl;
return c;
}
const int maxn=400010;
bool cmp(point a,point b){ return a.x==b.x?a.y<b.y:a.x<b.x; }
point f[maxn],c[maxn],ch[maxn];
void convexhull(point *a,int n,int &top)
{
//水平序的Andrew算法求凸包。
sort(a+1,a+n+1,cmp); top=0;
for(int i=1;i<=n;i++){ //求下凸包
while(top>=2&&det(ch[top-1],ch[top],a[i])<=0) top--;
ch[++top]=a[i];
}
int ttop=top;
for(int i=n-1;i>=1;i--){ //求上凸包
while(top>ttop&&det(ch[top-1],ch[top],a[i])<=0) top--;
ch[++top]=a[i];
}
}
void P(double x)
{
//if(fabs(x)<0.000001) puts("0.00000000"); else
printf("%.9lf",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
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
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e2 + 5;
const double pi = acos(-1.0);
const double eps = 1e-8;

int Sgn(double X) {
if (fabs(X) < eps) {
return 0;
}
return X < 0 ? -1 : 1;
}

struct Point {
double X, Y, Z;

void Input() {
scanf("%lf%lf%lf", &X, &Y, &Z);
}
};

typedef Point Vector;

Vector operator + (Vector Key1, Vector Key2) {
return (Vector){Key1.X + Key2.X, Key1.Y + Key2.Y, Key1.Z + Key2.Z};
}

Vector operator - (Vector Key1, Vector Key2) {
return (Vector){Key1.X - Key2.X, Key1.Y - Key2.Y, Key1.Z - Key2.Z};
}

double operator * (Vector Key1, Vector Key2) {
return Key1.X * Key2.X + Key1.Y * Key2.Y + Key1.Z * Key2.Z;
}

double Length(Vector Key) {
return sqrt(Key * Key);
}

double operator ^ (Vector Key1, Vector Key2) {
return Length((Vector){Key1.Y * Key2.Z - Key1.Z * Key2.Y, Key1.Z * Key2.X - Key1.X * Key2.Z, Key1.X * Key2.Y - Key1.Y * Key2.X});
}

double DisPointToPoint(Point Key1, Point Key2) {
return sqrt((Key1 - Key2) * (Key1 - Key2));
}

struct Sphere {
double pi=acos(-1.0);
Point Center;
double Radius;

void Input() {
Center.Input();
scanf("%lf", &Radius);
}
double get(){
return (4.0/3.0)*pi*Radius*Radius*Radius;
}
};

double CalVolume(Sphere Key) {
return 4.0 / 3.0 * pi * Key.Radius * Key.Radius * Key.Radius;
}

double SphereIntersectVolume(Sphere Key1, Sphere Key2) {
double Ans = 0.0;
double Dis = DisPointToPoint(Key1.Center, Key2.Center);
if (Sgn(Dis - Key1.Radius - Key2.Radius) >= 0) {
return Ans;
}
if (Sgn(Key2.Radius - (Dis + Key1.Radius)) >= 0) {
return CalVolume(Key1);
}
else if (Sgn(Key1.Radius - (Dis + Key2.Radius)) >= 0) {
return CalVolume(Key2);
}
double Length1 = ((Key1.Radius * Key1.Radius - Key2.Radius * Key2.Radius) / Dis + Dis) / 2;
double Length2 = Dis - Length1;
double X1 = Key1.Radius - Length1, X2 = Key2.Radius - Length2;
double V1 = pi * X1 * X1 * (Key1.Radius - X1 / 3.0);
double V2 = pi * X2 * X2 * (Key2.Radius - X2 / 3.0);
return V1 + V2;
}

int T;
int N;
Sphere a;
Sphere b;
double ans;

int main(int argc, char *argv[]) {

a.Input();
b.Input();
ans=a.get()+b.get()-SphereIntersectVolume(a,b);
cout<<fixed<<setprecision(15);
cout<<ans<<endl;
return 0;
}

其他

技巧

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
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
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Scanner;

Scanner cin=new Scanner(System.in);

BigInteger num1=new BigInteger("12345");
BigInteger num2=cin.nextBigInteger();

BigDecimal num3=new BigDecimal("123.45");
BigDecimal num4=cin.nextBigDecimal();
import java.math.BigInteger;

public class Main {
public static void main(String[] args) {
BigInteger num1=new BigInteger("12345");
BigInteger num2=new BigInteger("45");
//加法
System.out.println(num1.add(num2));
//减法
System.out.println(num1.subtract(num2));
//乘法
System.out.println(num1.multiply(num2));
//除法(相除取整)
System.out.println(num1.divide(num2));
//取余
System.out.println(num1.mod(num2));
//最大公约数GCD
System.out.println(num1.gcd(num2));
//取绝对值
System.out.println(num1.abs());
//取反
System.out.println(num1.negate());
//取最大值
System.out.println(num1.max(num2));
//取最小值
System.out.println(num1.min(num2));
//是否相等
System.out.println(num1.equals(num2));
}
}
import java.math.BigDecimal;

public class Main {
public static void main(String[] args) {
BigDecimal num1=new BigDecimal("123.45");
BigDecimal num2=new BigDecimal("4.5");
//加法
System.out.println(num1.add(num2));
//减法
System.out.println(num1.subtract(num2));
//乘法
System.out.println(num1.multiply(num2));
//除法(在divide的时候就设置好要精确的小数位数和舍入模式)
System.out.println(num1.divide(num2,10,BigDecimal.ROUND_HALF_DOWN));
//取绝对值
System.out.println(num1.abs());
//取反
System.out.println(num1.negate());
//取最大值
System.out.println(num1.max(num2));
//取最小值
System.out.println(num1.min(num2));
//是否相等
System.out.println(num1.equals(num2));
//判断大小( > 返回1, < 返回-1)
System.out.println(num2.compareTo(num1));
}
}
while(cin.hasNext())//相当于EOF
二分求开根号
static BigInteger cal(BigInteger x){
BigInteger l = BigInteger.ONE ;
BigInteger r = x ;
BigInteger temp = BigInteger.ZERO ;
while(!l.equals(r)){
BigInteger mid = (l.add(r)).divide(BigInteger.valueOf(2)) ;
if(temp.compareTo(BigInteger.ZERO)!=0&&temp.compareTo(mid)==0){
break ;
}else{
temp = mid ;
}
if(temp.compareTo(BigInteger.ZERO)==0){
temp = mid ;
}
if(mid.multiply(mid).compareTo(x)==1){
r=mid ;
}else{
l=mid ;
}
}
if(l.multiply(l).compareTo(x)==1){
l=l.subtract(BigInteger.ONE) ;
}
return l;

}
牛顿迭代求平方根
static BigInteger cal(BigInteger x) {
BigInteger ans=BigInteger.valueOf(0);
if(x.equals(BigInteger.valueOf(0))==true)return ans;
BigInteger r=BigInteger.valueOf(1);
BigInteger l=BigInteger.valueOf(-1);
while(true) {
BigInteger nxt =r.add(x.divide(r)).shiftRight(1);
if(nxt.equals(l)==true) {
if(l.compareTo(r)==-1)return r;
else return l;
}
l=r;r=nxt;
}

}

文件读入

1
2
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);

扩栈

1
2
// 解决爆栈问题
#pragma comment(linker, "/STACK:1024000000,1024000000")

快速读入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 适用于正负整数
template <class T>
inline bool scan_d(T &ret)
{
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; //EOF
while (c != '-' && (c < '0' || c > '9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
inline void out(int x)
{
if (x > 9) out(x / 10);
putchar(x % 10 + '0');
}

莫队算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ---
// 莫队算法,可以解决一类静态,离线区间查询问题。分成 $\sqrt{x}$ 块,分块排序。
// ---
struct query { int L, R, id; };
void solve(query node[], int m)
{
memset(ans, 0, sizeof(ans));
sort(node, node + m, [](query a, query b) {
return a.l / unit < b.l / unit
|| a.l / unit == b.l / unit && a.r < b.r;
});
int L = 1, R = 0;
for (int i = 0; i < m; i++)
{
while (node[i].L < L) add(a[--L]);
while (node[i].L > L) del(a[L++]);
while (node[i].R < R) del(a[R--]);
while (node[i].R > R) add(a[++R]);
ans[node[i].id] = tmp;
}
}

高精度

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
// 加法 乘法 小于号 输出
struct bint
{
int l;
short int w[100000];
bint(ll x = 0)
{
l = x == 0, memset(w, 0,sizeof(w));
while (x) w[l++] = x % 10, x /= 10;
}
bool operator<(const bint& x) const
{
if (l != x.l) return l < x.l;
int i = l - 1;
while (i >= 0 && w[i] == x.w[i]) i--;
return (i >= 0 && w[i] < x.w[i]);
}
bint operator+(const bint& x) const
{
bint ans;
ans.l = l > x.l ? l : x.l;
for (int i = 0; i < ans.l; i++)
{
ans.w[i] += w[i] + x.w[i];
ans.w[i + 1] += ans.w[i] / 10;
ans.w[i] = ans.w[i] % 10;
}
if (ans.w[ans.l] != 0) ans.l++;
return ans;
}
bint operator*(const bint& x) const
{
bint res;
int up, tmp;
for (int i = 0; i < l; i++)
{
up = 0;
for (int j = 0; j < x.l; j++)
{
tmp = w[i] * x.w[j] + res.w[i + j] + up;
res.w[i + j] = tmp % 10;
up = tmp / 10;
}
if (up != 0) res.w[i + x.l] = up;
}
res.l = l + x.l;
while (res.w[res.l - 1] == 0 && res.l > 1) res.l--;
return res;
}
void print()
{
for (int i = l - 1; ~i; i--) printf("%d", w[i]);
puts("");
}
};

矩阵

矩阵快速幂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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++)
if(A[i][k])
for(int j=0;j<B[0].size();j++)
C[i][j]=(C[i][j]+A[i][k]*B[k][j]%mod+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;
for(;n;n>>=1,A=mul(A,A))
if(n&1)B=mul(B,A);
return B;
}

高斯消元

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
void gauss()
{
int now = 1, to;
double t;
for (int i = 1; i <= n; i++, now++)
{
/*for (to = now; !a[to][i] && to <= n; to++);
//做除法时减小误差,可不写
if (to != now)
for (int j = 1; j <= n + 1; j++)
swap(a[to][j], a[now][j]);*/
t = a[now][i];
for (int j = 1; j <= n + 1; j++) a[now][j] /= t;
for (int j = 1; j <= n; j++)
if (j != now)
{
t = a[j][i];
for (int k = 1; k <= n + 1; k++) a[j][k] -= t * a[now][k];
}
}
}
int gauss(int n,int m){
int col, i, mxr, j, row;
for (row = col = 1; row <= n && col <= m;row++,col++){
mxr = row;
for (i = row + 1; i <= n;i++)
if(fabs(a[i][col])>fabs(a[mxr][col]))
mxr = i;
if(mxr!=row)
swap(a[row], a[mxr]);
if(fabs(a[row][col])<eps){
row--;
continue;
}
for (i = 1; i <= n;i++) /////消成上三角矩阵
if(i!=row&&fabs(a[i][col])>eps)
for (j = m; j >= col;j--)
a[i][j] -= a[row][j] / a[row][col] * a[i][col];

}
row--;
for (i = row; i >= 1;i--){//////回代成对角矩阵
for (j = i + 1; j <= row;j++){
a[i][m] -= a[j][m] * a[i][j];
}
a[i][m] /= a[i][i];
}
return row;
}

数位DP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int num[20];
int dp[20][2];
int dfs(int pos,int pre,int sta,bool first){
if(pos==-1)return 1;
if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
int up=first?num[pos]:9;
int ans=0;
for(int i=0;i<=up;i++){
if(pre==6&&i==2)continue;
if(i==4)continue;
ans+=dfs(pos-1,i,i==6,first&&i==num[pos]);
}
if(!first)dp[pos][sta]=ans;
return ans;
}
int ac(int x){
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}

Codeforces Round 548 (Div. 2)

Diposting di 2019-04-24 | Edited on 2019-03-25

https://codeforces.com/contest/1139/my

D - Steps to One

思路

1…8910…13

luowentaoaa

嘤嘤嘤

125 posting
53 tags
© 2019 luowentaoaa
Powered by Hexo v3.7.1
|
Tema – NexT.Mist v6.3.0