标签: acm - CheaSim Blog

[acm]hash学习笔记

hash学习

hash是一种比较常见的处理字符串的手法。在acm题目中,经常使用hash来处理字符串。比如判断一个子串在一个字符串中出现过几次。就可以使用hash来处理。

hash主要的方法就是把不同的字符串对应到不同的数字,之后通过数组来确定字符串是否出现过,从而减少检索字符串的时间。

常见的hash方法

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef unsigned long long ull;
ull base[maxn],has[maxn];
void init(){
base[0] = 1;
rep(i,1,maxn) base[i] = base[i-1] * 131;
scanf("%s",s);
int len = strlen(s);
has[len] = 0;
per(i,0,len) has[i] = has[i+1]*131 + (s[i]-'a'+1);
}
ull get_hash(int i,int L){
return has[i] - has[i+L]*base[L];
}

练习

hdoj4821 String

题目大意就是求$S$子串中,

  • 长为$M*L$
  • 其中每一段连续的$L$长度的子串都各不相同。

就是使用hash来做。但是我TLE了。因为暴力了每一段的hash值。

其实由于如果$S$的长度很长的话。其中很多段子段的hash值是被反复求的。所以我们在求完一段$M$个的字符串之后,就可以按照这个把第一段去除。之后选择后一段。这样重复下去。

Wa1:等于号想清楚了在判定。

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;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
typedef unsigned long long ull;
const int maxn = 1e5+10;
ull xp[maxn],Hash[maxn];
void init(){
xp[0] = 1;
rep(i,1,maxn) xp[i] = xp[i-1] * 175;
}
ull get_Hash(int i,int L){
return Hash[i] - Hash[i+L] * xp[L];
}
int n,m;
char s[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
init();
// m = length of L
while(~scanf("%d%d",&n,&m)){
scanf("%s",s);
int len = strlen(s);
Hash[len] = 0;
per(i,0,len){
Hash[i] = Hash[i+1]*175+(s[i]-'a'+1);
}
int ans = 0;
for(int i=0;i<m && i<=len-m*n;i++){
map<ull,int> mx;
for(int j=i;j<i+m*n;j+=m){
ull temp = get_Hash(j,m);
mx[temp] ++;
}
if(mx.size() == n) ans++;
for(int j = i+n*m;j+m<=len;j+=m){
ull temp = get_Hash(j-n*m,m);
mx[temp]--;
if(mx[temp] == 0) mx.erase(temp);
temp = get_Hash(j,m);
mx[temp]++;
if(mx.size() == n) ans++;
}
}
printf("%d\n",ans);
}
return 0;
}

hdoj4080 Stammering Aliens

题目大意是找到字符串$S$中,出现次数大于$m$次并且最长的字符串。

我是用hash做的,不过时间是4800ms,差点超时。用别人后缀数组+二分,200ms可以过。。

就酱吧。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
//head

const int maxn = 4e4+10;
ull base[maxn],has[maxn];
void init(){
base[0] = 1;
rep(i,1,maxn) base[i] = base[i-1] * 131;
}
ull get_hash(int i,int L){
return has[i] - has[i+L] * base[L];
}

int m,n;
char s[maxn];
int check(int len){
map<ull,int> mx;
int ans = -1;
rep(i,0,n-len+1) {
ull temp = get_hash(i,len);
mx[temp] ++;
if(mx[temp] >= m){
ans = i;
}
}
return ans;
}
int right_most = 0;
int res = 0;
void solve(int l,int r){
int mid;
while(l<=r){
mid = l+r>>1;
int temp = check(mid);
if(temp != -1){
l = mid + 1;
res = mid;
right_most = temp;
}else{
r = mid - 1;
}
}
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
init();
while(scanf("%d",&m) && m){
scanf("%s",s);
n = strlen(s);
has[n] = 0;
per(i,0,n) has[i] = has[i+1] * 131 + s[i] - 'a' + 1;
right_most = 0; res = 0;
solve(1,n+1);
if(res == 0){
puts("none");
}else{
printf("%d %d\n",res,right_most);
}
}

return 0;
}

reference:

https://blog.csdn.net/u012965373/article/details/38929637

https://blog.csdn.net/ck_boss/article/details/47066727

树链剖分学习笔记

树链剖分学习笔记

题型

当一道题在询问两点之间修改后的权值或者是两点之间边修改后的权值。这类题目往往我们一开始就会思考用线段树来维护,但是线段树无法维护一颗树上的链,所以我们需要来将树上的链剖分下来。

轻重边剖分

将树中的边分为两部分,轻边和重边,用$size[u]$来记录以$u$为根的子树的节点个数,之后令$v$为$u$的儿子中$size$最大的一个,那么我们就可以将$u$的边分为重边和轻边了(重边就是$(u.v)$)。

之后我们就可以得到如下性质

  • 如果$(u,v)$为轻边,那么$size[v] \leq size[u]/2$。因为如果大于的话,他就成为重边了。
  • 从根到某一点的路径上轻边的个数不会超过$logN$。假设从$root->v$,那么$size[v]>=1$,可以递推出$size[root]>=2^k$,其中$k$为根到点进过的点数。

实现

初始化操作

首先我们定义以下几个数组

  • $size[v]$表示以$v$为根的子树的节点数
  • $dep[v]$表示$v$的深度(根的深度为1)
  • $top[v]$表示所在重链的顶端节点
  • $fa[v]$表示父亲节点,$son[v]$表示重儿子节点
  • $w[v]$表示与父亲的连边

然后我们使用两个dfs来把数组确定下来。

第一个dfs求出fa,dep,size,top,w。

第二个dfs

  1. 对于循环到的$v$,当$son[v]$存在的时候,也就是非叶子节点,那么$top[son[v]] = top[v]$是显而易见的。在线段树中我们规定孩子节点的重边必须在父亲节点的后面,那么$w[son[v]]=totw+1$,其中$totw$表示线段树中加入的最后一条边的位置。之后进行递归dfs(son[v]);
  2. 对于$v$的轻孩子,那么他们没有身处重链所以$top[x]=x$。那么我们为了线段树好看一点也将他加入线段树。$w[x]=totw+1$。

经过两个dfs我们把所有的数组都给他确认了。

这代码量该有多大啊,对于常年只写70行就要debug很久的我来说。

更新操作

因为我们其实也是慢慢修改路径上的边的,所以我们先求个LCA。之后更新$u,v$到祖先节点。方法意会吧,或者看代码。

查询操作

跟更新操作很像,就是在过程中不再更新,而是求值。

例题

Aragorn’s Story

+1 更新节点的时候必须在id[x]更新

+1 大于小于没弄明白,在change函数中。

