CheaSim Blog

[hdoj4622]Reincarnation 字符串hash+dp

Reincarnation

题意

区间查询不同字符串的数量。

题解

字符串hash+dp思想

我们从$1-len$枚举子串的长度,如果该区间内子串就加1。由于可能会有重复所以记录长度为$x$的子串最后一次出现的$L$。如果子串出现过那么$dp[L][R]-1$。

定义$dp[l][r]$为在$[l,r]$区间内不同子串的数量那么可以得到递推公式

  1. $dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]$

相当于把一个区间拆成三部分,$[l+1][r],[l][r-1],[l+1][r-1]$,这三个区间合并就是前两个集合个数相加之后减掉这两个集合并的部分元素的个数。

妙啊!

之后在预处理枚举出每一种长度的子串,从1循环到Len,如果发生重复那么可以将之前出现的+1操作的$L$到现在操作的$R$减少一个,那么这个区间的不同子串的个数就减少了一个。而且由于是递推的所以所有$[1,2,3,4,5]-L,R$都会减少1.

由于$n\leq 2000$所以可以使用$O(n^2)$的做法。

主要是dp要想明白,如果一旦确定了递推情况,就不用管细枝末节的东西了,直接在区间上减少就可以了,使得每次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
#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 = 2e3+10;
typedef unsigned long long ull;
int n;
char s[maxn];
ull base[maxn],has[maxn],pos[maxn];
ull bas = 131;
const int mod = 1e5+7;
struct Hash_table{
int head[mod+2],num;
ull edgenum[maxn];
int next[maxn],close[maxn];
void init(){
num = 0;
memset(head,-1,sizeof(head));
}
int add(ull val,int id){
int u = val % mod;
for(int i=head[u];~i;i=next[i]){
if(val == edgenum[i]){
int temp = close[i]; close[i] = id;
return temp;
}
}
edgenum[num] = val;
close[num] = id;
next[num] = head[u];
head[u] = num++;
return -1;
}
}H;
int dp[maxn][maxn];
int main(){
#ifdef LOCAL
freopen("3.in","r",stdin);
#endif
int T;scanf("%d",&T);
base[0] = 1;
rep(i,1,maxn) base[i] = base[i-1]*bas;
while(T--){
scanf("%s",s+1); int len = strlen(s+1);
memset(dp,0,sizeof(dp));
rep(i,1,len+1) has[i] = has[i-1]*bas + s[i] - 'a' + 1;
for(int x=1;x<=len+1;x++){
H.init();
for(int i=1;i+x-1<=len;i++){
int pos = H.add(has[i+x-1]-base[x]*has[i-1],i);
dp[i][x+i-1]++;
if(pos != -1) dp[pos][x+i-1]--;
}
}
per(i,1,len+1) rep(j,i,len+1)
dp[i][j] += dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
int q;scanf("%d",&q);
while(q--){
int x,y;scanf("%d%d",&x,&y);
printf("%d\n",dp[x][y]);
}
}

return 0;
}

[hdoj6406]Taotao Picks Apple

Taotao Picks Apples

题意

一段序列,从中挑选的子序列是这样规定的

能取就取,而且取得数字一定要比上一次取得数字要大。

已知一段序列,问如果改变序列中的一个数字,那么取得数字的个数是多少。

题解

每次查询将数组分为两部分$[1,p-1],[p+1,n]$,前半部分的最长长度就是可以通过预处理得到。很容易想到先处理出从头到$i$的最大长度$dl[i]$。然后针对后半段,我们要得到的是置换的$q$,然后大于他的第一个数字到末尾的最大长度。

有一个细节就是,如果置换的数字小于前面最大的数字,那么就是从前面最大的数字到$[p+1,n]$中间大于这个数字的第一个数字开始。

如果置换的数字大于前面最大的数字,那么答案就是$dl([1,p-1])+1+dr([p+1,n])$这个意思。

寻找第一个比该数字大的最前数字用的是线段树。


+1 判断最大值的时候直接用mmax判断,应该用a[x]判断

