点分治题集

点分治

淀粉质

1.Codeforces 161D🔗

计算树上距离为\(k\)的点对数量,模板题

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 5e4+7;
const int INF = 0x3f3f3f3f;
typedef long long int LL;
int n,k,root,sz[MAXN],maxsz[MAXN],dist[MAXN];
bool vis[MAXN];
LL ret;
vector<int> G[MAXN];
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void rua(int u, int f, int d, int &cnt){
    dist[cnt++] = d;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        rua(v,u,d+1,cnt);
    }
}
void calculate(int u, int d, int tag){
    int cnt = 0;
    rua(u,0,d,cnt);
    sort(dist,dist+cnt);
    int l = 0, r = cnt - 1;
    while(l<r){
        while(l<r and dist[l]+dist[r]>k) r--;
        if(l==r) break;
        ret += tag*(upper_bound(dist+l+1,dist+r+1,k-dist[l]) - lower_bound(dist+l+1,dist+r+1,k-dist[l]));
        l++;
    }
}
void divide(int u){
    vis[u] = true;
    calculate(u,0,1);
    for(int v : G[u]){
        if(vis[v]) continue;
        calculate(v,1,-1);
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
int main(){
    ____();
    cin >> n >> k;
    for(int i = 1; i < n; i++){
        int u, v; cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    cout << ret << endl;
    return 0;
}

2.洛谷P4178 Tree🔗

求树上距离小于\(k\)的路径数量
类似模板,改一下统计时的操作即可

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 4e4+7;
const int INF = 0x3f3f3f3f;
typedef long long int LL;
int n,sz[MAXN],maxsz[MAXN],root,dist[MAXN],k;
LL ret;
bool vis[MAXN];
vector<pair<int,int> > G[MAXN];

void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(auto e : G[u]){
        int v = e.first;
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void rua(int u, int f, int d, int &cnt){
    dist[cnt++] = d;
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(v==f or vis[v]) continue;
        rua(v,u,d+w,cnt);
    }
}
void calculate(int u, int d, int tag){
    int cnt = 0;
    rua(u,0,d,cnt);
    sort(dist,dist+cnt);
    int l = 0, r = cnt - 1;
    while(l<r){
        while(l<r and dist[l]+dist[r]>k) r--;
        if(l==r) break;
        ret += tag * (r-l);
        l++;
    }
}
void divide(int u){
    vis[u] = true;
    calculate(u,0,1);
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(vis[v]) continue;
        calculate(v,w,-1);
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
int main(){
    ____();
    cin >> n;
    for(int i = 1; i < n; i++){
        int u, v, w;
        cin >> u >> v >> w;
        G[u].push_back(make_pair(v,w));
        G[v].push_back(make_pair(u,w));
    }
    cin >> k;
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    cout << ret << endl;
    return 0;
}

3.洛谷P4149[IOI2011]Race🔗

存一个\(dist[]\)数组,对于每次分治的根,\(dist[i]\)表示到当前根路径边权和为\(i\)的最小边数,遍历每棵子树,计算两个距离\(d1,d2\)\(d1\)是带边权距离,\(d2\)是边的数量,每次到一个点之后,可以更新答案为\(ret = min(ret,dist[k-d1]+d2)\)
然后更新整棵子树,所有子树遍历完之后,再遍历一遍清空数组

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e5+7;
const int MAXK = 1e6+7;
const int INF = 0x3f3f3f3f;
typedef long long int LL;
int n,k,ret,sz[MAXN],maxsz[MAXN],root,dist[MAXK];
bool vis[MAXN];
vector<pair<int,int> > G[MAXN];
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(auto e : G[u]){
        int v = e.first;
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void calculate(int u, int f, int d1, int d2){
    if(d1>k) return;
    ret = min(ret,dist[k-d1]+d2);
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(v==f or vis[v]) continue;
        calculate(v,u,d1+w,d2+1);
    }
}
void update(int u, int f, int d1, int d2, int tag){
    if(d1>k) return;
    if(tag==1) dist[d1] = min(dist[d1],d2);
    else dist[d1] = INF;
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(v==f or vis[v]) continue;
        update(v,u,d1+w,d2+1,tag);
    }
}
void divide(int u){
    vis[u] = true;
    dist[0] = 0;
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(vis[v]) continue;
        calculate(v,0,w,1);
        update(v,0,w,1,1);
    }
    update(u,0,0,0,-1);
    for(auto e : G[u]){
        int v = e.first;
        if(vis[v]) continue;
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
int main(){
    ____();
    cin >> n >> k;
    for(int i = 1; i < n; i++){
        int u, v, w;
        cin >> u >> v >> w;
        u++; v++;
        G[u].push_back(make_pair(v,w));
        G[v].push_back(make_pair(u,w));
    }
    memset(dist,0x3f,sizeof(dist));
    ret = INF;
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    cout << (ret==INF?-1:ret) << endl;
    return 0;
}

4.洛谷P2634[国家集训队]聪聪可可🔗

开个数组统计一下模\(3\)余数的个数就好了
注意同一条路径\(u,v\)\(v,u\)是不同的,一个点也算一条路径

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e4+7;
const int INF = 0x3f3f3f3f;
typedef long long int LL;
LL ret;
int n,root,sz[MAXN],maxsz[MAXN],dist[3];
bool vis[MAXN];
vector<pair<int,int> > G[MAXN];
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(auto e : G[u]){
        int v = e.first;
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void calculate(int u, int f, int d){
    ret += dist[(3-d)%3];
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(v==f or vis[v]) continue;
        calculate(v,u,(d+w)%3);
    }
}
void update(int u, int f, int d, int tag){
    dist[d] += tag;
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(v==f or vis[v]) continue;
        update(v,u,(d+w)%3,tag);
    }
}
void divide(int u){
    vis[u] = true;
    dist[0] = 1;
    for(auto e : G[u]){
        int v = e.first, w = e.second;
        if(vis[v]) continue;
        calculate(v,0,w);
        update(v,0,w,1);
    }
    update(u,0,0,-1);
    for(auto e : G[u]){
        int v = e.first;
        if(vis[v]) continue;
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
int main(){
    ____();
    cin >> n;
    for(int i = 1; i < n; i++){
        int u, v, w;
        cin >> u >> v >> w;
        w %= 3;
        G[u].push_back(make_pair(v,w));
        G[v].push_back(make_pair(u,w));
    }    
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    LL num = ret * 2 + n, den = 1ll * n * n;
    LL g = __gcd(num,den);
    num /= g; den /= g;
    cout << num << '/' << den << endl;
    return 0;
}

5.洛谷P2664树上游戏🔗

神仙题 转为计算每条链答案的贡献

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
typedef long long int LL;
const int MAXN = 1e5+7;
const int INF = 0x3f3f3f3f;
int n,root,sz[MAXN],maxsz[MAXN],cnt[MAXN],c[MAXN];
LL sum,color[MAXN],ret[MAXN];
bool vis[MAXN];
vector<int> G[MAXN];
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int v : G[u]){
        if(vis[v] or v==f) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void calculate_cbt(int u, int f){
    sz[u] = 1;
    cnt[c[u]]++;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        calculate_cbt(v,u);
        sz[u] += sz[v];
    }
    if(cnt[c[u]]==1){
        color[c[u]] += sz[u];
        sum += sz[u];
    }
    cnt[c[u]]--;
}
void update(int u, int f, int tag){
    cnt[c[u]]++;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        update(v,u,tag);
    }
    if(cnt[c[u]]==1){
        color[c[u]] += sz[u] * tag;
        sum += sz[u] * tag;
    }
    cnt[c[u]]--;
}
void rua(int u, int f, int dif, LL rhssz){
    cnt[c[u]]++;
    if(cnt[c[u]]==1){
        dif++;
        sum -= color[c[u]];
    }
    ret[u] += sum + rhssz * dif;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        rua(v,u,dif,rhssz);
    }
    if(cnt[c[u]]==1) sum += color[c[u]];
    cnt[c[u]]--;
}
void clear(int u, int f){
    cnt[c[u]] = color[c[u]] = 0;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        clear(v,u);
    }
}
void solve(int u){
    calculate_cbt(u,0);
    ret[u] += sum;          // 对根节点的贡献就是所有颜色的贡献和
    for(int v : G[u]){
        if(vis[v]) continue;
        LL rhstreesz = sz[u] - sz[v];
        cnt[c[u]]++;
        color[c[u]] -= sz[v];
        sum -= sz[v];
        update(v,u,-1);
        cnt[c[u]]--;
        rua(v,u,0,rhstreesz);
        cnt[c[u]]++;
        color[c[u]] += sz[v];
        sum += sz[v];
        update(v,u,1);
        cnt[c[u]]--;
    }
    clear(u,0);
    sum = 0;
}
void divide(int u){
    solve(u);
    vis[u] = true;
    for(int v : G[u]){
        if(vis[v]) continue;
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
int main(){
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d",&c[i]);
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    for(int i = 1; i <= n; i++) printf("%lld\n",ret[i]);
    return 0;
}

6.洛谷P4292[WC2010]重建计划🔗

首先题目所给的式子是个分数规划\(k = \frac{\sum_{e\in S}v(e)}{|S|}\)
我们枚举这个二分值\(k\),可以得到\(\sum_{e\in S}v(e)-k|S|=\sum_{e\in S}(v(e)-k)\)
我们希望这个值取到最大值,即\(h(k)=max\{\sum_{e\in S}(v(e)-k)\}\)
假设答案应该是\(k*\),有三种情况:
\(\begin{cases} h(k)=0 & k=k* \\ h(k)<0 & k>k* \\ h(k)>0 & k>k* \end{cases}\)
接下来就是找最大的\(h(k)\),可以用点分治来做,每一层遍历所有子树,找到匹配的在区间内的最小值,如果用线段树来做的话要\(n\log ^3 n\),复杂度太大,仔细考虑可以发现其实可以用单调队列来做

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
const int INF = 0x3f3f3f3f;
const double eps = 3e-4;
int n,L,R,root,sz[MAXN],maxsz[MAXN],maxdep[MAXN],depth[MAXN];
bool vis[MAXN];
double dist[MAXN],d[MAXN];
vector<pair<int,double> > G[MAXN];
vector<int> Gt,vec[MAXN];
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(auto &e : G[u]){
        int v = e.first;
        if(vis[v] or v==f) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void divide(int u){
    vis[u] = true;
    Gt.push_back(u);
    for(auto &e : G[u]){
        int v = e.first;
        if(vis[v]) continue;
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
void getTree(){
    root = 0; maxsz[0] = INF;
    findroot(1,0,n);
    divide(root);
}
void bfs(int rt, double w){
    depth[rt] = 1;          //离根的深度
    d[rt] = w;              //离根的距离
    queue<int> que;
    que.push(rt);
    vis[rt] = true;
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vec[rt].push_back(u);
        for(auto &e : G[u]){
            int v = e.first;
            if(vis[v]) continue;
            d[v] = d[u] + e.second;
            depth[v] = depth[u] + 1;
            que.push(v);
            vis[v] = true;
        }
    }
    for(int node : vec[rt]) vis[node] = false;          //复原暂时使用的vis数组
    maxdep[rt] = depth[vec[rt].back()];                 //子树中深度最大值
}
bool check(double m){
    fill(begin(vis),end(vis),false);
    fill(begin(dist),end(dist),-1e9);
    for(int i = 1; i <= n; i++) for(auto &e : G[i]) e.second -= m;
    for(int rt : Gt){
        vis[rt] = true;
        vector<int> ord;            //记录子树的遍历顺序
        for(auto &e : G[rt]){
            int v = e.first;
            if(vis[v]) continue;
            bfs(v,e.second);        //按节点深度遍历
            ord.push_back(v);
        }
        // 按子树最大深度排序
        sort(ord.begin(),ord.end(),[](const int &lhs, const int &rhs){
            return maxdep[lhs] < maxdep[rhs];
        });
        int top = 0;        // 当前遍历的所有子树的最大深度
        dist[0] = 0;        // 根节点的最短距离是0
        for(int u : ord){   // 按子树最大深度从小到大遍历所有子树
            deque<int> dq;  // 单调队列
            int now = top;  // 深度指针
            for(int x : vec[u]){    // u子树中节点深度从浅到深
                int dep = depth[x];
                double dt = d[x];
                if(dep>R) break;            // 已经超过了右边界 之后的必然都不可能了,直接break
                if(dep+top<L) continue;     // 最小都达不到,continue到深度更深的
                int lt = max(L-dep,0);      // 当前其他子树可到达的深度左边界
                while(now>=lt){
                    while(!dq.empty() and dist[dq.back()]<=dist[now]) dq.pop_back();    // 单调递减的单调队列
                    dq.push_back(now);
                    now--;
                }
                while(!dq.empty() and dq.front()+dep>R) dq.pop_front();         // 超过右边界的点
                if(dist[dq.front()]+dt>=0){                 // 找到了一条符合条件的链
                    for(int i = 1; i <= n; i++) for(auto &e : G[i]) e.second += m;  // 恢复边权
                    for(int u : ord) vec[u].clear();
                    return true;
                }
            }
            for(int x : vec[u]){                            // 把当前的子树加入到使用过的子树中
                if(dist[depth[x]]==-1e9) top = depth[x];        // 之前没有出现过的深度, 更新最深深度
                dist[depth[x]] = max(dist[depth[x]],d[x]);      // 相同深度取最大值
            }
        }
        for(int u : ord) for(int v : vec[u]) dist[depth[v]] = -1e9;     //一层遍历完 恢复
        for(int u : ord) vec[u].clear();                                // 同上
    }
    for(int i = 1; i <= n; i++) for(auto &e : G[i]) e.second += m;      // 恢复边权
    return false;
}
int main(){
    scanf("%d %d %d",&n,&L,&R);
    for(int i = 1; i < n; i++){
        int u, v, w;
        scanf("%d %d %d",&u,&v,&w);
        G[u].push_back(make_pair(v,w));
        G[v].push_back(make_pair(u,w));
    }
    getTree();                          //得到dfs序下的点分树
    double l = 0, r = 1e6;
    while(r-l>eps){                     //分数规划 二分最优解
        double mid = (l + r) / 2;
        if(check(mid)) l = mid;
        else r = mid;
    }
    printf("%.3f\n",(l+r)/2);
    return 0;
}

7.HDU4812 D Tree🔗

先线性预处理逆元
用数组存每个出现过的乘积
然后点分治处理,每一层分别处理子树,先对每一棵子树查询答案,再更新数组
假设根节点到当前节点的乘积为\(val\),查询答案就是找数组中是否存在\(inv[val]*k%MOD\)

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
typedef long long int LL;
const int MAXN = 1e5+7;
const int MAXM = 1e6+7;
const int MOD = 1e6+3;
const int INF = 0x3f3f3f3f;
int n,k,root,sz[MAXN],maxsz[MAXN],mt[MAXM];
LL inv[MAXM],w[MAXN];
bool vis[MAXN];
vector<int> G[MAXN];
pair<int,int> ret;
void preprocess(){
    inv[1] = 1;
    for(int i = 2; i < MAXM; i++) inv[i] = (MOD-MOD/i) * inv[MOD%i] % MOD;
}
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[root]>maxsz[u]) root = u;
}
void calculate(int u, int f, int val){
    val = 1ll * val * w[u] % MOD;
    if(mt[inv[val]*k%MOD]!=INF)
        ret = min(ret,make_pair(min(u,mt[inv[val]*k%MOD]),max(u,mt[inv[val]*k%MOD])));
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        calculate(v,u,val);
    }
}
void update(int u, int f, int val){
    val = 1ll * val * w[u] % MOD;
    mt[val] = min(mt[val],u);
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        update(v,u,val);
    }
}
void clear(int u, int f, int val){
    val = 1ll * val * w[u] % MOD;
    mt[val] = INF;
    for(int v : G[u]){
        if(v==f or vis[v]) continue;
        clear(v,u,val);
    }
}
void divide(int u){
    vis[u] = true;
    mt[w[u]] = u;
    for(int v : G[u]){
        if(vis[v]) continue;
        calculate(v,u,1);
        update(v,u,w[u]);
    }
    clear(u,0,1);
    for(int v : G[u]){
        if(vis[v]) continue;
        root = 0; findroot(v,0,sz[v]);
        divide(root);
    }
}
void solve(){
    for(int i = 1; i <= n; i++) scanf("%I64d",&w[i]);
    for(int i = 1; i <= n; i++) G[i].clear();
    memset(vis,0,sizeof(vis));
    memset(mt,0x3f,sizeof(mt));
    ret = make_pair(INF,INF);
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    maxsz[0] = INF;
    root = 0; findroot(1,0,n);
    divide(root);
    if(ret.first==INF) puts("No solution");
    else printf("%d %d\n",ret.first,ret.second);
}
int main(){
    preprocess();
    while(scanf("%d %d",&n,&k)!=EOF) solve();
    return 0;
}

8.HDU5977 Garden of Eden 🔗

点分治+SOSDP

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
typedef long long int LL;
const int MAXN = 5e4+7;
const int N = 10;
LL ret;
int n,k,f[1<<N],g[1<<N],root,sz[MAXN],maxsz[MAXN],col[MAXN];
bool vis[MAXN];
vector<int> G[MAXN];
void findroot(int u, int fa, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int v : G[u]){
        if(v==fa or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void search(int u, int fa, int msk){
    f[msk|=col[u]]++;
    for(int v : G[u]){
        if(v==fa or vis[v]) continue;
        search(v,u,msk);
    }
}
void calculate(int u, int msk, LL tag){
    for(int i = 0; i < (1<<k); i++) f[i] = 0;
    search(u,0,msk);
    for(int i = 0; i < (1<<k); i++) g[i] = f[i];
    for(int bit = 0; bit < k; bit++) for(int msk = 0; msk < (1<<k); msk++) if(!(msk&(1<<bit))) g[msk] += g[msk^(1<<bit)];
    for(int msk = 0; msk < (1<<k); msk++) ret += tag * f[msk] * g[((1<<k)-1)^msk];
}
void divide(int u){
    vis[u] = true;
    calculate(u,0,1);
    for(int v : G[u]){
        if(vis[v]) continue;
        calculate(v,col[u],-1);
        root = 0; findroot(v,u,sz[v]);
        divide(root);
    }
}
void solve(){
    for(int i = 1; i <= n; i++) G[i].clear();
    memset(vis,0,sizeof(vis));
    for(int i = 1; i <= n; i++) scanf("%d",&col[i]);
    for(int i = 1; i <= n; i++) col[i] = (1<<(col[i]-1));
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    ret = 0;
    maxsz[0] = MAXN;
    root = 0; findroot(1,0,n);
    divide(root);
    printf("%I64d\n",ret);
}
int main(){
    while(scanf("%d %d",&n,&k)!=EOF) solve();
    return 0;
}

动态淀粉质

1. 洛谷P6329【模板】点分树 | 震波🔗

点分树上维护线段树,每次往点分树的父亲上遍历修改
查询的时候线段树加容斥原理

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
int n,m,tp[MAXN],val[MAXN],root,sz[MAXN],maxsz[MAXN],par[MAXN][18],depth[MAXN],fa[MAXN];
bool vis[MAXN];
struct Graph{
    int head[MAXN],to[MAXN<<1],nxt[MAXN<<1],tot;
    void ADDEDGE(int u, int v){
        ++tot;
        to[tot] = v;
        nxt[tot] = head[u];
        head[u] = tot;
    }
}G;
struct SegmentTree{
    int pt[MAXN],tot,sum[MAXN*25],ls[MAXN*25],rs[MAXN*25];
    void update(int pos, int x, int &rt, int L, int R){
        if(!rt) rt = ++tot;
        sum[rt] += x;
        if(L+1==R) return;
        int mid = (L + R) >> 1;
        if(pos<mid) update(pos,x,ls[rt],L,mid);
        else update(pos,x,rs[rt],mid,R);
    }
    int query(int l, int r, int rt, int L, int R){
        if(l>=R or L>=r) return 0;
        if(l<=L and R<=r) return sum[rt];
        int mid = (L + R) >> 1;
        return query(l,r,ls[rt],L,mid) + query(l,r,rs[rt],mid,R);
    }
}ST[2];
void dfs(int u, int f){
    par[u][0] = f;
    depth[u] = depth[f] + 1;
    for(int i = 1; par[u][i-1]; i++) par[u][i] = par[par[u][i-1]][i-1];
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(v==f) continue;
        dfs(v,u);
    }
}
int LCA(int u, int v){
    if(depth[u]<depth[v]) swap(u,v);
    for(int i = 0; depth[u] - depth[v]; i++) if((depth[u]-depth[v])&(1<<i)) u = par[u][i];
    if(u==v) return u;
    for(int i = 17; i >= 0; i--) if(par[u][i]!=par[v][i]){
        u = par[u][i];
        v = par[v][i];
    }
    return par[u][0];
}
inline int dist(int u, int v){ return depth[u] + depth[v] - 2 * depth[LCA(u,v)]; }
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void divide(int u, int f){
    vis[u] = true;
    fa[u] = f;
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(vis[v]) continue;
        root = 0; findroot(v,u,sz[v]);
        maxsz[root] = sz[v];
        divide(root,u);
    }
}
void modify(int u, int w){
    int rt = u;
    while(rt){
        ST[0].update(dist(u,rt),w,ST[0].pt[rt],0,maxsz[rt]);
        if(fa[rt]) ST[1].update(dist(u,fa[rt]),w,ST[1].pt[rt],0,maxsz[fa[rt]]);
        rt = fa[rt];
    }
    val[u] += w;
}
int query(int u, int k){
    int ret = 0, rt = u;
    while(rt){
        if(k-dist(rt,u)>=0) ret += ST[0].query(0,k-dist(rt,u)+1,ST[0].pt[rt],0,maxsz[rt]);
        if(fa[rt] and k-dist(fa[rt],u)) ret -= ST[1].query(0,k-dist(fa[rt],u)+1,ST[1].pt[rt],0,maxsz[fa[rt]]);
        rt = fa[rt];
    }
    return ret;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1; i <= n; i++) scanf("%d",&tp[i]);
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d %d",&u,&v);
        G.ADDEDGE(u,v); G.ADDEDGE(v,u);
    }
    dfs(1,0);
    maxsz[0] = MAXN;
    root = 0; findroot(1,0,n);
    divide(root,0);
    for(int i = 1; i <= n; i++) modify(i,tp[i]);
    int lastans = 0;
    while(m--){
        int op; scanf("%d",&op);
        if(!op){
            int x, k; scanf("%d %d",&x,&k);
            x ^= lastans; k ^= lastans;
            printf("%d\n",lastans=query(x,k));
        }
        else{
            int x, y; scanf("%d %d",&x,&y);
            x ^= lastans; y ^= lastans;
            modify(x,y-val[x]);
        }
    }
    return 0;
}