+1 update的时候以为是线段树直接从左边更新到右边,其实是在那个重边更新,所以是update(val,id[x],id[t1],1,totw,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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 5e4+10;
int size[maxn],dep[maxn],top[maxn],fa[maxn],id[maxn],son[maxn];
int head[maxn],w[maxn];
int n,cnt,totw,m;
struct edge{
int next,to;
}G[maxn<<1];
int find(int x){ return x==fa[x]?x:find(fa[x]);}
void addedge(int u,int v){
G[cnt].next = head[u];
G[cnt].to = v;
head[u] = cnt++;
}
void dfs1(int u,int f){
size[u] = 1;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==f) continue;
dep[v] = dep[u]+1;
fa[v] = u;
dfs1(v,u);
if(size[v] > size[son[u]]) son[u] = v;
size[u]+=size[v];
}
}
void dfs2(int u,int topu){
top[u] = topu;
id[u] = ++totw;
if(son[u]) dfs2(son[u],top[u]);
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==fa[u] || v==son[u]) continue;
dfs2(v,v);
}
}
int data[maxn<<2],lazy[maxn<<2];
void pushdown(int rt){
if(lazy[rt]!=0){
data[rt<<1] += lazy[rt];
data[rt<<1|1] += lazy[rt];
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
lazy[rt] = 0;
}
}
void build(int l,int r,int rt){
lazy[rt] = 0;
if(l==r){
data[rt] = w[l];
return;
}
int mid = l+r>>1;
build(lson); build(rson);
}
int query(int pos,int l,int r,int rt){
if(l==r && l==pos) return data[rt];
int mid = l+r>>1;
pushdown(rt);
if(pos<=mid) return query(pos,lson);
else return query(pos,rson);
}
void update(int val,int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
lazy[rt] += val;
data[rt] += val;
return;
}
pushdown(rt);
int mid = l+r>>1;
if(L<=mid) update(val,L,R,lson);
if(mid<R) update(val,L,R,rson);
}
void change(int u,int v,int val){
int t1=top[u],t2=top[v];
while(t1!=t2){
if(dep[t1]<dep[t2]){
swap(t1,t2);
swap(u,v);
}
update(val,id[t1],id[u],1,totw,1);
u = fa[t1];
t1 = top[u];
}
if(dep[u]>dep[v]) swap(u,v);
update(val,id[u],id[v],1,totw,1);
}
int val[maxn];
int p;
char op[20];
void init(){
cnt = totw = 0;
memset(head,-1,sizeof(head));
dep[1] = fa[1] = size[0] = 0;
memset(son,0,sizeof(son));
}
int main(){
#ifdef LOCAL
freopen("4.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&m,&p)){
init();
rep(i,1,n+1) scanf("%d",val+i);
rep(i,0,m){
int u,v;scanf("%d%d",&u,&v);
addedge(u,v); addedge(v,u);
}
dfs1(1,0);
dfs2(1,1);
rep(i,1,n+1) w[id[i]] = val[i];
build(1,totw,1);
while(p--){
scanf("%s",op);
if(op[0]=='Q'){
int x;scanf("%d",&x);
printf("%d\n",query(id[x],1,totw,1));
}else{
int u,v,val;scanf("%d%d%d",&u,&v,&val);
if(op[0]=='D') val = -val;
change(u,v,val);
}
}
}
return 0;
}

个人鄙见:

树链剖分就是将树一部分分成区间修改,一部分分成单点修改,但是单点修改的复杂度控制在了$O(logN)$。

Reference:

https://blog.csdn.net/dyx404514/article/details/8718249

LCA专项练习

LCA专项练习

前提提要

由于A,B太水了,就不放了。

前几题先试试用trajan能不能全杀,之后再看在线算法。


易错点统计

  1. 如果两个点相同,那么他们的祖先节点居然会变成0。。可能是我的模板写法有点问题。可以在判断vis[u]=2的时候加上如果u=now也是找到了公共祖先。
  2. i=fa[j]; 应该是fa[j] = i;
  3. 如果要加入假设边的话,G[maxn<<2]得开4倍大小,不然会已知TLE。。
  4. 有时候两种不同的写法不能混淆。
  5. 两个点相同又错了。ST表写法的时候也得特判的时候还是要考虑一下,因为有的题目不是单纯的距离之差。

C - Distance Queries

题意

一棵树,求两点之间最短距离。

题解