+1 得到dr数组的时候范围搞错了,应该是[i,n]的范围内比a[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
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
#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

typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 2e5+100;
int n,m;
int a[maxn];
struct Segtree{
int mmax[maxn<<2]={0},id[maxn<<2]={0},cur=0;
void pushup(int rt){
int x = id[rt<<1],y = id[rt<<1|1];
id[rt] = a[x]>a[y]?x:y;
mmax[rt] = max(mmax[rt<<1],mmax[rt<<1|1]);
}
void build(int l,int r,int rt){
if(l==r){
mmax[rt] = a[l];
id[rt] = l;
return;
}
int mid = l+r>>1;
build(lson); build(rson);
pushup(rt);
}
void query1(int val,int L,int R,int l,int r,int rt){
if(l==r){
if(mmax[rt]>val) cur = min(cur,id[rt]);
return;
}
int mid = l+r>>1;
if(L<=l && r<=R){
if(mmax[rt<<1]>val) query1(val,L,R,lson);
else if(mmax[rt<<1|1]>val) query1(val,L,R,rson);
return;
}
if(L<=mid) query1(val,L,R,lson);
if(mid<R) query1(val,L,R,rson);
}
void query2(int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
if(mmax[rt]>a[cur]) cur = id[rt];
return;
}
int mid = l+r>>1;
if(L<=mid) query2(L,R,lson);
if(mid<R) query2(L,R,rson);
}
}st;
int dl[maxn],dr[maxn];
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
rep(i,1,n+1) scanf("%d",a+i);
st.build(1,n,1);
int mx = 0;
memset(dr,0,sizeof(dr));
memset(dl,0,sizeof(dl));
rep(i,1,n+1){
if(a[i]>mx){
mx = a[i];
dl[i] = dl[i-1]+1;
}else{
dl[i] = dl[i-1];
}
}
per(i,1,n+1){
st.cur = n+1;
st.query1(a[i],i,n,1,n,1);
if(st.cur>n) st.cur = 0;
dr[i] = dr[st.cur]+1;
}
while(m--){
int p,q;scanf("%d%d",&p,&q);
int ans = 0; st.cur = 0;
if(p>1) st.query2(1,p-1,1,n,1);
ans += dl[st.cur];
if(q > a[st.cur]) ans ++;
if(q <= a[st.cur]) q = a[st.cur];
st.cur = n+1;
if(p<n) st.query1(q,p+1,n,1,n,1);
if(st.cur<=n) ans += dr[st.cur];
printf("%d\n",ans);
}
}

return 0;
}

[hdoj6376]度度熊剪纸条

度度熊剪纸条

题意

将一段01的序列分成$k$段,将他们重新拼接,问拼接成的纸条中前导0最多有多少个。

  • 拼接不可以改变方向。

题解

我模拟下来就是我们可以将这段序列分成三部分,

比如

11111/0/111/0/1111/0/11111

最前方的1部分和最后方的1部分和中间的1部分。

如果想将中间的1放在前面就必须剪两次,前方和后方的只需要1次。

模拟的话。

  • 我们首先如果第一段不剪,那么就是从后面的中间1和后方1选择,中间1需要剪两次,后方1只要剪一次。

  • 如果第一段剪掉,那么就是选择最大的,在他前面切一刀,把它当做最后的处理,其余的是前面的和后面的剪一刀和中间的剪两刀。

如果$k==0$特殊处理一下。

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
#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 int maxn = 1e4+100;
char s[maxn];
int ve[maxn];
int solve(){
bool flag = 0;
int cnt = 0;
int l = 0,r = 0;
int ans = 0;
int j = 0;
int st = 0,ed = n-1;
while(s[st]==1&&st<n)st++;
l = st;
while(s[ed]==1&&st<=ed) ed--;
r = n-ed-1;
rep(i,st,ed+1){
if(s[i] == 1) cnt++;
if(s[i] == 0 && cnt){
ve[++j] = cnt;
cnt = 0;
}
}
if(k==0) return l;
sort(ve+1,ve+j+1);
while(k>2 && j>=1){
ans += ve[j--];
k-=2;
}
if(k==1){
ans += max(l+r,ve[j]);
}else{
ans += max(l+r,max(l+ve[j],r+ve[j]));
}
return ans;
}
char temp[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
while(~scanf("%d%d",&n,&k)){
scanf("%s",s);
rep(i,0,n) s[i] -= '0';
printf("%d\n",solve());
}

return 0;
}

