周二
昨天打牛客多校了,所以没写博客,今天补题。
LCS(思维)
这题当时是我想的,做出来了。但是写的比较麻烦,我是先把abc排个序,然后再来构造,然后再反推回去。这个反推的过程挺烧脑的,很绕。不过最后还是AC了
看了题解,本质上是一样的,但是思路更清晰一些,也更好写一些
首先是一个转化,mi为a b c中最小的,那么先放mi个‘a’
这样就把三个LCS转化成了两个LCS,简单很多
接下来就无脑填就好了,比如a还有剩,就在第一个字符串和第二个字符串后加上‘b’
满足后,剩下就随便填填满就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int a, b, c, n;
scanf("%d%d%d%d", &a, &b, &c, &n);
int mi = min({a, b, c});
a -= mi; b -= mi; c -= mi;
if(a + b + c + mi > n)
{
puts("NO");
return 0;
}
string s1(mi, 'a'), s2(mi, 'a'), s3(mi, 'a');
while(a--)
{
s1.push_back('b');
s2.push_back('b');
}
while(b--)
{
s2.push_back('c');
s3.push_back('c');
}
while(c--)
{
s1.push_back('d');
s3.push_back('d');
}
while(s1.size() < n) s1.push_back('x');
while(s2.size() < n) s2.push_back('y');
while(s3.size() < n) s3.push_back('z');
cout << s1 << endl << s2 << endl << s3 << endl;
return 0;
}
Average(二分答案)
这道题当时是队友用凸包加二分写的。看了题解还可以二分答案。都写一写
之后学学斜率优化再来补这题的斜率写法
当时是没有想到二分答案的。
如果设答案为k
那么就验证是否存在一个长度大于x的区间的平均值大于等于k
直接这样很难处理,因为这样等价于求某一段区间和大于len * k
而这个len >= x是不确定的,从而也就确定和S
这里用了一个技巧,就是每个数都减去k
从而就是平均均值大于等于0,从而就是和大于等于 len * 0 = 0
这样len乘上去就没有用了,从而没有了len不确定的影响
所以就是求是否有一段区间和大于等于0
把区间和转化为前缀和的差值,也就是s[r] - min(s[l - 1])
用一个变量存s[l - 1]的最小值即可,可以O(n)求
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, m, x, y;
double s[N];
bool check(double k, int *a, int n, int x)
{
double res = -1e9, mi = 1e9;
_for(i, 1, n) s[i] = s[i - 1] + a[i] - k;
_for(i, 1, n)
{
if(i - x >= 0) mi = min(mi, s[i - x]);
res = max(res, s[i] - mi);
}
return res >= 0;
}
double solve(int *a, int n, int x)
{
double l = 0, r = 1e6;
while(r - l > 1e-10)
{
double m = (l + r) / 2;
if(check(m, a, n, x)) l = m;
else r = m;
}
return l;
}
int main()
{
scanf("%d%d%d%d", &n, &m, &x, &y);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, m) scanf("%d", &b[i]);
printf("%.10f\n", solve(a, n, x) + solve(b, m, y));
return 0;
}
Inverse Pair(思维)
当时是队友做的,我也写一下
每次加1,如果前面有a[i] + 1的那么逆序对可以减1,同时后面的a[i] - 1再加1就没用了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N], f[N], vis[N], n;
int lowbit(int x) { return x & -x; }
void add(int x, int p)
{
for(; x <= n; x += lowbit(x))
f[x] += p;
}
int sum(int x)
{
int res = 0;
for(; x; x -= lowbit(x))
res += f[x];
return res;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
ll ans = 0;
_for(i, 1, n)
{
ans += sum(n) - sum(a[i]);
add(a[i], 1);
if(!vis[a[i]] && sum(a[i] + 1) - sum(a[i]) == 1)
{
ans--;
vis[a[i] - 1] = 1;
}
}
printf("%lld\n", ans);
return 0;
}
Tree Xor(异或 线段树写法 )
这题真的理解了我一个下午,太菜了
还是学到了很多很多的。有两种写法,一种是类似线段树,一种是字典树
可以发现,一个数的值确定了,其他数的值全部都确定了
如果设a[1] = 0,那么就可以求出其他所有点的权值
这时,如果令a[1] = x,那么其他所有点都会异或一个x
如a[i] = 0 ^ w1 ^ w2 ^ w3…… = w1 ^ w2 ^ w3……
a[1]=x以后,a[i] = x ^ w1 ^ w2 ^w3
因此会多异或一个x
那么问题就转化成a[1]有多少个取值x
使得 l[i] <= a[i] ^ x <= r[i] 对于每一个点都要符合
也就是a[i] ^ x 属于[l[i], r[i]]
两边同时异或一个a[i]
变成x属于[l[i], r[i]] ^ x
那么显然一个点可以贡献 (r[i] - l[i] - 1)个x的可能的取值
那么把所有点的可能的x的取值求交集,交集的个数就是答案
但是问题在于[l[i], r[i]] ^ x之后的值就很混乱了,不再是一个区间了
那么题解用了一个很巧妙的方法,用类似线段树的思路,将[l, r]分成很多个小区间
开始的区间取[0, 11111111111……] 也就是[0, (1 << 30) - 1]
这样子按照线段树去二分区间就会把[l, r]切成很多个小区间
这样的小区间都满足一个性质,l和r的高位是一样的,低位是0000和1111
如010101 000000
010101 1111111
非常神奇,更神奇的是,这样的区间去异或的话,结果还是落在一个区间里面
前面高位异或之后的结果是固定的,后面的0000到1111去异或之后,不论异或什么,因为已经取遍了所有情况,所以结果还是0000到1111
因此这种区间异或之后的结果还是一个区间
所以利用这个性质,就可以得出这样的小区间每一个数会出现一次
问题就转化为,有很多个区间,一个区间内的每一个数会出现一次
问有多少个数出现了n次
这就可以用一维差分来解决
实际上并没有用到线段树,只是用到了线段树二分区间的这个思想
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int l[N], r[N], a[N], n;
struct edge{ int v, w; };
vector<edge> g[N];
vector<pair<int, int>> res;
void dfs(int u, int fa)
{
for(auto x: g[u])
{
if(x.v == fa) continue;
a[x.v] = a[u] ^ x.w; //先赋值再dfs 这个过程是向下搜的时候赋值,不是回溯的时候赋值
dfs(x.v, u);
}
}
void add(int l, int r, int pos, int x)
{
int cut = ((1 << 30) - 1) ^ ((1 << pos) - 1); //切出pos到29位
int L = (x & cut) ^ (l & cut); int R = L + (1 << pos) - 1;
res.push_back(make_pair(L, 1));
res.push_back(make_pair(R + 1, -1));
}
void split(int l, int r, int L, int R, int pos, int x) //分为第0位到第29位 pos~29位是一样的
{
if(L <= l && r <= R)
{
add(l, r, pos, x);
return;
}
int m = l + r >> 1;
if(L <= m) split(l, m, L, R, pos - 1, x);
if(R > m) split(m + 1, r, L, R, pos - 1, x);
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d%d", &l[i], &r[i]);
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back(edge{v, w});
g[v].push_back(edge{u, w});
}
a[1] = 0;
dfs(1, 0);
_for(i, 1, n) split(0, (1 << 30) - 1, l[i], r[i], 30, a[i]);
int cnt = 0, ans = 0;
sort(res.begin(), res.end());
rep(i, 0, res.size() - 1)
{
cnt += res[i].second;
if(cnt == n) ans += res[i + 1].first - res[i].first;
}
printf("%d\n", ans);
return 0;
}
周三
这几天晚上都在看奥运,没有写题,有点懈怠……训练起来!!
Tree Xor(二进制遍历)
学了这道题的另一种写法,我发现这种写法更好写,更普遍一些
我之前有遇到类似的思路
同样解决一个区间内的数异或的问题
首先对于区间[l, r] 用前缀和
把[0, r]的异或加入
这里涉及到一个二进制的数的遍历方式
如一个二进制数100010101
可以从高位到低位,每遇到一个1,就把这一位赋值位0,后面可以为00000到11111
比如遇到第一个1,就可以切成一个区间
011111111
00000000
然后遇到第二个1,又可以切多一个区间
100001111
100000000
第三个1
100010011
100010000
这样就可以遍历完0到100010101
这样会少一个本身,还剩下一个自己,也就是100010101
可以发现,这样的一个一个区间又特殊的性质,前面是一样的,后面是全0到全1
这样的区间异或后还是一个区间,所以解决这个问题
具体写的时候还有一些细节要注意
1.因为不包括本身,所以计算这个数的时候加个1,就把本身加进去
2.写的时候巧用map,用一维差分
3.位运算
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int l[N], r[N], a[N], n;
struct edge{ int v, w; };
vector<edge> g[N];
map<int, int> mp;
void dfs(int u, int fa)
{
for(auto x: g[u])
{
if(x.v == fa) continue;
a[x.v] = a[u] ^ x.w;
dfs(x.v, u);
}
}
void add(int cur, int x, int v)
{
for(int i = 30; i >= 0; i--)
{
int bit = 1 << i;
if(cur & bit)
{
int cut = ~(bit - 1); //把bit ~ 最高位 截下来
int L = cut & (cur ^ bit ^ x); //注意把当前位赋为0
mp[L] += v;
mp[L + bit] -= v;
}
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d%d", &l[i], &r[i]);
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back(edge{v, w});
g[v].push_back(edge{u, w});
}
a[1] = 0;
dfs(1, 0);
_for(i, 1, n)
{
add(r[i] + 1, a[i], 1);
add(l[i], a[i], -1);
}
int ans = 0, pre = 0, cnt = 0;
for(auto x: mp)
{
if(cnt == n) ans += x.first - pre;
cnt += x.second;
pre = x.first;
}
printf("%d\n", ans);
return 0;
}
D - Meaningless Sequence(二进制遍历)
这道题之前用数位dp写的,也可以用上一道题的遍历一个二进制数来写
推一推公式就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int mod = 1e9 + 7;
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int main()
{
string s;
int c, len;
cin >> s >> c;
len = s.size();
int ans = 0, cnt = 0;
rep(i, 0, len)
if(s[i] == '1')
{
ans = add(ans, mul(binpow(c + 1, len - i - 1), binpow(c, cnt)));
cnt++;
}
ans = add(ans, binpow(c, cnt));
printf("%d\n", ans);
return 0;
}
P3384 【模板】轻重链剖分/树链剖分(树链剖分)
学学树链剖分
最近学一学树上问题的一些算法
树链剖分主要是维护树上路径的一些信息
和dfs序有点类似,都是把树形结构转化成线性结构
dfs序把一个子树拍成一个区间,而树链剖分可以把一条路径分成很多条链,每一条链都是连续的线性区间
具体实现重链剖分,对于一个节点,儿子中子树节点个数最多的为重儿子,父亲到重儿子的边叫重边
多条重边连成重链,重链的顶端是轻儿子。对于叶子节点,是轻儿子的话,这个单独的节点也视为一个重链
这样子下来一颗树就被剖成了很多条链
具体实现时要两次dfs
在dfs的时候,先搜重儿子,是的同一天重链的dfs序是连续的,这样才可以维护一条链的信息
在操作的时候,要处理一条路径上的信息的时候,同样是转成lca
这时类似树上倍增,这时点向上跳是一条链一条链的跳,可以证明这样是logn的
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int t[N << 2], lazy[N << 2], a[N], mod, root, n, m;
int d[N], siz[N], fa[N], son[N], top[N], id[N], rk[N], cnt;
vector<int> g[N];
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
void up(int k) { t[k] = add(t[l(k)], t[r(k)]); }
void update(int k, int l, int r, int x)
{
lazy[k] = add(lazy[k], x);
t[k] = add(t[k], mul(r - l + 1, x));
}
void down(int k, int l, int r)
{
if(!lazy[k]) return;
int m = l + r >> 1;
update(l(k), l, m, lazy[k]);
update(r(k), m + 1, r, lazy[k]);
lazy[k] = 0;
}
void build(int k, int l, int r)
{
if(l == r)
{
t[k] = rk[l];
return;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void change(int k, int l, int r, int L, int R, int x)
{
if(L <= l && r <= R)
{
update(k, l, r, x);
return;
}
down(k, l, r);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, x);
if(R > m) change(r(k), m + 1, r, L, R, x);
up(k);
}
int query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k, l, r);
int m = l + r >> 1, res = 0;
if(L <= m) res = add(res, query(l(k), l, m, L, R));
if(R > m) res = add(res, query(r(k), m + 1, r, L, R));
return res;
}
void dfs1(int u, int father) //第一次dfs预处理,处理出深度,父亲,大小
{
d[u] = d[father] + 1;
fa[u] = father;
siz[u] = 1;
for(int v: g[u])
{
if(v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int fa, int t) //第二次dfs进行剖分,处理出dfs序,每条链的顶端,以及dfs序对应的权值
{
top[u] = t;
id[u] = ++cnt;
rk[cnt] = a[u];
if(siz[u] == 1) return;
dfs2(son[u], u, t);
for(int v: g[u])
{
if(v == fa || v == son[u]) continue;
dfs2(v, u, v);
}
}
void add(int u, int v, int x)
{
while(top[u] != top[v]) //先以链为整体来考虑,不在同一条链的时候,让链顶端较深的点向上跳
{
if(d[top[u]] < d[top[v]]) swap(u, v);
change(1, 1, n, id[top[u]], id[u], x); //跳的过程重处理这条链上的点的信息
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v); //在同一条链时,继续处理
change(1, 1, n, id[v], id[u], x); //从深度小的到深度深的,dfs序从小到大
}
int ask(int u, int v)
{
int res = 0;
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v); //注意是比较top的深度,不是u,v的深度
res = add(res, query(1, 1, n, id[top[u]], id[u]));
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v);
res = add(res, query(1, 1, n, id[v], id[u]));
return res;
}
int main()
{
scanf("%d%d%d%d", &n, &m, &root, &mod);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(root, 0);
dfs2(root, 0, root);
build(1, 1, n);
while(m--)
{
int op, x, y, z;
scanf("%d", &op);
if(op == 1)
{
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
else if(op == 2)
{
scanf("%d%d", &x, &y);
printf("%d\n", ask(x, y));
}
else if(op == 3)
{
scanf("%d%d", &x, &z);
change(1, 1, n, id[x], id[x] + siz[x] - 1, z); //用size数组推出dfs序的右端点
}
else
{
scanf("%d", &x);
printf("%d\n", query(1, 1, n, id[x], id[x] + siz[x] - 1));
}
}
return 0;
}
【模板】最近公共祖先(树链剖分求lca)
树链剖分的常数很小
求lca很方便
树链剖分说白了就是两次dfs剖成很多链,然后处理路径信息的时候往上一条链一条链的跳
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e5 + 10;
int d[N], fa[N], siz[N], son[N], id[N], top[N], n, m, root, cnt;
vector<int> g[N];
void dfs1(int u, int father)
{
d[u] = d[father] + 1;
siz[u] = 1;
fa[u] = father;
for(int v: g[u])
{
if(v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t)
{
id[u] = ++cnt;
top[u] = t;
if(siz[u] == 1) return;
dfs2(son[u], t);
for(int v: g[u])
{
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int lca(int u, int v)
{
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v);
u = fa[top[u]];
}
return d[u] < d[v] ? u : v;
}
int main()
{
scanf("%d%d%d", &n, &m, &root);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(root, 0);
dfs2(root, root);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", lca(u, v));
}
return 0;
}
P2184 贪婪大陆(树状数组)
开始以为要二维树状数组,发现空间开不下
然后发现只看不在当前区间就好了,和左端点和右端点有关
左端点和右端点分别维护一个树状数组即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int f[N][2], n, m, cnt;
int lowbit(int x) { return x & -x; }
void add(int x, int op)
{
for(; x <= n; x += lowbit(x))
f[x][op]++;
}
int sum(int x, int op)
{
int res = 0;
for(; x; x -= lowbit(x))
res += f[x][op];
return res;
}
int main()
{
scanf("%d%d", &n, &m);
while(m--)
{
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if(op == 1)
{
add(x, 0);
add(y, 1);
cnt++;
}
else printf("%d\n", cnt - (sum(x - 1, 1) + sum(n, 0) - sum(y, 0)));
}
return 0;
}
周四
P2146 [NOI2015] 软件包管理器(树链剖分)
比较模板的一道题了
维护树上路径和子树的信息
写完一遍肉眼查错要仔细,检查一下低级错误,变量名打错等等
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int t[N << 2], lazy[N << 2], cnt, n, m;
int id[N], d[N], siz[N], son[N], fa[N], top[N];
vector<int> g[N];
void up(int k) { t[k] = t[l(k)] + t[r(k)]; }
void update(int k, int l, int r, int x)
{
lazy[k] = x;
t[k] = x * (r - l + 1);
}
void down(int k, int l, int r)
{
if(lazy[k] == -1) return;
int m = l + r >> 1;
update(l(k), l, m, lazy[k]);
update(r(k), m + 1, r, lazy[k]);
lazy[k] = -1;
}
void change(int k, int l, int r, int L, int R, int x)
{
if(L <= l && r <= R)
{
update(k, l, r, x);
return;
}
down(k, l, r);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, x);
if(R > m) change(r(k), m + 1, r, L, R, x);
up(k);
}
int query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k, l, r);
int m = l + r >> 1, res = 0;
if(L <= m) res += query(l(k), l, m, L, R);
if(R > m) res += query(r(k), m + 1, r, L, R);
return res;
}
void dfs1(int u, int father)
{
siz[u] = 1;
d[u] = d[father] + 1;
fa[u] = father;
for(int v: g[u])
{
if(v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t)
{
id[u] = ++cnt;
top[u] = t;
if(siz[u] == 1) return;
dfs2(son[u], t);
for(int v: g[u])
{
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
void modify(int u)
{
while(top[u] != 1)
{
change(1, 1, n, id[top[u]], id[u], 1);
u = fa[top[u]];
}
change(1, 1, n, id[1], id[u], 1);
}
int ask(int u)
{
int res = 0;
while(top[u] != 1)
{
res += query(1, 1, n, id[top[u]], id[u]);
u = fa[top[u]];
}
res += query(1, 1, n, id[1], id[u]);
return res;
}
int main()
{
memset(lazy, -1, sizeof lazy);
scanf("%d", &n);
_for(i, 2, n)
{
int x; scanf("%d", &x);
x++;
g[x].push_back(i);
}
dfs1(1, 0);
dfs2(1, 1);
scanf("%d", &m);
while(m--)
{
char op[20]; int x;
scanf("%s%d", op, &x);
x++;
if(op[0] == 'i')
{
printf("%d\n", d[x] - ask(x));
modify(x);
}
else
{
printf("%d\n", query(1, 1, n, id[x], id[x] + siz[x] - 1));
change(1, 1, n, id[x], id[x] + siz[x] - 1, 0);
}
}
return 0;
}
P2486 [SDOI2011]染色(树链剖分 + 线段树)
用线段树维护左右端点的颜色以及颜色的段数
注意合并节点的时候lazy是不用理的,我开始写的时候lazy不小心写成上传了,调了巨久
lazy单独开一个数组
合并节点的时候注意边界处答案一样的话颜色的段数要减一
统计路径的答案的时候也要注意这个问题
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
struct node
{
int sum, lc, rc, lazy;
node(int a = 0, int b = 0, int c = 0, int d = 0): sum(a), lc(b), rc(c), lazy(d) {}
node operator + (const node& rhs) const
{
if(!sum) return rhs;
if(!rhs.sum) return *this;
return node(sum + rhs.sum - (rc == rhs.lc), lc, rhs.rc, 0);
}
}t[N << 2];
int id[N], d[N], siz[N], son[N], fa[N], top[N], color[N], rk[N];
int cnt, n, m;
vector<int> g[N];
void up(int k)
{
t[k] = t[l(k)] + t[r(k)];
}
void update(int k, int x)
{
t[k] = node{1, x, x, x};
}
void down(int k)
{
if(!t[k].lazy) return;
update(l(k), t[k].lazy);
update(r(k), t[k].lazy);
t[k].lazy = 0;
}
void build(int k, int l, int r)
{
if(l == r)
{
t[k] = node(1, rk[l], rk[l], 0);
return;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void change(int k, int l, int r, int L, int R, int x)
{
if(L <= l && r <= R)
{
update(k, x);
return;
}
down(k);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, x);
if(R > m) change(r(k), m + 1, r, L, R, x);
up(k);
}
node query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k);
node res;
int m = l + r >> 1;
if(L <= m) res = res + query(l(k), l, m, L, R);
if(R > m) res = res + query(r(k), m + 1, r, L, R);
return res;
}
void dfs1(int u, int father)
{
d[u] = d[father] + 1;
fa[u] = father;
siz[u] = 1;
for(int v: g[u])
{
if(v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t)
{
top[u] = t;
id[u] = ++cnt;
rk[cnt] = color[u];
if(siz[u] == 1) return;
dfs2(son[u], t);
for(int v: g[u])
{
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
void modify(int u, int v, int c)
{
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v);
change(1, 1, n, id[top[u]], id[u], c);
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v);
change(1, 1, n, id[v], id[u], c);
}
int ask(int u, int v)
{
node resu, resv;
while(top[u] != top[v])
{
if(d[top[u]] > d[top[v]])
{
resu = query(1, 1, n, id[top[u]], id[u]) + resu;
u = fa[top[u]];
}
else
{
resv = query(1, 1, n, id[top[v]], id[v]) + resv;
v = fa[top[v]];
}
}
if(d[u] < d[v])
{
resv = query(1, 1, n, id[u], id[v]) + resv;
return resv.sum + resu.sum - (resv.lc == resu.lc);
}
else
{
resu = query(1, 1, n, id[v], id[u]) + resu;
return resv.sum + resu.sum - (resv.lc == resu.lc);
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &color[i]);
_for(i, 2, n)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
build(1, 1, n);
while(m--)
{
char op[5]; int x, y, z;
scanf("%s", op);
if(op[0] == 'C')
{
scanf("%d%d%d", &x, &y, &z);
modify(x, y, z);
}
else
{
scanf("%d%d", &x, &y);
printf("%d\n", ask(x, y));
}
}
return 0;
}
P3313 [SDOI2014]旅行(树链剖分)
开很多1e5颗线段树就好了
但是要动态开点,注意要引用
用root数组存一下每颗线段树的根
树剖就先练到这
感觉树剖就是个工具把树上路径转化为线性区间,用线段树维护
这几道题变化都在于线段树的写法
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int lc[N << 5], rc[N << 5], root[N];
struct node
{
int sum, mx;
node(int a = 0, int b = 0): sum(a), mx(b) {}
node operator + (const node& rhs) const
{
return node(sum + rhs.sum, max(mx, rhs.mx));
}
}t[N << 5];
int id[N], d[N], siz[N], son[N], fa[N], top[N], a[N], w[N], c[N];
int cnt, cnt2, n, m;
vector<int> g[N];
void add(int& k, int l, int r, int x, int p)
{
if(!k) k = ++cnt2;
if(l == r)
{
t[k] = node(p, p);
return;
}
int m = l + r >> 1;
if(x <= m) add(lc[k], l, m, x, p);
else add(rc[k], m + 1, r, x, p);
t[k] = t[lc[k]] + t[rc[k]];
}
node query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
node res;
int m = l + r >> 1;
if(L <= m) res = res + query(lc[k], l, m, L, R);
if(R > m) res = res + query(rc[k], m + 1, r, L, R);
return res;
}
void dfs1(int u, int father)
{
d[u] = d[father] + 1;
fa[u] = father;
siz[u] = 1;
for(int v: g[u])
{
if(v == father) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t)
{
top[u] = t;
id[u] = ++cnt;
add(root[c[u]], 1, n, cnt, w[u]);
if(siz[u] == 1) return;
dfs2(son[u], t);
for(int v: g[u])
{
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
node ask(int u, int v)
{
int rt = root[c[u]];
node res;
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v);
res = res + query(rt, 1, n, id[top[u]], id[u]);
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v);
res = res + query(rt, 1, n, id[v], id[u]);
return res;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d%d", &w[i], &c[i]);
_for(i, 2, n)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
while(m--)
{
string s; int x, y;
cin >> s >> x >> y;
if(s == "CC")
{
add(root[c[x]], 1, n, id[x], 0);
add(root[y], 1, n, id[x], w[x]);
c[x] = y;
}
else if(s == "CW")
{
add(root[c[x]], 1, n, id[x], y);
w[x] = y;
}
else if(s == "QS") printf("%d\n", ask(x, y).sum);
else printf("%d\n", ask(x, y).mx);
}
return 0;
}
下午眼睛有点痛,先不练了
明天练练启发式合并
这篇博客里有很多题目
https://blog.csdn.net/pb122401/article/details/84648993
周五
开刷树上启发式合并
看了几篇博客终于弄懂了,树上启发式合并就是优雅的暴力,把暴力的O(n^2)优化成O(nlogn)
用树上启发式合并有三个条件 1.统计子树内的一些信息 2.没有修改 3.离线
思考这样一个问题,一棵树,每个节点有颜色,问每一颗子树里有多少不一样的颜色
一看到题就可以dfs序 + 莫队
但这是根号n的,树上启发式合并可以优化到log
先考虑暴力,暴力就是枚举每一个节点,然后对于一个节点就暴力统计其子树的颜色就好了
注意每次暴力之后要清空数组,避免影响其他子树的统计
代码是这样
void dfs2(int u, int fa)
{
for(int v: g[u])
{
if(v == fa) continue;
dfs2(v, u);
clear(v, u); //暴力消除影响
}
insert(v, u); //暴力统计当前子树答案
for(auto x: ask[u]) ans[x.id] = sum[x.k]; //记录答案
}
这看起非常地暴力,树上启发式合并就是优化这个暴力的
可以发现,最后一个儿子是可以不用清空答案的,保留下来,当前子树统计答案的时候就不用统计它的,就统计其他儿子和根节点就行了
这个儿子显然越大越好,于是选择重儿子,即子树节点数最多的儿子。
这样竟然就可以优化到nlogn,这就是树上启发式合并
代码是这样
void dfs2(int u, int fa)
{
for(int v: g[u]) //1.先往下搜 保留重儿子的答案
{
if(v == fa || v == son[u]) continue; //先遍历轻儿子
dfs2(v, u);
clear(v, u); //遍历完暴力消除影响
}
if(son[u]) dfs2(son[u], u); //再遍历重儿子,不消除影响
for(int v: g[u]) //2.以下是统计当前节点答案
{ //应该是直接insert u的,但是重儿子答案已有了,就统计剩下的就行了
if(v == fa || v == son[u]) continue; //剩下的就是轻儿子的和根节点的
insert(v, u); //暴力统计轻儿子的答案
}
cnt[c[u]]++; //加入根节点的答案
sum[cnt[c[u]]]++;
for(auto x: ask[u]) ans[x.id] = sum[x.k]; //3.记录当前的答案
}
CF375D Tree and Queries(树上启发式合并)
就是刚才讲的题加上一个统计大于等于k的颜色的数目
有一个很骚的操作,直接开一个sum数组记录,sum[i]表示大于等于i的颜色的数量,也就是直接记录答案
可以O(1)完成统计
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int son[N], siz[N], c[N], ans[N], cnt[N], sum[N], n, m;
struct node{ int id, k; };
vector<node> ask[N];
vector<int> g[N];
void dfs1(int u, int fa)
{
siz[u] = 1;
for(int v: g[u])
{
if(v == fa) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void clear(int u, int fa) //把以u为根的子树影响清除
{
sum[cnt[c[u]]]--;
cnt[c[u]]--;
for(int v: g[u])
{
if(v == fa) continue;
clear(v, u);
}
}
void insert(int u, int fa) //把以u为根的子树的答案加入
{
cnt[c[u]]++;
sum[cnt[c[u]]]++;
for(int v: g[u])
{
if(v == fa) continue;
insert(v, u);
}
}
void dfs2(int u, int fa)
{
for(int v: g[u]) //1.先往下搜 保留重儿子的答案
{
if(v == fa || v == son[u]) continue; //先遍历轻儿子
dfs2(v, u);
clear(v, u); //遍历完暴力消除影响
}
if(son[u]) dfs2(son[u], u); //再遍历重儿子,不消除影响
for(int v: g[u]) //2.以下是统计当前节点答案
{ //应该是直接insert u的,但是重儿子答案已有了,就统计剩下的就行了
if(v == fa || v == son[u]) continue; //剩下的就是轻儿子的和根节点的
insert(v, u); //暴力统计轻儿子的答案
}
cnt[c[u]]++; //加入根节点的答案
sum[cnt[c[u]]]++;
for(auto x: ask[u]) ans[x.id] = sum[x.k]; //3.记录当前的答案
}
void dfs2(int u, int fa)
{
for(int v: g[u])
{
if(v == fa) continue;
dfs2(v, u);
clear(v, u); //暴力消除影响
}
insert(v, u); //暴力统计当前子树答案
for(auto x: ask[u]) ans[x.id] = sum[x.k]; //记录答案
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &c[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
_for(i, 1, m)
{
int u, k;
scanf("%d%d", &u, &k);
ask[u].push_back(node{i, k});
}
dfs1(1, 0);
dfs2(1, 0);
_for(i, 1, m) printf("%d\n", ans[i]);
return 0;
}
E. Lomsat gelral(树上启发式合并)
这道题之前用线段树合并写过
貌似也可以用莫队写
这次用树上启发式合并写
思维量挺小的,因为真的很暴力
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int son[N], siz[N], c[N], cnt[N], mx, n;
vector<int> g[N];
ll sum, ans[N];
void dfs1(int u, int fa)
{
siz[u] = 1;
for(int v: g[u])
{
if(v == fa) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void clear(int u, int fa)
{
cnt[c[u]]--;
for(int v: g[u])
{
if(v == fa) continue;
clear(v, u);
}
}
void add(int u)
{
cnt[c[u]]++;
if(cnt[c[u]] > mx)
{
mx = cnt[c[u]];
sum = c[u];
}
else if(cnt[c[u]] == mx) sum += c[u];
}
void insert(int u, int fa)
{
add(u);
for(int v: g[u])
{
if(v == fa) continue;
insert(v, u);
}
}
void dfs2(int u, int fa)
{
for(int v: g[u])
{
if(v == fa || v == son[u]) continue;
dfs2(v, u);
mx = sum = 0;
clear(v, u);
}
if(son[u]) dfs2(son[u], u);
for(int v: g[u])
{
if(v == fa || v == son[u]) continue;
insert(v, u);
}
add(u);
ans[u] = sum;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &c[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 0);
_for(i, 1, n) printf("%lld ", ans[i]);
return 0;
}
CF570D Tree Requests
这题我用了两种做法做
一.dfs序加前缀和
这是我看到这题的第一个想法,每一个深度存一个vector
之前做过类似的题
把点的dfs序打进去,然后可以每一个深度排序,然后做26个前缀和
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e5 + 10;
int L[N], R[N], val[N], c[N], dmax, cnt, n, m;
struct record
{
int sum[26];
record() { memset(sum, 0, sizeof sum); }
};
vector<int> g[N], node[N];
vector<record> r[N];
void dfs(int u, int d)
{
dmax = max(dmax, d);
L[u] = ++cnt;
val[cnt] = c[u];
node[d].push_back(cnt);
for(int v: g[u]) dfs(v, d + 1);
R[u] = cnt;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
string s;
cin >> s;
rep(i, 0, n) c[i + 1] = s[i] - 'a';
dfs(1, 1);
_for(i, 1, dmax) sort(node[i].begin(), node[i].end());
_for(i, 1, dmax)
{
record pre;
for(int x: node[i])
{
record now = pre;
now.sum[val[x]]++;
r[i].push_back(now);
pre = now;
}
}
while(m--)
{
int u, k;
scanf("%d%d", &u, &k);
if(k > dmax)
{
puts("Yes");
continue;
}
int ll = lower_bound(node[k].begin(), node[k].end(), L[u]) - node[k].begin();
int rr = upper_bound(node[k].begin(), node[k].end(), R[u]) - node[k].begin() - 1;
if(ll > rr)
{
puts("Yes");
continue;
}
int odd = 0;
_for(i, 0, 25)
{
int cur = !ll ? r[k][rr].sum[i] : r[k][rr].sum[i] - r[k][ll - 1].sum[i];
odd += cur % 2 == 1;
}
puts(odd <= 1 ? "Yes" : "No");
}
return 0;
}
二.树上启发式合并
很暴力
用cnt数组存一下每一个深度的每一个字母出现了多少次即可
不要再每个深度存一个vector,这样常数很大,会T
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e5 + 10;
int c[N], d[N], siz[N], son[N], ans[N], cnt[N][30], n, m;
struct query { int id, k; };
vector<query> q[N];
vector<int> g[N];
void dfs1(int u, int dep)
{
siz[u] = 1;
d[u] = dep;
for(int v: g[u])
{
dfs1(v, dep + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void insert(int u)
{
cnt[d[u]][c[u]]++;
for(int v: g[u]) insert(v);
}
void clear(int u)
{
cnt[d[u]][c[u]]--;
for(int v: g[u]) clear(v);
}
void dfs2(int u)
{
for(int v: g[u])
{
if(v == son[u]) continue;
dfs2(v);
clear(v);
}
if(son[u]) dfs2(son[u]);
for(int v: g[u])
{
if(v == son[u]) continue;
insert(v);
}
cnt[d[u]][c[u]]++;
for(auto x: q[u])
{
int odd = 0;
_for(i, 0, 25) odd += (cnt[x.k][i] % 2 == 1);
ans[x.id] = (odd <= 1);
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
string s;
cin >> s;
rep(i, 0, n) c[i + 1] = s[i] - 'a';
_for(i, 1, m)
{
int u, k;
scanf("%d%d", &u, &k);
q[u].push_back(query{i, k});
}
dfs1(1, 1);
dfs2(1);
_for(i, 1, m) puts(ans[i] ? "Yes" : "No");
return 0;
}
CF208E Blood Cousins
又想到了两种做法
一.dfs序 + 二分
首先要转化一下,用树上倍增迅速找出祖先,然后就转化成询问一个子树中有多少个深度为d的点
那么可以每一个深度建一个vector,把dfs序存进去
这个子树的关系就用dfs序来表示,然后vector 里面二分就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int vis[N], root[N], up[N][25], ans[N], dmax, n, m;
int L[N], R[N], cnt;
struct node { int id, dep; };
vector<int> g[N], ve[N];
vector<node> q[N], ask[N];
void dfs(int u, int fa, int depth)
{
L[u] = ++cnt;
ve[depth].push_back(cnt);
dmax = max(dmax, depth);
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
for(auto x: q[u])
{
if(depth - x.dep < 1) continue;
int cur = u;
_for(j, 0, 20)
if(x.dep & (1 << j))
cur = up[cur][j];
ask[cur].push_back(node{x.id, depth});
}
for(int v: g[u]) dfs(v, u, depth + 1);
R[u] = cnt;
}
void dfs2(int u)
{
for(auto x: ask[u])
{
int ll = lower_bound(ve[x.dep].begin(), ve[x.dep].end(), L[u]) - ve[x.dep].begin();
int rr = upper_bound(ve[x.dep].begin(), ve[x.dep].end(), R[u]) - ve[x.dep].begin() - 1;
ans[x.id] = rr - ll;
}
ask[u].clear();
for(int v: g[u]) dfs2(v);
}
void solve(int rt)
{
cnt = dmax = 0;
dfs(rt, 0, 1);
_for(i, 1, dmax) sort(ve[i].begin(), ve[i].end());
dfs2(rt);
_for(i, 1, dmax) ve[i].clear();
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
if(!x) root[i] = 1;
else g[x].push_back(i);
}
scanf("%d", &m);
_for(i, 1, m)
{
int u, k;
scanf("%d%d", &u, &k);
q[u].push_back(node{i, k});
}
_for(i, 1, n)
if(root[i])
solve(i);
_for(i, 1, m) printf("%d ", ans[i]);
return 0;
}
二.树上启发式合并
对树上启发式合并有新的理解
前面那么做法是用dfs序来表示子树的关系
还可以用启发式合并表示查询子树内的信息,因为是直接暴力的
因此直接用一个数组维护当前不同深度有多少个点就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int vis[N], root[N], up[N][25], son[N], siz[N], ans[N], cnt[N], d[N], n, m;
struct node { int id, dep; };
vector<node> q[N], ask[N];
vector<int> g[N];
void dfs(int u, int fa, int depth)
{
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
d[u] = d[fa] + 1;
for(auto x: q[u])
{
if(depth - x.dep < 1) continue;
int cur = u;
_for(j, 0, 20)
if(x.dep & (1 << j))
cur = up[cur][j];
ask[cur].push_back(node{x.id, depth});
}
siz[u] = 1;
for(int v: g[u])
{
dfs(v, u, depth + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void insert(int u)
{
cnt[d[u]]++;
for(int v: g[u]) insert(v);
}
void clear(int u)
{
cnt[d[u]]--;
for(int v: g[u]) clear(v);
}
void dfs2(int u)
{
for(int v: g[u])
{
if(v == son[u]) continue;
dfs2(v);
clear(v);
}
if(son[u]) dfs2(son[u]);
for(int v: g[u])
{
if(v == son[u]) continue;
insert(v);
}
cnt[d[u]]++;
for(auto x: ask[u]) ans[x.id] = cnt[x.dep] - 1;
ask[u].clear();
}
void solve(int rt)
{
dfs(rt, 0, 1);
dfs2(rt);
clear(rt);
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
if(!x) root[i] = 1;
else g[x].push_back(i);
}
scanf("%d", &m);
_for(i, 1, m)
{
int u, k;
scanf("%d%d", &u, &k);
q[u].push_back(node{i, k});
}
_for(i, 1, n)
if(root[i])
solve(i);
_for(i, 1, m) printf("%d ", ans[i]);
return 0;
}
CF246E Blood Cousins Return(树上启发式合并)
这题可以有多种方法
dfs序 + 树状数组
线段树合并
树上启发式合并
我发现树上启发式合并是思维量比较小的,代码量也不大
没啥吧思维吧,就是暴力
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int root[N], son[N], siz[N], ans[N];
int cnt[N], d[N], n, m, dmax;
string name[N];
struct node { int id, k; };
vector<node> q[N];
vector<int> g[N];
map<string, int> mp[N];
void dfs(int u, int depth)
{
dmax = max(dmax, depth);
siz[u] = 1;
d[u] = depth;
for(int v: g[u])
{
dfs(v, depth + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void insert(int u)
{
if(++mp[d[u]][name[u]] == 1) cnt[d[u]]++;
for(int v: g[u]) insert(v);
}
void clear(int u)
{
if(--mp[d[u]][name[u]] == 0) cnt[d[u]]--;
for(int v: g[u]) clear(v);
}
void dfs2(int u, int depth)
{
for(int v: g[u])
{
if(v == son[u]) continue;
dfs2(v, depth + 1);
clear(v);
}
if(son[u]) dfs2(son[u], depth + 1);
for(int v: g[u])
{
if(v == son[u]) continue;
insert(v);
}
if(++mp[d[u]][name[u]] == 1) cnt[d[u]]++;
for(auto x: q[u])
if(depth + x.k <= dmax)
ans[x.id] = cnt[depth + x.k];
}
void solve(int rt)
{
dmax = 0;
dfs(rt, 1);
dfs2(rt, 1);
clear(rt);
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x;
cin >> name[i] >> x;
if(!x) root[i] = 1;
else g[x].push_back(i);
}
scanf("%d", &m);
_for(i, 1, m)
{
int u, k;
scanf("%d%d", &u, &k);
q[u].push_back(node{i, k});
}
_for(i, 1, n)
if(root[i])
solve(i);
_for(i, 1, m) printf("%d\n", ans[i]);
return 0;
}
F. Dominant Indices(树上启发式合并)
没啥可说的,暴力
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int son[N], siz[N], ans[N], cnt[N], d[N], n, mx, dep;
vector<int> g[N];
void dfs(int u, int fa)
{
siz[u] = 1;
d[u] = d[fa] + 1;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void add(int u)
{
cnt[d[u]]++;
if(cnt[d[u]] > mx)
{
mx = cnt[d[u]];
dep = d[u];
}
else if(cnt[d[u]] == mx)
dep = min(dep, d[u]);
}
void insert(int u, int fa)
{
add(u);
for(int v: g[u])
{
if(v == fa) continue;
insert(v, u);
}
}
void clear(int u, int fa)
{
cnt[d[u]]--;
for(int v: g[u])
{
if(v == fa) continue;
clear(v, u);
}
}
void dfs2(int u, int fa, int depth)
{
for(int v: g[u])
{
if(v == son[u] || v == fa) continue;
dfs2(v, u, depth + 1);
clear(v, u);
mx = dep = 0;
}
if(son[u]) dfs2(son[u], u, depth + 1);
for(int v: g[u])
{
if(v == son[u] || v == fa) continue;
insert(v, u);
}
add(u);
ans[u] = dep - depth;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
dfs2(1, 0, 1);
_for(i, 1, n) printf("%d\n", ans[i]);
return 0;
}
周日
昨天打牛客了,今天补题
King of Range(单调队列)
首先可以发现,当l递增的时候,r是不降的
所以l和r的两个端点都是递增的,所以可以用单调队列O(n)的维护最大值和最小值
用set的话多一个log会T
单调队列的写法注意一下
取队里面的元素的时候要判断是否队列为空
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N], qmin[N], qmax[N], n, m, k;
ll ans;
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &a[i]);
while(m--)
{
ans = 0;
scanf("%d", &k);
int R = 0;
int lmin = 1, rmin = 0;
int lmax = 1, rmax = 0;
_for(L, 1, n)
{
while(lmax <= rmax && qmax[lmax] < L) lmax++;
while(lmin <= rmin && qmin[lmin] < L) lmin++;
while(lmax > rmax || lmin > rmin || a[qmax[lmax]] - a[qmin[lmin]] <= k)
{
if(++R > n) break;
while(lmax <= rmax && a[qmax[rmax]] <= a[R]) rmax--;
while(lmin <= rmin && a[qmin[rmin]] >= a[R]) rmin--;
qmax[++rmax] = qmin[++rmin] = R;
}
if(R > n) break;
ans += n - R + 1;
}
printf("%lld\n", ans);
}
return 0;
}
Jewels(二分图最大权匹配)
队友想到了是二分图最大权匹配
抄了个KM模板过了
最大权匹配还可以转化为最小费用最大流来写
但是会慢一些,赛后我写然后T了
二分图的话,像最大匹配,多重匹配用网络流会快一些,但是最大权匹配就用KM算法更快
Boxes(数学期望)
策略挺好想,就是计算概率的时候我卡住了
可以设开几次箱结束,那么剩下的一定是全白或者全黑,前一个就是另一种颜色,剩下随便,就可以算出概率
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
double s[N], w[N], c, sum, ans;
int n;
int main()
{
scanf("%d%lf", &n, &c);
_for(i, 1, n)
{
scanf("%lf", &w[i]);
sum += w[i];
}
sort(w + 1, w + n + 1);
_for(i, 1, n) s[i] = s[i - 1] + w[i];
double cur = 1;
for(int i = n - 1; i >= 0; i--)
{
cur /= 2;
ans += cur * s[i];
}
printf("%.9f\n", min(ans + c, sum));
return 0;
}
Double Strings(dp + 组合数)
首先用dp求出A串中前i位,B串中前j位,有多少个相同的子序列
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]
在a[i] == b[j] 时再加上dp[i - 1][j - 1] + 1
处理完之后,当a[i] < b[j]时,第i位后面和第j位后面可以任意取相同的
这里用组合数,然后化简一下可以得到c(n, n + m) n是A串剩余的长度,m是B串剩余的长度
预处理一下阶乘和阶乘的逆元,这样就可以O(1)的算出组合数
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5000 + 10;
const int mod = 1e9 + 7;
int dp[N][N], fac[N << 1], invfac[N << 1], n, ans;
char a[N], b[N];
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int C(int n, int m)
{
return mul(fac[m], mul(invfac[n], invfac[m - n]));
}
int main()
{
invfac[0] = fac[0] = 1;
_for(i, 1, 1e4)
{
fac[i] = mul(fac[i - 1], i);
invfac[i] = binpow(fac[i], mod - 2);
}
scanf("%s%s", a + 1, b + 1);
int lena = strlen(a + 1);
int lenb = strlen(b + 1);
_for(i, 1, lena)
_for(j, 1, lenb)
{
dp[i][j] = (add(dp[i - 1][j], dp[i][j - 1]) - dp[i - 1][j - 1] + mod) % mod;
if(a[i] == b[j]) dp[i][j] = add(dp[i][j], dp[i - 1][j - 1] + 1);
}
_for(i, 1, lena)
_for(j, 1, lenb)
if(a[i] < b[j])
{
int t1 = lena - i, t2 = lenb - j;
ans = add(ans, mul(dp[i - 1][j - 1] + 1, C(t1, t1 + t2)));
}
printf("%d\n", ans);
return 0;
}
线性求逆
挺简单的,就是一个公式
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5000 + 10;
const int mod = 1e9 + 7;
int inv[N];
int main()
{
inv[0] = inv[1] = 1;
_for(i, 2, 5000) inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;
return 0;
}
上面那题可以用线性求逆优化一下
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5000 + 10;
const int mod = 1e9 + 7;
int dp[N][N], fac[N << 1], invfac[N << 1], inv[N << 1], n, ans;
char a[N], b[N];
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int C(int n, int m)
{
return mul(fac[m], mul(invfac[n], invfac[m - n]));
}
int main()
{
inv[0] = inv[1] = fac[0] = fac[1] = invfac[0] = invfac[1] = 1;
_for(i, 2, 1e4)
{
fac[i] = mul(fac[i - 1], i);
inv[i] = mul(mod - mod / i, inv[mod % i]);
invfac[i] = mul(invfac[i - 1], inv[i]);
}
scanf("%s%s", a + 1, b + 1);
int lena = strlen(a + 1);
int lenb = strlen(b + 1);
_for(i, 1, lena)
_for(j, 1, lenb)
{
dp[i][j] = (add(dp[i - 1][j], dp[i][j - 1]) - dp[i - 1][j - 1] + mod) % mod;
if(a[i] == b[j]) dp[i][j] = add(dp[i][j], dp[i - 1][j - 1] + 1);
}
_for(i, 1, lena)
_for(j, 1, lenb)
if(a[i] < b[j])
{
int t1 = lena - i, t2 = lenb - j;
ans = add(ans, mul(dp[i - 1][j - 1] + 1, C(t1, t1 + t2)));
}
printf("%d\n", ans);
return 0;
}
树的直径
补一些这些小知识点,不难
树的直接就是树上最路径的最大值
有两种方法求
一种是两次dfs
第一次dfs找到距离根节点最长的点,这个点一定是直径的一端
然后再dfs一次就可以找到另外一端
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int d[N], n, pos;
void dfs(int u, int fa)
{
for(int v: g[u])
{
if(v == fa) continue;
d[v] = d[u] + 1;
if(d[v] > d[pos]) pos = v;
dfs(v, u);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
d[pos] = 0;
dfs(pos, 0);
printf("%d\n", d[pos]);
return 0;
}