裸LCA+dfs求距离

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
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 4e4+100;
struct Query{
int to,id;
Query(){}
Query(int a,int b){
to = a;id = b;
}
};
vector<Query> query[maxn];
struct node{
int to,next,val;
}G[maxn<<2];
int n,m,cnt;
int head[maxn],vis[maxn],fa[maxn],ans[maxn],dis[maxn];
void add(int u,int v,int val){
G[cnt].to = v;
G[cnt].next = head[u];
G[cnt].val = val;
head[u] = cnt++;
}
void init(){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(ans,0,sizeof(ans));
memset(dis,0,sizeof(dis));
rep(i,1,n+1) query[i].clear();
rep(i,1,n+1) fa[i] = i;
cnt = 0;
}
void dfs1(int u,int f){
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==f) continue;
dis[v] = dis[u] + G[i].val;
dfs1(v,u);
}
}
int find(int x){
if(x!=fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void dfs(int now){
vis[now] = 1;
for(int i=head[now];~i;i=G[i].next){
int v = G[i].to;
if(vis[v]) continue;
dfs(v);
fa[v] = now;
}
for(int i = 0;i<query[now].size();i++){
Query node = query[now][i];
int u = node.to; int id = node.id;
if(vis[u]==2){
ans[id] = find(u);
}
}
vis[now] = 2;
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
while(~scanf("%d%d",&n,&m)){
init();
vector<pair<int,int> > ask;
rep(i,0,m){
int u,v,val;scanf("%d%d%d",&u,&v,&val);
char s[10]; scanf("%s",s);
add(u,v,val); add(v,u,val);
}
dfs1(1,-1);
int t;scanf("%d",&t);
rep(i,0,t){
int u,v;
scanf("%d%d",&u,&v);
query[u].push_back(Query(v,i));
query[v].push_back(Query(u,i));
ask.push_back(make_pair(u,v));
}
dfs(1);
rep(i,0,t){
int x = ask[i].fi,y = ask[i].se;
if(x==y){
puts("0");
continue;
}
printf("%d\n",dis[x]-dis[ans[i]]+dis[y]-dis[ans[i]]);
}
}
return 0;
}

修订版。在LCA里面特判。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 4e4+100;
struct Query{
int to,id;
Query(){}
Query(int a,int b){
to = a;id = b;
}
};
vector<Query> query[maxn];
struct node{
int to,next,val;
}G[maxn<<2];
int n,m,cnt;
int head[maxn],vis[maxn],fa[maxn],ans[maxn],dis[maxn];
void add(int u,int v,int val){
G[cnt].to = v;
G[cnt].next = head[u];
G[cnt].val = val;
head[u] = cnt++;
}
void init(){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(ans,0,sizeof(ans));
memset(dis,0,sizeof(dis));
rep(i,1,n+1) query[i].clear();
rep(i,1,n+1) fa[i] = i;
cnt = 0;
}
void dfs1(int u,int f){
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==f) continue;
dis[v] = dis[u] + G[i].val;
dfs1(v,u);
}
}
int find(int x){
if(x!=fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void dfs(int now){
vis[now] = 1;
for(int i=head[now];~i;i=G[i].next){
int v = G[i].to;
if(vis[v]) continue;
dfs(v);
fa[v] = now;
}
for(int i = 0;i<query[now].size();i++){
Query node = query[now][i];
int u = node.to; int id = node.id;
if(vis[u]==2 || u==now){
ans[id] = find(u);
}
}
vis[now] = 2;
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
while(~scanf("%d%d",&n,&m)){
init();
vector<pair<int,int> > ask;
rep(i,0,m){
int u,v,val;scanf("%d%d%d",&u,&v,&val);
char s[10]; scanf("%s",s);
add(u,v,val); add(v,u,val);
}
dfs1(1,-1);
int t;scanf("%d",&t);
rep(i,0,t){
int u,v;
scanf("%d%d",&u,&v);
query[u].push_back(Query(v,i));
query[v].push_back(Query(u,i));
ask.push_back(make_pair(u,v));
}
dfs(1);
rep(i,0,t){
int x = ask[i].fi,y = ask[i].se;
printf("%d\n",dis[x]-dis[ans[i]]+dis[y]-dis[ans[i]]);
}
}
return 0;
}

D - Connections between cities

题意

分成未知数目颗树,问其中两点是否有路径,路径的最短长度是多少。

题解

LCA+假点。

但是这道题的内存有点卡,导致不能用vector存询问,还是用两个链式前向星还行。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int n,m,c,cnt,tot;
const int maxn = 1e4+1000;
struct node{
int next,to,val;
}G[maxn<<2];
int head[maxn],fa[maxn],vis[maxn],ans[maxn*200],dis[maxn],headq[maxn];
void add(int u,int v,int val){
G[cnt].to = v;
G[cnt].next = head[u];
G[cnt].val = val;
head[u] = cnt++;
}
struct Query{
int next,to;
}query[maxn*200];
int find(int x){
if(x!=fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void addq(int u,int v){
query[tot].to = v;
query[tot].next = headq[u];
headq[u] = tot++;
}
void dfs(int now){
vis[now] = 1;
for(int i=headq[now];~i;i=query[i].next){
int v = query[i].to;
if(vis[v]){
if(v==now){
ans[i/2] = 0;
continue;
}
if(find(v)==0) ans[i/2] = -1;
else ans[i/2] = dis[now]+dis[v]-2*dis[find(v)];
}
}
for(int i=head[now];~i;i=G[i].next){
int v = G[i].to;
if(vis[v]) continue;
dis[v] = dis[now] + G[i].val;
dfs(v);
fa[v] = now;
}
}
void init(){
memset(head,-1,sizeof(int)*(n+2));
memset(headq,-1,sizeof(int)*(n+2));
memset(vis,0,sizeof(int)*(n+1));
rep(i,1,n+1) fa[i] = i;
cnt = 0;
tot = 0;
}
void Union(int a,int b){
int i = find(a), j = find(b);
if(i==j) return;
fa[i] = j;
}
template <typename T>
inline bool read (T &ret) {
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; //EOF
while (c != '-' && (c < '0' || c > '9') ) {
if((c = getchar()) == EOF) return 0;
}
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;
}
int main(){
#ifdef LOCAL
freopen("3.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&m,&c)){
init();
rep(i,0,m){
int u,v,val;read(u);read(v);read(val);
add(u,v,val); add(v,u,val);
Union(u,v);
}
rep(i,1,n+1){
if(fa[i] == i) fa[i] = 0;
add(0,i,0); add(i,0,0);
}
rep(i,0,c){
int u,v; read(u); read(v);
addq(u,v); addq(v,u);
}
rep(i,1,n+1) fa[i] = i;
dfs(0);
rep(i,0,c){
if(ans[i]==-1){
puts("Not connected");
continue;
}
printf("%d\n",ans[i]);
}
}

return 0;
}

F - Tree

题意

对于一棵树有如下两种操作

  • 将$v$,$v$两个点之间所有的点包括这两个点的权值全部加$val$
  • 将$v$,$v$两个点之间所有的边的权值全部加$val$

题解

lca+标记递推

我们定义$add[x][0]$为在$x$点上往后递推的点值,那么对于两个$u,v$来说我们要让这条链上的所有点都加$val$。

1
2
3
4
add[u][0] += val;
add[v][0] += val;
add[x][0] -= val;
des[x] -= val;

其中$x$是他们的LCA,而且为了使得到了他们的LCA不继续增加这个值,我们定义一个数组$des[]$,递推的时候就加上这个数组在递推,就可以使得在LCA的时候不递推到LCA的祖先节点了。

树链剖分题解

树链剖分可以处理点权和边权,不过这是我第一次看到一起处理的。

trick:

把点用数组存起来,到边的时候就用孩子节点来表示孩子节点到父亲节点的边。

ac代码

G - One and One Story

H - CD操作

题意

一个加一点背景的LCA,往父节点走要+1,从祖先节点到任意子节点也只需要加1。

题解

LCA,我用了ST表+RMQ,跑的飞快。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e5 + 10;
int cnt,n,m;
int head[maxn];
int fa[maxn];
struct node{
int next,to;
}G[maxn<<1];
void addedge(int u,int v){
G[cnt].to = v;
G[cnt].next = head[u];
head[u] = cnt++;
}
struct ST{
int tot;
int first[maxn<<1],R[maxn<<1],order[maxn<<1],dp[maxn<<1][20],dis[maxn];
void init(int root){
tot = 0;
dfs(root,0,-1);
ST_init(tot);
}
void ST_init(int n){
rep(i,1,n+1) dp[i][0] = i;
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
int a = dp[i][j-1],b = dp[i+(1<<(j-1))][j-1];
dp[i][j] = R[a]<R[b]?a:b;
}
}
}
int LCA(int u,int v){
int x = first[u],y = first[v];
if(x>y) swap(x,y);
int res = RMQ(x,y);
return order[res];
}
int RMQ(int l,int r){
int k = 0;
while((1<<(k+1))<=r-l+1) k++;
int a = dp[l][k],b = dp[r-(1<<k)+1][k];
return R[a]<R[b]?a:b;
}
void dfs(int u,int deep,int f){
R[++tot] = deep;
dis[u] = deep;
order[tot] = u;
first[u] = tot;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==f) continue;
dfs(v,deep+1,u);
order[++tot] = u;
R[tot] = deep;
}
}
}st;
int find(int x){
return x==fa[x]?x:fa[x] = find(fa[x]);
}
int ind[maxn];
char s[60];
void init(){
cnt = 0;
memset(head,-1,sizeof(head));
memset(ind,0,sizeof(ind));
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
map<string,int> mp;
int tot = 0;
rep(i,0,n-1){
int u,v;
scanf("%s",s);
if(mp[s]==0) mp[s] = ++tot,u = tot;
else u = mp[s];
scanf("%s",s);
if(mp[s]==0) mp[s] = ++tot,v = tot;
else v = mp[s];
addedge(v,u); ind[u]++;
}
int root = 0;
rep(i,1,n+1) if(ind[i]==0){
root = i;
break;
}
st.init(root);
rep(i,0,m){
int u,v,ans;
scanf("%s",s); u = mp[s];
scanf("%s",s); v = mp[s];
int rt = st.LCA(u,v);
int x = st.dis[u] - st.dis[rt];
int y = st.dis[v] - st.dis[rt];
if(rt==u) ans = 1;
else if(rt == v) ans = x;
else ans = x+1;
if(u==v) ans = 0;
printf("%d\n",ans);
}
}
return 0;
}