[hdoj4628]Pieces

HDU - 6071

题意

将一个字符串每次减少一个子回文串,例子avdffd可以减少dd,就是subsequence。问最少减少几个回文子串可以使得字符串消失。

题解

因为数据量只有16,所以是状压dp。

将每个字符串的位置状态压缩,之后再将回文子串先预处理出来,每次都判断一下是否可以减少回文子串,之后就是状压dp的基本操作了。


有一个检验是否可以减去的好方法

1
2
3
if((S&x)==x)
dfs(S^x);
//表示了x是可以减掉的。

之后预处理的时候将状压的值进行check更加方便。

好久没做状压dp了,居然可以return 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
#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
char s[30];
int len;
int vis[1<<18];
bool check(int x){
char str[20] = "";int cnt = 0;
rep(i,0,len){
if(((x>>i)&1)==1)
str[cnt++] = s[i];
}
rep(i,0,cnt/2){
if(str[i] != str[cnt-i-1]) return false;
}
return true;
}
int Set[1<<18];
int tot;
int dfs(int S){
if(vis[S]<INF) return vis[S];
rep(i,0,tot) if((S&Set[i]) == Set[i]){
vis[S] = min(dfs(S^Set[i]),vis[S]);
}
return ++vis[S];
}
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%s",s);len = strlen(s);
tot = 0;
int S = (1<<len) - 1;
memset(vis,127,sizeof(vis));
vis[0] = 0;
rep(i,1,S+1){
if(check(i)) Set[tot++] = i;
}
printf("%d\n",dfs(S));
}

return 0;
}

树形dp5题连做

树形dp,3天搞定

A - Information Disturbing

题意

对于一棵树,切断一些边使得每一个叶节点都无法连接到根节点,有两个要求

  • 每条边带权重,切掉的边权重和不大于$m$
  • 切掉的每条边都不能大于一个$ans$

问$ans$最小是多少?

题解

二分+树形dp


+1 ans初始化-1

看别人代码发现了个好东西,链式前项星判断节点是否为叶节点可以这样判断

1
2
3
4
if(G[head[u]].to == father) 
该节点是叶节点 //在双向边的时候适用
if(head[u]==-1)
该节点是叶节点 //在单向边的时候适用

ac代码

模板

Java大数模板

二、Java之输入输出处理
由于ACM竞赛题目的输入数据和输出数据一般有多组(不定),并且格式多种多样,所以,如何处理题目的输入输出是对大家的一项最基本的要求。这也是困扰初学者的一大问题。

  1. 输入:

格式1:Scanner sc = new Scanner (new BufferedInputStream(System.in));

格式2:Scanner sc = new Scanner (System.in);

在读入数据量大的情况下,格式1的速度会快些。

读一个整数: int n = sc.nextInt(); 相当于 scanf(“%d”, &n); 或 cin >> n;

读一个字符串:String s = sc.next(); 相当于 scanf(“%s”, s); 或 cin >> s;

读一个浮点数:double t = sc.nextDouble(); 相当于 scanf(“%lf”, &t); 或 cin >> t;

读一整行: String s = sc.nextLine(); 相当于 gets(s); 或 cin.getline(…);

判断是否有下一个输入可以用sc.hasNext()或sc.hasNextInt()或sc.hasNextDouble()或sc.hasNextLine()

1
2
3
4
5
6
import java.util.Scanner;
import java.math.*;
Scanner cin = new Scanner (new BufferedInputStream(System.in));
while(cin.haxNextInt){
//操作
}

大数使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import java.io.*;
import java.math.*;
import java.util.*;

