树上边差分
松鼠采松果
一颗“树”,树上有n个点,n-1条边(无环),有m次操作,每次操作给定两个点x,y和一个add,
在x点到y点的简单路径上所有的边都增加add
q次询问,给定x,y两个点,输出x,y之间的边权和
简单路径:路径上各个顶点不重合
树上差分(模板题)对于每次修改可以O(log(n))修改,修改完成之后O(n+m)处理数据,最终每次O(log(n))查询即可
he[x] += w; he[y] += w;he[lc] -= 2 * w;的操作只在x,y之间,dfs1为将差分变成正常的和,dfs2为求前缀和
#include
using namespace std;
const int N = 5e5 + 10;
int a[N], b[N], c[N];
int he[N];
struct Edge {
int v, next;
}e[N << 1];
int lg[N], d[N];
int head[N];
int cnt;
int f[N][22]; //f[i][j]记录i的2^j级父亲结点
void insert(int u, int v) {
e[++cnt] = Edge{ v,head[u] };
head[u] = cnt;
}
void dfs(int now, int fa) {
f[now][0] = fa;
d[now] = d[fa] + 1;
for (int i = 1; i <= lg[d[now]]; i++) {
f[now][i] = f[f[now][i - 1]][i - 1]; //2^i=2^(i-1)+2^(i-1)
}
for (int i = head[now]; i; i = e[i].next) {
if (e[i].v != fa) {
dfs(e[i].v, now);
}
}
}
int LCA(int x, int y) {
if (d[x] < d[y]) {
swap(x, y); //设x>y;
}
while (d[x] > d[y]) {
x = f[x][lg[d[x] - d[y]] - 1];
}
if (x == y)
return x;
for (int i = lg[d[x]] - 1; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
void dfs_1(int u, int fa) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa)
continue;
dfs_1(v, u);
he[u] += he[v];
}
}
void dfs_2(int u, int fa) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa)
continue;
he[v] += he[u];
dfs_2(v, u);
}
}
int main()
{
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i < n; i++) {
int u, v;
cin >> a[i] >> b[i] >> c[i];
u = a[i];
v = b[i];
insert(u, v);
insert(v, u);
}
for (int i = 1; i <= n; i++) { //常数优化
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
//cout << (lg[i - 1]) << ' ' << (1 << lg[i - 1] == i)<<'\n';
//lg[i]=lg[i/2]+1;
}
dfs(1, 0);
for (int i = 1; i < n; i++) {
int x, y, w;
x = a[i]; y = b[i]; w = c[i];
int lc = LCA(x, y); //求得最近公共祖先节点
he[x] += w; he[y] += w;
he[lc] -= 2 * w;
}
while (m--) {
int x, y, w;
cin >> x >> y >> w;
int lc = LCA(x, y);
he[x] += w; he[y] += w;
he[lc] -= 2 * w;
}
dfs_1(1, 0);
dfs_2(1, 0);
while (q--) {
int x, y;
cin >> x >> y;
int lc = LCA(x, y);
cout << he[x] + he[y] - 2 * he[lc] << '\n';
}
return 0;
}
树上点差分
P3128 [USACO15DEC] Max Flow P
树上有n个点,n-1条边(无环),有m次操作,每次操作给定两个点x,y和一个add
在x点到y点的简单路径上所有的边都增加1,求最大的点
he[x] += 1; he[y] += 1; he[lc]--; he[f[lc][0]]--;lc只减1是因为lc也在路径上
#include
using namespace std;
const int N = 5e5 + 10;
int a[N], b[N], c[N];
int he[N];
int ans = 0;
struct Edge {
int v, next;
}e[N << 1];
int lg[N], d[N];
int head[N];
int cnt;
int f[N][22]; //f[i][j]记录i的2^j级父亲结点
void insert(int u, int v) {
e[++cnt] = Edge{ v,head[u] };
head[u] = cnt;
}
void dfs(int now, int fa) {
f[now][0] = fa;
d[now] = d[fa] + 1;
for (int i = 1; i <= lg[d[now]]; i++) {
f[now][i] = f[f[now][i - 1]][i - 1]; //2^i=2^(i-1)+2^(i-1)
}
for (int i = head[now]; i; i = e[i].next) {
if (e[i].v != fa) {
dfs(e[i].v, now);
}
}
}
int LCA(int x, int y) {
if (d[x] < d[y]) {
swap(x, y); //设x>y;
}
while (d[x] > d[y]) {
x = f[x][lg[d[x] - d[y]] - 1];
}
if (x == y)
return x;
for (int i = lg[d[x]] - 1; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
void dfs_1(int u, int fa) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa)
continue;
dfs_1(v, u);
he[u] += he[v];
}
ans = max(ans, he[u]);
}
void dfs_2(int u, int fa) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa)
continue;
he[v] += he[u];
dfs_2(v, u);
}
}
int main()
{
int n, m, q;
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v;
cin >> a[i] >> b[i];
u = a[i];
v = b[i];
insert(u, v);
insert(v, u);
}
for (int i = 1; i <= n; i++) { //常数优化
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
//cout << (lg[i - 1]) << ' ' << (1 << lg[i - 1] == i)<<'\n';
//lg[i]=lg[i/2]+1;
}
dfs(1, 0);
while (m--) {
int x, y;
cin >> x >> y;
int lc = LCA(x, y);
he[x] += 1; he[y] += 1;
he[lc]--; he[f[lc][0]]--;
}
dfs_1(1, 0);
cout << ans << '\n';
return 0;
}
//race/牛客/多校/2023河南萌新联赛第(二)场:河南工业大学/卡特兰数.cpp
//求前n项卡特兰数之积的末尾0的个数
//思路:因为10为2*5,所以,统计2和5因子数,取较小值即可,又因为直接计算数值巨大,所以分开计算,对于乘法可转换因子数相加,对应的除法可以
//转换因子数相减
#include
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
typedef pair pr;
#define int long long
#define ll long long
#define fr(i,l,r) for(int i=l;i<=r;i++)
#define ufr(i,n,z) for(int i = n;i >= z; i--)
#define pb(x) push_back(x)
#define all(a) a.begin(),a.end()
#define fi first
#define se second
const int N = 5e6+10;
const int mod=998244353,inf=LONG_LONG_MAX;
int dx[]={0,0,-1,0,1},dy[]={0,-1,0,1,0};
int n,m;
int cnt2[N],cnt5[N]; //0的个数可转换成min(因子2的个数,因子5的个数)
int fun(int x,int t){
int sum=0;
while(x%t==0){
sum++;
x/=t;
}
return sum;
}
void solve(){
int n;
cin>>n;
fr(i,1,n){
cnt2[i]=cnt2[i-1];
cnt5[i]=cnt5[i-1];
cnt2[i]+=fun(4*i-2,2); //乘为+,除为-
cnt5[i]+=fun(4*i-2,5);
cnt2[i]-=fun(i+1,2);
cnt5[i]-=fun(i+1,5);
}
fr(i,1,n){
cnt2[i]+=cnt2[i-1];
cnt5[i]+=cnt5[i-1];
}
cout<>t;
while(t--) solve();
return 0;
}
P1801 黑匣子
操作1:将元素ai放入数据库,操作2:i++(起始为0)输出第i小的元素
给定n,m,n长度a数组代表ai进入数据库,m长度的b数组bi代表,bi个元素进入数据库查询
思路:利用大小根堆,大根堆负责将已经标记过的第i-1小全部入队,作为比较对象,始终保存i-1小,而小根堆
只需输出剩余的最小的就是第i小
#include
#include
using namespace std;
const int N = 2e5 + 10;
#define fr(i,l,r) for(int i=l;i<=r;i++)
priority_queue, greater>s; //小根堆
priority_queue, less>b; //大根堆
int a[N];
int u[N];
int main()
{
int n,m;
cin >> n>>m;
fr(i, 1, n) {
cin >> a[i];
}
fr(i, 1, m) {
cin >> u[i];
}
int cnt = 0;
fr(i, 1, m) {
while (cnt < u[i]) {
cnt++;
b.push(a[cnt]);
s.push(b.top());
b.pop();
}
cout << s.top() << '\n';
b.push(s.top());
s.pop();
}
return 0;
}
codeforce
contest/1791/problem/G2
思路:最开始思路为对于如果是从右端点到达传送门,一定要是从左端点使用一次传送门后,才能进行,
于是计算两次,一次提前为使用右端点而去寻找从左端点的传送门,二标记使用直接计算
但是如果是提前为使用右端点而去寻找从左端点的传送门,在第一次就走向右端点最优的传送门而左端点最优较大就会不成立
于是枚举第一次使用传送门,但是从头计算会超时,于是二分门数量+前缀和最优解
#include
#include
#include