LCA学习笔记

LCA学习笔记

祖先的定义

如果学习树结构就会明白除了根节点以外每个节点都有一个父节点,所以我们定义祖先为父节点的父节点,然后我们给出最近公共祖先的定义

一棵树祖先中到两个点距离最近的节点。

能够解决LCA问题的方式有很多,所以作为一个ACM选手,肯定是我全都要

解决方案

欧拉序

树的欧拉序是对树进行dfs的一种序列,他有两条性质

  • 在每个节点进和出都加入序列
  • 只要到达每一个节点就把他加入序列

那么我们寻找他们的最近公共祖先就可以从第一次出现第一个节点和第一次出现的第二个节点之间节点中深度最大的节点。

HDOJ[2586]

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define pb push_back
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 4e4+200;
const int maxm = 2e2+10;
int dis[maxn];
int n,m;
struct node{
int next,to,val;
}G[maxn<<1];
int head[maxn];
int cnt;
vector<int> order;
void init(){
cnt = 0;
memset(head,-1,sizeof(head));
order.clear();
}
void add(int u,int v,int val){
G[cnt].next = head[u];
G[cnt].to = v;
G[cnt].val = val;
head[u] = cnt++;
}
void dfs(int u,int fa,int val){
order.pb(u);
dis[u] = val;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==fa) continue;
dfs(v,u,val+G[i].val);
order.pb(v);
}
}
int find(int p1,int p2){
int mmin = INF,res;
if(p1>p2){
int temp = p1;
p1 = p2;
p2 = temp;
}
rep(i,p1,p2+1){
if(dis[order[i]] < mmin){
mmin = dis[order[i]];
res = i;
}
}
return res+1;
}
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
rep(i,0,n-1){
int u,v,val;
scanf("%d%d%d",&u,&v,&val);
add(u,v,val); add(v,u,val);
}
dfs(1,1,0);
int len = order.size();
rep(i,0,m){
int x,y; int p1=-1,p2=-1;
scanf("%d%d",&x,&y);
rep(j,0,len){
if(p1==-1&&x==order[j]) p1 = j;
if(p2==-1&&y==order[j]) p2 = j;
if(p1!=-1 && p2!=-1) break;
}
int lca = find(p1,p2);
//cout<<dis[lca]<<' '<<dis[x]<<' '<<dis[y]<<endl;
int ans = dis[x]-dis[lca]+dis[y]-dis[lca];
printf("%d\n",ans);
}

}

return 0;
}

Trajan算法

首先Trajan是一个离线算法,在一次遍历中把所有询问一次解决,时间复杂度$O(n+q)$。

大佬题解

我也想自己写啊,但是就是不会啊,自己想是不可能自己想的,只要抄抄别人的博客才能维持生活。

  1. 任选择一个点作为根节点,从根节点开始
  2. 遍历该节点的所有子节点,并标记这些子节点$v$已经被访问过
  3. 如果$v$还有子节点,返回2,否则就进入下一步
  4. 合并$v$到$u$。
  5. 寻找与当前点$u$有询问关系的点$v$
  6. 若是$v$已经被访问过了,则可以确定$u$和$v$的最近公共祖先为$v$被合并的父亲节点$fa[u]$。
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<cstdio>
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int T,n,cnt;
const int maxn = 1e4+10;
struct node{
int next,to;
}G[maxn<<1];
int head[maxn],fa[maxn],vis[maxn],ans[maxn];
void init(){
cnt = 0;
memset(head,-1,sizeof(head));
memset(fa,0,sizeof(fa));
memset(vis,0,sizeof(vis));
rep(i,1,n+1) fa[i] = i;
}
void add(int u,int v){
G[cnt].to = v;
G[cnt].next = head[u];
head[u] = cnt++;
}
int find(int x){
if(fa[x]!=x) fa[x] = find(fa[x]);
return fa[x];
}
struct Query{
int to,id;
Query(){}
Query(int _to,int _id){
to = _to;
id = _id;
}
};
vector<Query> query[maxn];
void dfs(int now){
int u,v;
vis[now] = 1;
for(int i=head[now];~i;i=G[i].next){
v = G[i].to;
if(vis[v]) continue;
dfs(v);
fa[v] = now;
}
for(int i = 0;i<query[now].size();i++){
Query node = query[now][i];
u = node.to; int id = node.id;
if(vis[u] == 2){
ans[find(u)] ++;
}
}
vis[now] = 2;
}
int vvis[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
while(~scanf("%d",&n)){
memset(head,-1,sizeof(head));
cnt = 0;
memset(vis,0,sizeof(vis));
memset(vvis,0,sizeof(vvis));
memset(ans,0,sizeof(ans));
rep(i,1,n+1) query[i].clear();
rep(i,1,n+1) fa[i] = i;
int root = 0;
rep(i,0,n){
int u,x,v;scanf("%d",&u);
scanf("%*c%*c%d%*c",&x);
rep(i,0,x){
scanf("%d",&v);
add(u,v);
vvis[v] = 1;
}
}
rep(i,1,n+1){
if(vvis[i]==0){
root = i;
break;
}
}
int m;scanf("%d",&m);
char ch;
rep(i,0,m){
ch = getchar();
while(ch!='(') ch = getchar();
int u,v;scanf("%d%d",&u,&v);
query[u].push_back(Query(v,i));
query[v].push_back(Query(u,i));
while(ch!=')') ch = getchar();
}
dfs(root);
rep(i,1,n+1){
if(ans[i]){
printf("%d:%d\n",i,ans[i]);
}
}
}
return 0;
}