public class Main {
static Scanner in = new Scanner(System.in);
public static void main(String[] args) {
int a = in.nextInt();
BigInteger b = in.nextBigInteger();
BigDecimal c = in.nextBigDecimal();
/*
BigDecimal:
构造方法:
BigDecimal(BigInteger val)
BigDecimal(BigInteger unscaledVal, int scale)
BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)
BigDecimal(BigInteger val, MathContext mc)
BigDecimal(char[] in)
BigDecimal(char[] in, int offset, int len)
BigDecimal(char[] in, int offset, int len, MathContext mc)
BigDecimal(char[] in, MathContext mc)
BigDecimal(double val)
BigDecimal(double val, MathContext mc)
BigDecimal(int val)
BigDecimal(int val, MathContext mc)
BigDecimal(long val)
BigDecimal(long val, MathContext mc)
BigDecimal(String val)
BigDecimal(String val, MathContext mc)
成员方法:
BigDecimal abs()
BigDecimal abs(MathContext mc)
BigDecimal add(BigDecimal augend)
BigDecimal add(BigDecimal augend, MathContext mc)
byte byteValueExact()
int compareTo(BigDecimal val)
BigDecimal divide(BigDecimal divisor)
BigDecimal divide(BigDecimal divisor, int roundingMode)
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
BigDecimal divide(BigDecimal divisor, MathContext mc)
BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
BigDecimal[] divideAndRemainder(BigDecimal divisor)
BigDecimal[] divideAndRemainder(BigDecimal divisor, MathContext mc)
BigDecimal divideToIntegralValue(BigDecimal divisor)
BigDecimal divideToIntegralValue(BigDecimal divisor, MathContext mc)
double doubleValue()
boolean equals(Object x)
float floatValue()
int hashCode()
int intValue()
int intValueExact()
long longValue()
long longValueExact()
BigDecimal max(BigDecimal val)
BigDecimal min(BigDecimal val)
BigDecimal movePointLeft(int n)
BigDecimal movePointRight(int n)
BigDecimal multiply(BigDecimal multiplicand)
BigDecimal multiply(BigDecimal multiplicand, MathContext mc)
BigDecimal negate()
BigDecimal negate(MathContext mc)
BigDecimal plus()
BigDecimal plus(MathContext mc)
BigDecimal pow(int n)
BigDecimal pow(int n, MathContext mc)
int precision()
BigDecimal remainder(BigDecimal divisor)
BigDecimal remainder(BigDecimal divisor, MathContext mc)
BigDecimal round(MathContext mc)
int scale()
BigDecimal scaleByPowerOfTen(int n)
BigDecimal setScale(int newScale)
Returns a BigDecimal whose scale is the specified value, and whose value is numerically equal to this BigDecimal's.
BigDecimal setScale(int newScale, int roundingMode)
BigDecimal setScale(int newScale, RoundingMode roundingMode)
Returns a BigDecimal whose scale is the specified value, and whose unscaled value is determined by multiplying or dividing this BigDecimal's unscaled value by the appropriate power of ten to maintain its overall value.
short shortValueExact()
int signum()
Returns the signum function of this BigDecimal. (1,0,-1)
BigDecimal stripTrailingZeros()
Returns a BigDecimal which is numerically equal to this one but with any trailing zeros removed from the representation.
BigDecimal subtract(BigDecimal subtrahend)
BigDecimal subtract(BigDecimal subtrahend, MathContext mc)
BigInteger toBigInteger()
BigInteger toBigIntegerExact()
String toEngineeringString()
String toPlainString()
String toString()
BigDecimal ulp()
Returns the size of an ulp, a unit in the last place, of this BigDecimal.
BigInteger unscaledValue()
static BigDecimal valueOf(double val)
static BigDecimal valueOf(long val)
static BigDecimal valueOf(long unscaledVal, int scale)
*/
BigDecimal test = new BigDecimal("1.234567");
test = test.setScale(3, RoundingMode.HALF_UP);
System.out.println(test);
test = test.setScale(7, RoundingMode.HALF_EVEN);
System.out.println(test);
test = test.divide(new BigDecimal("3"), MathContext.UNLIMITED); // 默认也是UNLIMITED精度,无限小数会报错
System.out.println(test);

/*
BigInteger:
构造方法:
BigInteger(byte[] val)
BigInteger(int signum, byte[] magnitude)
BigInteger(int bitLength, int certainty, Random rnd)
Constructs a randomly generated positive BigInteger that is probably prime, with the specified bitLength.
BigInteger(int numBits, Random rnd)
Constructs a randomly generated BigInteger, uniformly distributed over the range 0 to (2numBits - 1), inclusive.
BigInteger(String val)
BigInteger(String val, int radix)
成员方法:
BigInteger abs()
BigInteger add(BigInteger val)
BigInteger and(BigInteger val)
BigInteger andNot(BigInteger val)
Returns a BigInteger whose value is (this & ~val).
int bitCount()
Returns the number of bits in the two's complement representation of this BigInteger that differ from its sign bit.
int bitLength()
Returns the number of bits in the minimal two's-complement representation of this BigInteger, excluding a sign bit.
BigInteger clearBit(int n)
Returns a BigInteger whose value is equivalent to this BigInteger with the designated bit cleared.
int compareTo(BigInteger val)
BigInteger divide(BigInteger val)
BigInteger[] divideAndRemainder(BigInteger val)
double doubleValue()
boolean equals(Object x)
BigInteger flipBit(int n)
Returns a BigInteger whose value is equivalent to this BigInteger with the designated bit flipped.
float floatValue()
BigInteger gcd(BigInteger val)
Returns a BigInteger whose value is the greatest common divisor of abs(this) and abs(val).
int getLowestSetBit()
Returns the index of the rightmost (lowest-order) one bit in this BigInteger (the number of zero bits to the right of the rightmost one bit).
int hashCode()
int intValue()
boolean isProbablePrime(int certainty)
Returns true if this BigInteger is probably prime, false if it's definitely composite.
long longValue()
BigInteger max(BigInteger val)
BigInteger min(BigInteger val)
BigInteger mod(BigInteger m)
BigInteger modInverse(BigInteger m)
Returns a BigInteger whose value is (this^-1 mod m).
BigInteger modPow(BigInteger exponent, BigInteger m)
BigInteger multiply(BigInteger val)
BigInteger negate()
BigInteger nextProbablePrime()
Returns the first integer greater than this BigInteger that is probably prime.
BigInteger not()
BigInteger or(BigInteger val)
BigInteger pow(int exponent)
static BigInteger probablePrime(int bitLength, Random rnd)
Returns a positive BigInteger that is probably prime, with the specified bitLength.
BigInteger remainder(BigInteger val)
Returns a BigInteger whose value is (this % val).
BigInteger setBit(int n)
Returns a BigInteger whose value is equivalent to this BigInteger with the designated bit set.
BigInteger shiftLeft(int n)
Returns a BigInteger whose value is (this << n).
BigInteger shiftRight(int n)
Returns a BigInteger whose value is (this >> n).
int signum()
BigInteger subtract(BigInteger val)
boolean testBit(int n)
Returns true if and only if the designated bit is set.
byte[] toByteArray()
String toString()
String toString(int radix)
static BigInteger valueOf(long val)
BigInteger xor(BigInteger val)
*/

MyPair[] pairs = new MyPair[1000];
Arrays.sort(pairs);
Arrays.binarySearch(pairs, 1, 4, new MyPair());
/*
二分查找
如果元素在数组中,则值为0~n-1,否则值为-1~-(n+1),表示第一个比它大的值的位置,下标从1开始
*/
List<MyPair> pairList = new ArrayList<>();
pairList.add(new MyPair());
//pairList.sort();
pairList.sort(new Cmp());
Collections.shuffle(pairList);
Collections.swap(pairList, 1, 3);
Collections.sort(pairList);
}
}

