小苯点兵点将
小苯的好数
完全平方数的性质 : 完全平方数等价于约数个数一定是奇数个。
对于偶数一定合法 ;对于奇数,根据上述性质,不是完全平方数的一定不满足,是完全平方数的一定满足。
证明 : 奇数的因子一定是 奇数 × 奇数, 因子是成对出现的, 所以只有完全平方数有奇数个因子。
小苯的 ovoovo
设 dp[i][j] 表示考虑前 i 个字母, 使用 j 次修改机会, 最多得到多少个不相交的 ovo 子串。
显然考虑最后三个字符修改为 ovo 的使用修改次数即可。
即 dp[i][j] = max(dp[i][j], dp[i - 3][j - cnt] + 1)。
注意 f[i][j] 要继承 f[i - 1][j] 的最大值。
小苯的水瓶
属于比较显然的二分。
小苯的旅行计划
树上差分板子。
我用的树链剖分。
#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 1E5 + 10;
vector<int> e[N];
// vector<pair<int,int>>ne[N];
#define lc u << 1
#define rc u << 1 | 1
int n, m, r;
int sz[N], son[N], dep[N], fa[N];
int w[N], nw[N], id[N], top[N], tim = 0;
void dfs1(int u, int father){
sz[u] = 1, dep[u] = dep[father] + 1, fa[u] = father;
for(int v : e[u]){
if(v == father) continue ;
dfs1(v, u);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
// id : 存 u 剖分之后的编号
// nw : 新编号在树中对应的权值
void dfs2(int u, int tp){
top[u] = tp, id[u] = ++ tim, nw[tim] = w[u];
if(!son[u]) return ;
dfs2(son[u], tp);
for(int v : e[u]){
if(v == fa[u] || v == son[u]) continue ;
dfs2(v, v);
}
}
struct tree{
int l, r;
int sum, add;
}tr[N * 4];
void pushup(int u){
tr[u].sum = tr[lc].sum + tr[rc].sum;
}
void pushdown(int u){
if(tr[u].add){
tr[lc].sum += tr[u].add * (tr[lc].r - tr[lc].l + 1);
tr[rc].sum += tr[u].add * (tr[rc].r - tr[rc].l + 1);
tr[lc].add += tr[u].add;
tr[rc].add += tr[u].add;
tr[u].add = 0;
}
}
void build(int u, int l, int r){
tr[u] = {l, r, nw[l], 0};
if(l == r) return ;
int mid = l + r >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(u);
}
void update(int u, int l, int r, int k){
if(l <= tr[u].l && r >= tr[u].r){
tr[u].sum += k * (tr[u].r - tr[u].l + 1);
tr[u].add += k;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid) update(lc, l, r, k);
if(r > mid) update(rc, l, r, k);
pushup(u);
}
// 将树从 u 到 v 节点最短路径上的所有节点的值加上 k
void update_path(int u, int v, int k){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
update(1, id[v], id[u], k);
}
// 以 u 为根的子树的所有节点的值加 k
// 以 u 为根的子树的 dfs 序是连续的
void update_tree(int u, int k){
update(1, id[u], id[u] + sz[u] - 1, k);
}
int ask(int u, int l, int r){
if(l <= tr[u].l && r >= tr[u].r){
return tr[u].sum;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
int res = 0;
if(l <= mid) res += ask(lc, l, r);
if(r > mid) res += ask(rc, l, r);
pushup(u);
return res;
}
// 查询树从 u 到 v 节点最短路径上的所有节点的值之和
int ask_path(int u, int v){
int res = 0;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
res += ask(1, id[top[u]], id[u]);
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
res += ask(1, id[v], id[u]);
return res;
}
// 查询以 u 为根的子树的节点值之和
int ask_tree(int u){
return ask(1, id[u], id[u] + sz[u] - 1);
}
int lca(int u, int v){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
void init(){
cin >> n >> m;
r = 1;
// n 个结点的权值
for(int i = 1; i <= n; i ++){
cin >> w[i];
}
// 读入树
for(int i = 1; i < n; i ++){
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
// 从根节点 dfs
dfs1(r, 0);
dfs2(r, r);
build(1, 1, n); // 用链建线段树
}
// int sz[N], son[N], dep[N], fa[N];
// int w[N], nw[N], id[N], top[N], tim = 0;
// int ddd[N];
// void dfs(int u,int ff) {
// for(auto [v,w] : ne[u]) {
// if(v==ff)continue;
// dfs(v,u);
// ddd[v] = ddd[u] + 1;
// }
// }
void solve(){
cin >> n >> m;
r = 1;
for(int i = 1; i <= n; i ++){
e[i].clear();
sz[i] = son[i] = dep[i] = fa[i] = w[i] = nw[i] = id[i] = top[i] = tim = 0;// ddd[i]=0;
}
vector<tuple<int,int,int>>tmp;
for(int i = 1; i < n; i ++){
int u, v, w;
cin >> u >> v >> w;
// ne[u].push_back({v,w});
// ne[v].push_back({u,w});
tmp.push_back({u,v,w});
}
// dfs(1,0);
// for(auto[u,v,ww]:tmp){
// e[u].push_back(v);
// e[v].push_back(u);
// if(ddd[u] > ddd[v]) {
// w[u] = ww;
// }
// else{
// w[v] = ww;
// }
// }
for(int i = 1; i <= n; i ++){
// cout<<w[i]<<'\n';
w[i]=0;
}
// 从根节点 dfs
dfs1(r, 0);
dfs2(r, r);
build(1, 1, n); // 用链建线段树
for(int i = 1; i <= m; i ++){
int a,b;
cin >> a >> b;
update_path(a, b, 1);
int l = lca(a,b);
update_path(l,l,-1);
}
int res = 0;
int mx = 0;
for(auto[u,v,ww]:tmp){
int peo = ask_path(u,v);
int l = lca(u, v);
peo -= ask_path(l,l);
res += ww*peo;
mx = max(mx, ww*peo);
// cout << u << ' ' << v << ' ' << peo << '\n';
// e[u].push_back(v);
// e[v].push_back(u);
// if(ddd[u] > ddd[v]) {
// w[u] = ww;
// }
// else{
// w[v] = ww;
// }
}
cout<<res-mx;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;cin>>t;while(t--)solve();
return 0;
}
// 将每个边权映射为点权,具体映射规则为 : 选择一条边两端点中,**较深的那一个** 。
// 此时如果要查询 $u$ 到 $v$ 最短路径的权值之和,会多查询 $lca(u,v)$ 的点权,删去即可 ;
// 修改的话,不修改 $lca(u,v)$ 即可。