RMQ算法(ST表)

其实这个方法也就是欧拉序加上RMQ,在第一个解决方案中我们使用枚举来寻找他们的LCA,这也导致时间复杂度变成了$O(n^2)$。而寻找固定区间内的最小值是一个很普遍的问题,也就是RMQ。而解决RMQ有很多种$O(logn)$的方法,

  • 线段树、树状数组
  • ST表

他们均可以有效地解决区间最值问题。

例题[hdoj5044]

使用方法为 RMQ+正负tag

详情见我刷的LCA专题训练。

Reference:https://blog.csdn.net/czl_233/article/details/78368927

http://www.cppblog.com/menjitianya/archive/2015/12/10/212447.html

http://www.cnblogs.com/JVxie/p/4854719.html

Codeforces 833B - The Bakery

Codeforces 833B - The Bakery

题意

将一段数字分成最多50个区间,每个区间的价值是区间内不同数字的个数,问怎么样分区间使得价值总和最大。

题解

$dp$加线段树。

$dp[i][j]$表示第$j$个坐标分成$i$块最大的价值。

转移方程

  • $dp[i][j] = max(dp[i][j],dp[i-1][x] + restofx)$,其中$x$是从1到$j$。

因为每次要获得剩下的x中数字不同的个数,暴力的做法是$O(n^2*k)$,

ac代码

The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online

The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online(青岛网络赛)

B.Red Black Tree

题意

题解

ac代码

J.Press the Button

题意

题解

ac代码

H Traveling on the Axis

题意

BOB走在$[1,n]$的路上,每两个点中间都有一个红绿灯,每一秒钟,

  • BOB先观察红绿灯是否绿,绿就走,红就停。
  • 红灯变绿,绿灯变红

$t(p,q)$就是$p$走到$q所需要的时间。

问: BOB从 $\sum^{n-1}{p=0} \sum{q=p+1}^nt(p,q)$的时间总和。

题解

对于第$i$个单独的红绿灯我们可以证明他对答案的贡献是

​ $(i)(len-i+1)(t(i))$

我们对于两个灯进行分析。

01 第一个零要2s,第二个一要1s

10 第一个一要1s,第二个零要1s

00 第一个零要2s,第二个一要2s

11 第一个一要1s,第二个一要2s

综上可以发现,

  • 该灯初始为0,那么他当头的时候贡献为2,如果不当头那么他的贡献就为1,除非跟前者相同。
  • 该灯初始为1,那么他和前一个灯相同时贡献为2,就算变成0了,不在头上的话贡献也为1.

我他妈sb了,不应该看着一段一段的区间,应该把红绿灯作为每一次状态的扩展,从红绿灯这$n$个出发开始计算,不应该考虑一个一个的单位为1的区间。

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
// CSL 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 17;
char s[N];
typedef long long ll;
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%s", s);
ll ans = 0;
int n = strlen(s);
for (int i = 0; i < n; i++) ans += 1LL * (i + 1) * (n - i);
for (int i = 0; s[i]; i++)
{
if (s[i] == '0') ans += n - i;
if (i && (s[i] == s[i - 1])) ans += 1LL * i * (n - i);
//要跟包含前者的可能,要两个相同才能这么加。
}
printf("%lld\n", ans);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head

const int maxn = 1e5 + 10;
char s[maxn];
int main(){
#ifdef LOCAL
freopen("h.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%s",s);
int len = strlen(s);
ll ans = 0; ll w = 0;
if(s[0]=='1') ans = w = 1;
else ans = w = 2;
rep(i,1,len){
if(s[i]==s[i-1]) w += i*2;
else w += i*1;
if(s[i]=='1') w += 1;
else w += 2;
ans += w;
}
printf("%lld\n",ans);
}

return 0;
}

reference:

https://blog.csdn.net/tianyizhicheng/article/details/82728350

概率dp入门

概率dp

概率dp有两种题型,一种是求概率一种是求期望。

来结合一下题目

Aeroplane chess

题意

一个人在一条线上掷骰子,在线上有类似飞行棋的可以直接到达某个点的特殊点,问从$0$到$n$掷骰子次数的数学期望是多少?

题解

期望dp,dp最重要的就是从一个已知的最优状态转化到另一个最优状态。

为什么期望要反着求呢,因为你并不知道$dp[0]$这个点的值,因为我们定义$dp[x]$为$x$点到$n$点的掷骰子数的数学期望。而$dp[n]$的数学期望我们是知道的,是0,因为已经在$n$点了。之后我们倒推前面点的数学期望,因为你骰子是6面,从一个点可以到后面的六个点并且是等概率的,所以

$dp[i] = \sum_{j=i+1}^{i+6}dp[j]/6+1$

而且如果是特殊的飞行点,那么他的数学期望直接就是他的到的点的数学期望。

+2:没有从n-1开始,居然sb到从n开始计算。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define pb push_back
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int n,m;
const int maxn = 1e5 + 10;
double dp[maxn];
int nxt[maxn];
int main(){
#ifdef LOCAL
freopen("11.in","r",stdin);
#endif
while(scanf("%d%d",&n,&m) && (m||n)){
rep(i,0,n+1) dp[i] = 0.0,nxt[i] = 0;
rep(i,0,m){
int x,y;scanf("%d%d",&x,&y);
nxt[x] = y;
}
per(i,0,n){
if(nxt[i]){
dp[i] = dp[nxt[i]];
}else{
double sum = 0.0;
rep(j,1,6+1){
if(i+j<=n){
sum+=dp[i+j];
}
}
dp[i] = sum/6.0+1.0;
}
}
printf("%0.4f\n",dp[0]);
}
return 0;
}

B - Discovering Gold

题意

跟上面的差不多,就是求期望。

题解

跟上面基本相似。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 110;
int T,n;
double dp[maxn];
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
scanf("%d",&T);
rep(test_case,1,T+1){
scanf("%d",&n);
rep(i,1,n+1) cin>>dp[i];
per(i,1,n){
double sum = 0.0;
double cnt = 0;
rep(j,i+1,i+7){
if(j<=n) sum+=dp[j],cnt++;
}
dp[i] += sum/cnt;
}
printf("Case %d: %0.9f\n",test_case,dp[1]);
}

return 0;
}

ACM-ICPC 2018 徐州赛区网络预赛

ACM-ICPC 2018 徐州赛区网络预赛

A. Hard to prepare

题意

$n$个人围成环,每个人可以选择$[0,2^k-1]$中的一个数字,要求相邻两人不能同或为0。

题解

递归。

