2020 牛客多校第一场
A. B-Suffix Array
后缀数组的思想:倍增+桶排序的方式找出一串连续序列后缀的大小。虽说正常使用的时候都是字典序,但是只要修改排序方式,也能够达到一个类似的"后缀数组“效果。
当然直接修改排序方式可能比较麻烦(都是抄板子),那么不如就对应排序方式修改下原数组的值做个变换就行。
题目解法来自论文,。。猜的对就能做
B. Infinite Tree
虚树的应用:
显然点太多不可能全部找出来,只有lca能来点作用
问题就是怎么找lca,
使用一个树状数组维护各个因数个数
由于树的转移都是找的最小因数,显然lca即为降序排序后,最大相同长度因数的乘积,然后这个乘积其实可以不需要,只求深度即可(显然一直增大)
可以通过反证法证明 v点比v点号的条件是: f [ u ] < 2 f [ v ] f[u] < 2 f[v] f[u]<2f[v]
C. Domino 不会
D. Quadratic Form
。。。。拉格朗日乘子法, 推公式
E.Counting Spanning Trees 论文题
F.Infinite String Comparision
普通做法:找循环节比较大小(KMP)
神仙做法:看成26进制下的无限循环小数
∣
S
∣
(
26
)
2
6
∣
s
∣
−
1
\frac{|S|_{(26)}}{26^{|s|}-1}
26∣s∣−1∣S∣(26)
之后通过式子变换可以直接比较S1+S2和S2+S1
补一个循环节:记得判长度与n-next[n]长度是否除尽
string getCycleString(const string & str){
int n = str.size();
vector<int> nxt(n + 1, 0);
for (int i = 1 ; i < n ; ++i){
int j = i;
while(j > 0){
j = nxt[j];
if(str[i] == str[j]){
nxt[i+1] = j + 1;
break;
}
}
}
int cycleLen = n - nxt[n];
if(n % cycleLen == 0)
return str.substr(0,cycleLen);
else
return str;
}
H. Minimum-cost Flow
给定每天边的价格
每次询问
u
i
v
i
\frac{u_i}{v_i}
viui,表示每条边的容量,求流量为1最小费用流
网络流的理解:对于能在残量网络上继续跑的算法,EK,Dinic, 通常都会有一个
while(bfs()) maxflow += run();表示选择一条路径进行增广
而在费用流里,每次增广的路径都是开销最小的。
换句话说,假设
F
i
F_i
Fi为第i次跑的流量,
C
i
C_i
Ci为费用
显然可以得出:
当最大流限制在
[
F
i
−
1
+
1
,
F
i
]
[F_{i-1}+1,F_{i}]
[Fi−1+1,Fi]的时候,我们选择的这条增广路径是不会变的,假设这条增广路径开销为
V
V
V,那么对于
F
i
−
1
<
F
≤
F
i
F_{i-1} < F \leq F_i
Fi−1<F≤Fi的流量
F
F
F,
显然有
C
o
s
t
=
C
i
−
1
+
(
F
−
F
i
−
1
)
∗
V
Cost = C_{i-1} + (F-F_{i-1})*V
Cost=Ci−1+(F−Fi−1)∗V ,
V
V
V显然可以通过相邻两个C推出。
I. 1 or 2
给出一些边和点,挑出子图使得恰好有
d
i
∈
{
1
,
2
}
d_i \in \{1,2\}
di∈{1,2}条边与点
V
i
V_i
Vi相连。
肯定是匹配问题,匹配问题必须要认清哪些是点哪些是边,这题就如题意
显然很难分成两个独立集合,看到数据50,100,并且d有限,考虑一般图匹配
主要是建模部分,对于连接 ( u , v ) (u,v) (u,v)的一条边,有3种情况
d
u
=
d
v
=
1
d_u = d_v = 1
du=dv=1 显然直接连即可
d
u
=
1
,
d
v
=
2
d_u = 1 , d_v = 2
du=1,dv=2 拆点直接连即可
d
u
=
d
v
=
2
d_u = d_v = 2
du=dv=2 不可直接两两互联,因为边只能用一次 ,所以对于这条边,开两个点
E
i
,
E
i
′
E_i,E_{i'}
Ei,Ei′使他们相连,并且
E
i
E_i
Ei连接u的两点
E
i
′
E_{i'}
Ei′连接v的两点。
跑一般图最大匹配后再判断是否所有都满足条件。
在写的时候我也尝试过把第三种写法应用在第二种上,也就是u-e1-e2-{v,v’}的构图方式,但后来发现有问题。
原因是:可能会出现u-e1被选择,v,v’早已被选择,而e2被孤立的情况。 我们求的虽然是最大匹配,但是由于边选择了,两个点必须也要被选。点选了,必须要有相应的边,不可能出现孤立的情况,所以我们要在保证是最大匹配的同时保证完美匹配
使用第二种构建方式的代码:
#include <bits/stdc++.h>
const int N = 1e3 + 100;
int n, m, match[N], pre[N];
int col[N], fa[N];
int que[N], head, tail;
std::vector<int> G[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int hash[N], hash_cnt;
// 暴力找LCA,交替爬升
inline int get_lca(int x, int y) {
++hash_cnt;
while (hash[x] != hash_cnt) {
if (x) hash[x] = hash_cnt, x = find(pre[match[x]]);
std::swap(x, y);
}
return x;
}
// 代码照搬了网上的,1表示白2表示黑
// fa维护的是在哪个花
// 把白点变成黑点并入队
inline void blossom(int x, int y, int p) {
while (find(x) != p) {
pre[x] = y;
fa[y = match[x]] = fa[x] = p;
if (col[y] == 1)
col[que[tail++] = y] = 2;
x = pre[y];
}
}
inline int bfs(int st) {
for (int i = 1; i <= n; ++i)
col[i] = 0, fa[i] = i;
col[que[tail = 1, head = 0] = st] = 2;
while (head != tail) {
int x = que[head++];
for (int y : G[x]) {
if (!col[y]) {
col[y] = 1, pre[y] = x;
if (!match[y]) {
// 增广
while (x) {
x = match[pre[y]];
match[match[y] = pre[y]] = y;
y = x;
}
return 1;
} else
col[que[tail++] = match[y]] = 2;
} else if (col[y] == 2 && find(x) != find(y)) {
// 奇环,缩花
int p = get_lca(x, y);
blossom(x, y, p);
blossom(y, x, p);
}
}
}
return 0;
}
int ds[N];
bool bzzb[3020];
inline void add(int a , int b){
bzzb[a] = bzzb[b] = 1;
G[a].push_back(b);
G[b].push_back(a);
}
void addedge(int x , int y, int id){
if(ds[x] == 1 && ds[y] == 1){
add(2*x,2*y);
} else if(ds[x] == 1 && ds[y] == 2) {
int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
add(e1,e2);
add(2*x,e1);
add(2*y,e2); add(2*y-1,e2);
} else if(ds[x] == 2 && ds[y] == 1){
int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
add(e1,e2);
add(2*x,e1); add(2*x-1,e1);
add(2*y,e2);
} else {
int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
add(e1,e2);
add(2*x,e1); add(2*x-1,e1);
add(2*y,e2); add(2*y-1,e2);
}
}
void clear(){
hash_cnt = 0;
memset(hash,0,sizeof(hash));
memset(match,0,sizeof(match));
memset(fa,0,sizeof(fa));
memset(col,0,sizeof(col));
memset(pre,0,sizeof(pre));
memset(bzzb,0,sizeof(bzzb));
memset(que,0,sizeof(que));
for (int i = 0 ; i < N ; ++i) G[i].clear();
head = tail = 0;
}
int main() {
using std::cin;
while(cin >> n >> m){
clear();
int sum = 0;
for (int i = 1 ; i <= n ; ++i){
scanf("%d",ds+i);
sum += ds[i];
bzzb[i*2] = 1;
if(ds[i] == 2){
bzzb[i*2-1] = 1;
}
}
for (int i = 1, x, y; i <= m; ++i) {
scanf("%d %d", &x, &y);
addedge(x,y,i);
}
int ans = 0;
int n1 = n*2;
n = 800;
for (int i = 1; i <= 3000; ++i){
if(!bzzb[i]) continue;
if(!match[i]) bfs(i);
if(match[i] && i <= n1) ans++;
if(!match[i]) ans = 1e5;
}
if(ans == sum) puts("Yes");
else puts("No");
}
return 0;
}
J Easy Integration
分部积分。
知识点回顾:
- 后缀数组cmp方法
- 虚树的使用,只和lca相关使用虚树
- 增广路径每步干了些什么
- 最大匹配和完美匹配的异同 :完美匹配一定是最大匹配,反之不成立。