2.洛谷P2056 [ZJOI2007]捉迷藏🔗

动态淀粉质+堆

view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
int n,q,root,sz[MAXN],maxsz[MAXN],fa[MAXN],par[MAXN][20],depth[MAXN],state[MAXN],num;
bool vis[MAXN];
struct Graph{
    int to[MAXN<<1],nxt[MAXN<<1],head[MAXN],tot;
    void ADDEDGE(int u, int v){
        tot++;
        to[tot] = v; nxt[tot] = head[u];
        head[u] = tot;
    }
}G;
class Priorityqueue{
private: 
    priority_queue<int> q1,q2;
    void clr(){ while(!q2.empty() and q1.top()==q2.top()) q1.pop(), q2.pop(); }
public:
    bool empty(){ return q1.empty(); }
    void push(int x){ q1.push(x); }
    void pop(){ q1.pop(); clr(); }
    int top(){ return q1.top(); }
    void erase(int x){ q2.push(x); clr(); }
    int size(){ return q1.size() - q2.size(); }
    int sectop(){ int x = top(); pop(); int y = top(); push(x); return y; }
}QA[MAXN],QB[MAXN],Q;
void dfs(int u, int f){
    depth[u] = depth[par[u][0]=f] + 1;
    for(int i = 1; par[u][i-1]; i++) par[u][i] = par[par[u][i-1]][i-1];
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(v==f) continue;
        dfs(v,u);
    }
}
inline int LCA(int u, int v){
    if(depth[u]<depth[v]) swap(u,v);
    for(int i = 0; depth[u]-depth[v]; i++) if((depth[u]-depth[v])&(1<<i)) u = par[u][i];
    if(u==v) return u;
    for(int i = 19; i >= 0; i--) if(par[u][i]!=par[v][i]){
        u = par[u][i];
        v = par[v][i];
    }
    return par[u][0];
}
inline int dist(int u, int v){ return depth[u] + depth[v] - 2 * depth[LCA(u,v)]; }
void findroot(int u, int f, int tot){
    sz[u] = 1; maxsz[u] = 0;
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(v==f or vis[v]) continue;
        findroot(v,u,tot);
        sz[u] += sz[v];
        maxsz[u] = max(maxsz[u],sz[v]);
    }
    maxsz[u] = max(maxsz[u],tot-sz[u]);
    if(maxsz[u]<maxsz[root]) root = u;
}
void divide(int u, int f){
    fa[u] = f;
    vis[u] = true;
    for(int i = G.head[u]; i; i = G.nxt[i]){
        int v = G.to[i];
        if(vis[v]) continue;
        root = 0; findroot(v,root,sz[v]);
        divide(root,u);
    }
}
void update(int x, int tag){
    if(tag==1 and QB[x].size()>=2) Q.push(QB[x].top() + QB[x].sectop());
    if(tag==-1 and QB[x].size()>=2) Q.erase(QB[x].top() + QB[x].sectop());
}
void turn_off(int u){
    num++; state[u] = 0;
    update(u,-1);
    QB[u].push(0);
    update(u,1);
    for(int rt = u; fa[rt]; rt = fa[rt]){
        int d = dist(fa[rt],u);
        update(fa[rt],-1);
        if(!QA[rt].empty()) QB[fa[rt]].erase(QA[rt].top());
        QA[rt].push(d);
        QB[fa[rt]].push(QA[rt].top());
        update(fa[rt],1);
    }
}
void turn_on(int u){
    num--; state[u] = 1;
    update(u,-1);
    QB[u].erase(0);
    update(u,1);
    for(int rt = u; fa[rt]; rt = fa[rt]){
        int d = dist(fa[rt],u);
        update(fa[rt],-1);
        QB[fa[rt]].erase(QA[rt].top());
        QA[rt].erase(d);
        if(!QA[rt].empty()) QB[fa[rt]].push(QA[rt].top());
        update(fa[rt],1);
    }
}
inline int read(){
    int x = 0, f = 1;
    char c = getchar();
    while(c!='-'&&(c<'0'||c>'9')) c = getchar();
    if(c=='-') f = -1,c = getchar();
    while(c>='0'&&c<='9') x = x*10+c-'0', c = getchar();
    return f*x;
}
int main(){
    n = read();
    for(int i = 1; i < n; i++){
        int u = read(), v = read();
        G.ADDEDGE(u,v); G.ADDEDGE(v,u);
    }
    dfs(1,0);
    maxsz[0] = MAXN;
    root = 0; findroot(1,0,n);
    divide(root,0);
    for(int i = 1; i <= n; i++) turn_off(i);
    q = read();
    while(q--){
        char op[2];
        scanf("%s",op);
        if(op[0]=='G'){
            if(num==0) printf("-1\n");
            else if(num==1) printf("0\n");
            else printf("%d\n",Q.top());
        }
        else{
            int x = read();
            if(state[x]) turn_off(x);
            else turn_on(x);
        }
    }
    return 0;
}
posted @ 2020-06-05 21:47  _kiko  阅读(274)  评论(0编辑  收藏  举报