可以YY出,第一个人有$2^k$种选择,之后第2到第$n-1$个人有$2^k-1$种选择,最后一个人可能可以选$2^k-2$,也可能可以选$2^k-1$。这取决于倒数第二个人是否跟第一个人选一样的。这时候我们就可以加上如果第一个人和倒数第二个人选择相同,并且,最后一个人多选了那$2^k-1-(2^k-2)$种,那么他们三个点变成一个点来选择了。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int n,k;
const ll mod = 1e9+7;
ll pow3(ll a,ll b){
ll ans = 1;
while(b){
if(b&1) ans = ans*a%mod;
a = a*a%mod;
b>>=1;
}
return ans;
}
ll k2,kk2,kk1;
ll solve(int n,int k){
if(k==1) return 2;
if(n==1) return k2;
if(n==2) return k2*kk1%mod;
ll res = (k2*kk2%mod)*pow3(kk1,n-2)%mod;
res = (res+solve(n-2,k))%mod;
return res;
}
int main(){
#ifdef LOCAL
freopen("4.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
k2 = pow3(2,k);
kk1 = (k2-1+mod)%mod;
kk2 = (k2-2+mod)%mod;
printf("%lld\n",solve(n,k));
}

return 0;
}

B.BE,GE or NE

题意

两个人玩游戏,给出$[0,3]$个选项和一个$m$初始值,两个人依次选择,有以下三种选择

  • 选择将值$+a$
  • 选择将值$-b$
  • 选择将值倒过来 $m=-m$

两个人一个想将值变大,一个想让值变小,两个人都知晓所有情况和选项,在最优情况下,问谁赢。

题解

博弈论dp+记忆化搜索

因为数据量很小,范围也在200之间,所以记忆化状态回复的很多。

$dp[pos][val]$表示在pos位置数值是val,之后dp代表的是最优可以赢还是输还是平,分别用2,1,0代表。

之后对于边界值要处理一下。数组存不了负值,我们统一加100处理。


妈的。我看到这个以为是纯博弈论直接人傻了。之后想着能不能记忆化搜索,但是看到$n=1000$还以为他喵的会爆,但是这道题的情况限制在范围$-100到100$,所以情况很少。所以可以直接记忆化,不过对于答案的选择和状态转移我可能还是不会太轻松地做出来,带int的dfs我还是不怎么熟悉,这按道理应该是算是博弈论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
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
// head
int n,m,k,l;
const int maxn = 1e3 + 10;
int a[maxn],b[maxn],c[maxn];
int dp[maxn][220];
// op
int dfs(int pos,int val){
if(pos == n){
if(val >= k){
return 2;
}else if(val > l){
return 1;
}else{
return 0;
}
}
if(dp[pos][val] != -1) return dp[pos][val];
if(pos%2==0){
int res = 0;
int mmin = min(200,val+a[pos]);
int mmax = max(0,val-b[pos]);
if(a[pos]) res = max(res,dfs(pos+1,mmin));
if(b[pos]) res = max(res,dfs(pos+1,mmax));
if(c[pos]) res = max(res,dfs(pos+1,200-val));
return dp[pos][val] = res;
}else{
int res = 2;
int mmin = min(200,val+a[pos]);
int mmax = max(0,val-b[pos]);
if(a[pos]) res = min(res,dfs(pos+1,mmin));
if(b[pos]) res = min(res,dfs(pos+1,mmax));
if(c[pos]) res = min(res,dfs(pos+1,200-val));
return dp[pos][val] = res;
}
}
int main(){
#ifdef LOCAL
freopen("b.in","r",stdin);
#endif // LOCAL
rep(i,0,maxn) rep(j,0,220) dp[i][j] = -1;
scanf("%d%d%d%d",&n,&m,&k,&l);
l += 100; k += 100; m += 100;
rep(i,0,n){
scanf("%d%d%d",a+i,b+i,c+i);
}
int op = dfs(0,m);
if(op==2) puts("Good Ending");
else if(op==1) puts("Normal Ending");
else puts("Bad Ending");
return 0;
}

单调队列学习笔记

单调队列

定义

单调队列,就是指队列中的元素是单调的。

$a_1,a_2,a_3,…,a_n$满足$a1\leq a_2\leq a_3…\leq a_n$的序列便是单调序列。

运用单调队列可以简化问题,由于队列是单调的,那么我们存取最大最小值的复杂度均是$O(1)$。而每一个元素入队一次,出队一次的复杂度也是$O(1)$。

而单调队列和数据结构deque结合较好,如果题目的数据量不是很大,没有卡std,就可以使用deque实现。

维护

以单调增序列为例子:

  1. 如果队列长度一定,先判断队首元素是否在规定范围内,如果超范围就弹出队首。
  2. 每次加入元素和队尾比较,如果当前元素小于队尾元素并且队列非空,就弹出队尾指针,直到满足单调性为止。

例题1 合并果子

(题目太老,只能换个改版的。代码有一定问题,细心的人能看下出来。)

题解

建两个单调队列,寻找两个最小值加入其中一个堆中,有点像haffman编码的方式。

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;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define pb push_back
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e4 + 10;
int n,ans;
int a[maxn];
int T;
int main(){
#ifdef LOCAL
freopen("111.in","r",stdin);
#endif
scanf("%d",&n);
rep(i,0,n){
scanf("%d",&a[i]);
}
deque<int> dq1;
deque<int> dq2;
sort(a,a+n);
rep(i,0,n) dq1.pb(a[i]);
ans = 0;
int x,y;
rep(i,0,n-1){
if(dq2.empty()){
x = dq1.front();
dq1.pop_front();
}else if(dq1.empty()){
x = dq2.front();
dq2.pop_front();
}else{
if(dq1.front() < dq2.front()){
x = dq1.front(),dq1.pop_front();
}else{
x = dq2.front(),dq2.pop_front();
}
}
if(dq2.empty()){
y = dq1.front();
dq1.pop_front();
}else if(dq1.empty()){
y = dq2.front();
dq2.pop_front();
}else{
if(dq1.front() < dq2.front()){
y = dq1.front(),dq1.pop_front();
}else{
y = dq2.front(),dq2.pop_front();
}
}
ans += (x+y);
dq2.pb(x+y);
}
printf("%d\n",ans);
return 0;
}

例题2 Sliding Window

题解

单调队列+维护一下队头指针不能超过$k$的范围。

由于有最大值和最小值,维护两个单调队列。

需要注意一下的就是如果是手写双向队列的话,我一般是定义