class MyPair implements Comparable {
int x, y;

@Override
public int compareTo(Object o) {
MyPair b = (MyPair)o;
if(x!=b.x) return x<b.x?-1:1;
else if(y!=b.y) return y<b.y?-1:1;
return 0;
}
}

class Cmp implements Comparator<MyPair> {
@Override
public int compare(MyPair o1, MyPair o2) {
if(o1.x!=o2.x) return o1.x<o2.x?-1:1;
else if(o1.y!=o2.y) return o1.y<o2.y?-1:1;
return 0;
}
}

关于树的

树链剖分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
#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;
}

线段树

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
#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
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head ----------------------------------------
const int maxn = 1e6+20;
int T,n,m;
struct sgt{
int maxlen[maxn<<2];
int ll[maxn<<2],rr[maxn<<2],mm[maxn<<2];
int lazy[maxn<<2];
void build(int l,int r,int rt){
lazy[rt] = -1;
if(l==r){
ll[rt] = rr[rt] = 0;
mm[rt] = 1;
return;
}
int mid = l+r>>1;
build(lson); build(rson);
}
void init(){
lazy[1] = 1;
}
void pushup(int l,int r,int rt){
if(lazy[rt]>=0){
maxlen[rt] = ll[rt] = rr[rt] = mm[rt] = (lazy[rt]?(r-l+1):0);
}else{
int lc = rt<<1,rc = rt<<1|1,mid = l+r>>1;
ll[rt] = ll[lc];
if(ll[lc]==(mid-l+1)) ll[rt] += ll[rc];
mm[rt] = rr[lc] + ll[rc];
rr[rt] = rr[rc];
if(rr[rc] == r-mid) rr[rt] += rr[lc];
maxlen[rt] = max(maxlen[lc],max(maxlen[rc],mm[rt]));
}
}
void pushdown(int rt){
if(lazy[rt]>=0){
int lc=rt<<1,rc=rt<<1|1;
lazy[lc] = lazy[rc] = lazy[rt];
lazy[rt] = -1;
}
}
int query(int val,int l,int r,int rt){
pushup(l,r,rt);
//cout<<l<<' '<<r<<' '<<" ";
int lc = rt<<1,rc = rt<<1|1,mid = l+r>>1;
if(val>maxlen[rt]) return 0;
if(ll[rt] >= val) return l;
pushdown(rt);
pushup(rson);
int temp = query(val,lson);
if(temp) return temp;
if(mm[rt] >= val) return mid-rr[lc]+1;
return query(val,rson);
}
void update(int val,int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
lazy[rt] = val;
pushup(l,r,rt);
return;
}
pushdown(rt);
int mid = l+r>>1;
if(L<=mid) update(val,L,R,lson);
else pushup(lson);
if(mid<R) update(val,L,R,rson);
else pushup(rson);
pushup(l,r,rt);
}
}all,ns;
char op[20];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
scanf("%d",&T);
rep(test_case,1,T+1){
printf("Case %d:\n",test_case);
scanf("%d%d",&n,&m);
//all.build(1,n,1); ns.build(1,n,1);
all.init(); ns.init();
while(m--){
scanf("%s",op);
if(op[0]=='D'){
int x;scanf("%d",&x);
int ans = all.query(x,1,n,1);
if(ans){
all.update(0,ans,ans+x-1,1,n,1);
printf("%d,let's fly\n",ans);
}else{
puts("fly with yourself");
}
}else if(op[0]=='N'){
int x;scanf("%d",&x);
int ans = all.query(x,1,n,1);
if(!ans) ans = ns.query(x,1,n,1);
if(ans){
all.update(0,ans,ans+x-1,1,n,1);
ns.update(0,ans,ans+x-1,1,n,1);
printf("%d,don't put my gezi\n",ans);
}else{
puts("wait for me");
}
}else{
int x,y;scanf("%d%d",&x,&y);
all.update(1,x,y,1,n,1);
ns.update(1,x,y,1,n,1);
puts("I am the hope of chinese chengxuyuan!!");
}
}
}

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

树链剖分学习笔记

树链剖分学习笔记

题型

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

轻重边剖分

将树中的边分为两部分,轻边和重边,用$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代码