2023.7.19

树上边差分
松鼠采松果
一颗“树”,树上有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
#include
#include
#include
#include
#include
#include
#include
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#define ms(x,y) memset(x,y,sizeof x);
#define YES cout<<"YES"<<'\n';
#define NO  cout<<"NO"<<'\n';
#define fr(i,z,n) for(int i = z;i <= n; i++)
#define ufr(i,n,z) for(int i = n;i >= z; i--)
typedef long long ll;
const ll maxn=2e5+10,inf = 1e18 ; 
const ll mod = 1e9 + 7;
using namespace std;
paira[maxn];
ll sum[maxn];
void solve()
{
    int n, c;
    cin >> n >> c;
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ll x;
        cin >> x;
        a[i].first = x + min(i, n - i + 1);
        a[i].second = x + i;
    }
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++){
        sum[i] = sum[i - 1] + a[i].first;
    }
    for (int i = 1; i <= n; i++)
    {
        int l = 0, r = n;//二分枚举除起始门之外走过了多少门,所以边界是0~n
        int mid;
        ll tans = 0;
        while (l <= r)
        {
            mid = (l + r) >> 1;
            ll temp = sum[mid];
            ll teans = mid + 1;//记录的是所有门的数量,所以要加上起始门
            if (mid >= i)
            {//当前门在前缀和中被统计过
                temp -= a[i].first;
                teans--;
            }
            if (temp + a[i].second <= c)
            {//当前数量的门满足限额
                tans = max(tans, teans);//维护二分中的最大值
                l = mid + 1;
            }
            else
                r = mid - 1;
        }
        ans = max(ans, tans);//维护所有门中的最大值
    }
    cout << ans << endl;
}
signed main()
{
    ios;
    int t=1;
    cin >> t;
    while (t--) {
        solve();
    }
}

你可能感兴趣的:(算法,图论,深度优先)