1
2
3
4
5
int st,ed;
st = ed = 0;
mmax[ed++] = a[0];
//判定的时候需要判定ed-1才是指向队尾的值
while(st<ed && mmax[ed-1]<=a[i]) ed--;

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e6 + 10;
int n,k;
int a[maxn];
int mmax[maxn];
int mmin[maxn];
int main(){
#ifdef LOCAL
freopen("12.in","r",stdin);
#endif
scanf("%d%d",&n,&k);
rep(i,0,n) scanf("%d",&a[i]);
int st1,st2,ed1,ed2;
st1 = st2 = ed2 = ed1 = 0;
mmax[ed1++] = 0; mmin[ed2++] = 0;
rep(i,0,k-1){
while(st1<ed1 && a[mmax[ed1-1]] <= a[i]) ed1--;
mmax[ed1++] = i;
while(st2<ed2 && a[mmin[ed2-1]] >= a[i]) ed2--;
mmin[ed2++] = i;
}
rep(i,k-1,n){
while(st2<ed2 && i-mmin[st2]+1 > k) st2++;
while(st2<ed2 && a[mmin[ed2-1]] >= a[i]) ed2--;
mmin[ed2++] = i;
printf("%d%c",a[mmin[st2]],i==n-1?'\n':' ');
}
rep(i,k-1,n){
while(st1<ed1 && i-mmax[st1]+1 > k) st1++;
while(st1<ed1 && a[mmax[ed1-1]] <= a[i]) ed1--;
mmax[ed1++] = i;
printf("%d%c",a[mmax[st1]],i==n-1?'\n':' ');
}
return 0;
}

例题3 Max Sum of Max-K-sub-sequence

题意

给定一个序列,求不超过$k$长的最大连续子段和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int T,n,k;
const int maxn = 1e5 + 123;
int a[maxn*3];
int b[maxn];
int main(){
#ifdef LOCAL
freopen("11.in","r",stdin);
#endif
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
rep(i,0,n) scanf("%d",&b[i]);
rep(i,1,n*2+1) a[i] = (a[i-1] + b[(i-1)%n]);
deque<int> dq;
int ans = -INF;
int l,r;
rep(i,1,2*n+1){
while(!dq.empty() && i-dq.front() > k) dq.pop_front();
while(!dq.empty() && a[dq.back()] > a[i-1]) dq.pop_back();
dq.push_back(i-1);
int temp = a[i] - a[dq.front()];
if(ans < temp){
ans = temp;
l = dq.front()+1; r = i;
if(r>n) r-=n;
}
}
printf("%d %d %d\n",ans,l,r);
}
return 0;
}

例题4 Subsequence

题意

给定一段序列,求最长的一段子序列,他的条件是

  • 最大和最小值的差不超过$k$也不小于$m$。

题解

建立两个单调队列,维护$i$之前最大值的下标和$i$之前最小值的下标。

  • 如果最大值最小值超过$k$就弹出下标较小的,并记录较小的位置。

答案是$i-pos$,如果之后的点满足条件了,那么说明$[pos+1,n]$是满足条件的,因为e.g.$a[min[st] 到min[st+1]]$中的元素都大于$a[min[st+1]]$,如果$a[min[st+1]]$满足那么,$a[min[st]+1]$肯定满足条件。

心路历程:我真是太弱了,这就是一道基础的单调队列题目,我想到用两个单调队列来维护,但是对于小于$k$和大于$m$这个条件,不知道怎么去掌控,妈蛋,其实可以靠单调队列的单调性来掌控他们之间的差距。因为一个是递增一个是递减,如果他们之间的差距过大,那么就可以pop掉队首元素,来减少他们的差距,那么大于$m$怎么维护呢?那就是特判他们之间差距是否大于$m$,大于的话就更新一下答案。对于每个$i$就是维护了他之前最大的值的下标和最小值的下标。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int n,m,k;
const int maxn = 1e5 + 10;
int a[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&m,&k)){
rep(i,1,n+1) scanf("%d",&a[i]);
deque<int> dq1; //min
deque<int> dq2; //max
int ans = 0;int pos = 0;
rep(i,1,n+1){
while(!dq1.empty() && a[dq1.back()]>=a[i]) dq1.pop_back();
dq1.push_back(i);
while(!dq2.empty() && a[dq2.back()]<=a[i]) dq2.pop_back();
dq2.push_back(i);
while(!dq1.empty() && !dq2.empty() && a[dq2.front()]-a[dq1.front()]>k){
if(dq1.front() < dq2.front())
pos = dq1.front(),dq1.pop_front();
else
pos = dq2.front(),dq2.pop_front();
}
if(!dq1.empty() && !dq2.empty() && a[dq2.front()]-a[dq1.front()] >=m)
ans = max(ans,i-pos);
}
printf("%d\n",ans);
}
return 0;
}

例题5 Trade

题意

股票交易

  • $i$天你可以以$APi$价格买入股票,$BPi$价格卖出股票
  • $i$天最多买$ASi$股,最多卖$BSi$股
  • 最多拥有$MaxP$股股票
  • 交易后有缓冲期$M$天

开始无限制的钱,问最多能赚多少钱?

题解

AC代码

例题6 Cut the Sequence

例题7 瑰丽华尔兹

例题8 Sequence Partitioning

http://www.voidcn.com/article/p-bcrxdtjx-nd.html

[POJ3264]Balanced Lineup

[POJ3264]Balanced Lineup

题意

给一个数列,求数列中$l,r$范围内最大值和最小值的差。

题解

st表模板题。求两次rmq即可。

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<iostream>
#include<cstring>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 5e4 + 10;
int n,q;
int st[maxn][32-__builtin_clz(maxn)];
int st2[maxn][32-__builtin_clz(maxn)];
int h[maxn];
int ST(){
int l = 31 - __builtin_clz(n);
rep(i,0,n) {st[i][0] = h[i];st2[i][0] = h[i];}
rep(j,0,l) rep(i,0,n-(1<<j)+1){
st[i][j+1] = max(st[i][j],st[i+(1<<j)][j]);
st2[i][j+1] = min(st2[i][j],st2[i+(1<<j)][j]);
}
}
int max_rmq(int l,int r){
int k = 31 - __builtin_clz(r-l+1);
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int min_rmq(int l,int r){
int k = 31 - __builtin_clz(r-l+1);
return min(st2[l][k],st2[r-(1<<k)+1][k]);
}
int main(){
#ifdef LOCAL
freopen("shui.in","r",stdin);
#endif
while(~scanf("%d%d",&n,&q)){
rep(i,0,n) scanf("%d",&h[i]);
ST();
while(q--){
int l,r;scanf("%d%d",&l,&r);
l--,r--;
printf("%d\n",max_rmq(l,r)-min_rmq(l,r));
}
}
return 0;
}

2018 Multi-University Training Contest 8 D.Parenth

2018 Multi-University Training Contest 8 D.Parentheses Matrix

传送门

题目大意

一个由左括号和右括号组成的矩阵,定义矩阵的权值是矩阵中匹配的行数和列数的和。

题解

一道比较典型的构造题。

如果行数或者列数是奇数那么矩阵显然是奇数的行或列匹配不了。

(())

(())

(())

当情况很多有两个变量的时候我们可以假设一个变量小于另一个变量,假设$h\leq m$。

构造过程还是题解比较清晰。

首先,第一行、最后一列中最多只有一个能够匹配,第一列、最后一行也只有一个能够匹配(考 虑右上角和左下角的括号选取方法),故答案不会超过 w+h−2。 当 h = 2 时,每一列可以构造一对匹配,这样答案已经到达上界。 当 h = 4 时,可以如下构造:

( ( ( ( ( (

) ) ) ) ) )

( ( ( ( ( (

) ) ) ) ) )

这样答案是 w+h−3。

若存在更优的答案,则必有一个边界能够匹配,不妨设是第一列。这样,就已 经有除第一行以外的两行(右括号开头的行)不匹配了,而第一行和最后一列中至少有一个不匹配, 因此最优解不会超过 w+h−3。

当 h≥6 时,可以如下构造:

( ( ( ( ( ( ( (

( ) ( ) ( ) ( )

( ( ) ( ) ( ) )

( ( ( ( ) ) ) )

( ( ) ( ) ( ) )

) ) ) ) ) ) ) )

答案是 w+h−4。同理可证明不存在更优的方法。

在实际操作的时候如果$h>m$可以将矩阵反转一下。就ok了。

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int t,h,w;
char g[300][300];
int main(){
#ifdef LOCAL
freopen("d.in","r",stdin);
#endif
scanf("%d",&t);
while(t--){
bool flag = 0;
scanf("%d%d",&h,&w);
if(h&1){
rep(i,0,h){
rep(j,0,w){
g[i][j] = (j<w/2 ? '(' : ')');
}
}
}else if(w&1){
rep(i,0,h){
rep(j,0,w){
g[i][j] = (i<h/2 ? '(' : ')');
}
}
}
else{
if(h>w){
int temp = h;
h = w;
w = temp;
flag = 1;
}
if(h==2){
rep(i,0,w){
g[0][i] = '(';
g[1][i] = ')';
}
}else if(h == 4){
rep(i,0,w){
g[0][i] = '(';
g[1][i] = (i<w/2 ? ')' : '(');
g[2][i] = (i<w/2 ? '(' : ')');
g[3][i] = ')';
}
}else{
rep(i,0,h){
rep(j,0,w){
if(i==0 || j==0) g[i][j] = '(';
else if(i == h-1 || j == w-1) g[i][j] = ')';
else if((i^j)&1) g[i][j] = ')';
else g[i][j] = '(';
}
}
}
}
if(flag){
rep(j,0,w){
rep(i,0,h){
putchar(g[i][j]);
}
puts("");
}
}else{
rep(i,0,h){
rep(j,0,w){
putchar(g[i][j]);
}
puts("");
}
}
}
return 0;
}

A. Rikka with Nash Equilibrium

A. Rikka with Nash Equilibrium

题解:

神仙dp题

将数字从大到小一次排列,从大往小取.

  • 构造一个三维$dp[now][j][k]​$,表示放入$now​$数字的时候有$i​$行和$j​$列有数字下的情况。为了防止数字成为平衡位置,每次放置的位置都要放置在之前放置元素所在的列或者行上,如果不这么放置的话,他就会成为剩下当前行和当前列最大的(就没能有比他大的数字了)。

  • 初始化,因为第一个数字可以任意存放所以$dp[0][1][1]=m*n$。

  • 转移方程:

    1. 该点放置位置上行和列都有元素,那就是有$i$列和$j$行可以存放。剩下的位置是$j*k-i+1$

      ,$dp[next][j][k]+=dp[now][j][k]*(jk-i+1)$

    2. 该点放置位置上列已经有元素了,但是行上没有元素,从位置的行数减一转移加了一行$dp[next][j][k]+=dp[now][j-1][k]*j(n-i+1)$

    3. 同理如果行有元素的话。$dp[next][j][k]+=dp[now][j][k-1]*i(m-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
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<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 88;
int t,m,n;
ll mod;
ll dp[2][maxn][maxn];
inline ll add(ll a, ll b){
return (a+b>mod)?(a+b)%mod:a+b;
}
inline ll mul(ll a,ll b){
return a*b>mod?(a*b)%mod:a*b;
}
int main(){
#ifdef LOCAL
freopen("a.in","r",stdin);
#endif
scanf("%d",&t);
while(t--){
scanf("%d%d%lld",&n,&m,&mod);
memset(dp,0,sizeof(dp));
dp[0][1][1] = n*m;
int now = 0;
rep(i,2,m*n+1){
int nxt = now^1;
memset(dp[nxt],0,sizeof(dp[nxt]));
rep(j,0,n+1){
rep(k,0,m+1){
if(dp[now][j][k]){
ll tot = j*k - i + 1;
dp[nxt][j][k] = add(dp[nxt][j][k],dp[now][j][k]*tot);
dp[nxt][j+1][k] = add(dp[nxt][j+1][k],mul(dp[now][j][k],1ll*k*(n-j)));
dp[nxt][j][k+1] = add(dp[nxt][j][k+1],mul(dp[now][j][k],1ll*j*(m-k)));
//cout<<dp[now][j][k];
}
}
}
now = nxt;
}
printf("%lld\n",dp[now][n][m]);
}
return 0;
}

ST表学习笔记

ST表学习笔记

功能

ST表示用来求解给定区间RMQ的最值问题。

预处理复杂度:$O(nlongn)$,查询复杂度$O(1)$。

详解

原理

将原数组分成以2幂次的区间块,用$mn[i][j]$表示从$j$到$j-2^i-1$的最小值,最小值显然等于$$min(前半段最小值,后半段中最小值)$$从而得到递推式子

$$min[i][j]=min(mn[i-1][j],mn[i-1][j+2^{i-1}])$$

预处理代码

1
2
3
4
5
6
7
8
9
p[0] = 1;
rep(i,1,20) p[i] = 1<<i;
Log[0] = -1;
rep(i,1,200000) Log[i] = Log[i/2] + 1;
rep(i,1,n) mn[0][i] = a[i];
rep(i,1,n+1)
rep(j,1,n+1)
if(j+p[i]-1 <= n)
mn[i][j] = min(mn[i-1][j],mn[i-1][j+p[i-1]]);

查询

定理1:$$2^{log(a)}>a/2$$

查询$x$到$y$的最小值可以假设$len=y-x+1,t=log(len)$,根据定理1,$2^t>len/2$,那么位置过了一半之后最小值的可能就落在了$x$后面的$2^t$和$y$前面的$2^t$。式子为$$mmin = min(mn[t][x],mn[t][y-2^t+1])$$

实现

1
2
int t = Log[y-x+1];
int ans = min(mn[t][x],mn[t][y-p[t]+1]);

Reference:https://blog.csdn.net/hanks_o/article/details/77547380