【9.22校内测试】【可持久化并查集(主席树实现)】【DP】【点双联通分量/割点】

1 build
1.1 Description
从前有一个王国,里面有n 座城市,一开始两两不连通。现在国王将进行m 次命令,命令可
能有两种,一种是在u 和v 之间修建道路,另一种是询问在第u 次命令执行之后,城市v 经过任
意多条道路所能够到达的城市的数目(包括城市v)。
1.2 Input
第一行两个整数n;m,表示城市数和命令数。
接下来m 行,每行三个整数t; x; y,若t 为1 则表示为第一种命令,t 为2 则表示为第二种命
令。为了强制要求在线算法,u = x + lastans; v = y + lastans,其中lastans 为上次查询命令的
结果(若之前没有查询则为0)。
1.3 Output
对于每个查询命令,输出一行一个整数,表示查询的结果。
1.4 Sample Input
5 5
1 1 2
2 0 1
1 1 2
1 0 4
2 2 1
1.5 Sample Output
1
3
1.6 Hint
第一次命令:连接1、2 城市。
第二次命令:查询1 号城市在第0 次命令做完之后,能够到达的点数,为1。并将lastans 设
为1 。
第三次命令:连接2、3 城市。
第四次命令:连接1、5 城市。
第五次命令:查询2 号城市在第3 次命令做完之后,能够到达的点数,为3。并将lastans 设
为3 。
1.7 Note
对于30% 的数据,1 <= n;m <= 5000。
对于另外20% 的数据,对于第i 条命令,如果是查询命令,则有u = i ?? 1。
对于100% 的数据,1 <= n;m <= 100000。保证无自环,可能有重边。

可持久化并查集可以实现(然而现在并不会,所以就写主席树咯。

怎么想到主席树呢 题目说要回到以前的版本 不就是主席树的每个版本的$root$吗!

我们想要的是利用主席树实现维护$fa$数组的操作,所以叶子节点记录$fa$,每次$find$就和并查集一样了,只不过要找$fa$在树上的位置还要在树上$Query$一次,所以每次查询是$O(log_n^2)$

【注意】合并两个并查集以及查询操作和并查集一样aaa!!都是要先找到并查集的$fa$!!一直RE的元凶QAQ

然后因为主席树上其实只有叶子节点有用 所以路上的节点就可以不赋值叻

#include<bits/stdc++.h>
using namespace std;

int n, m;

struct Node {
    Node *ls, *rs;
    int siz, fa, pos;
} pool[100005*32], *tail = pool, *root[100005], *zero;

Node *newnode() {
    Node *nd = ++ tail;
    nd -> ls = zero;
    nd -> rs = zero;
    nd -> fa = 0;
    nd -> siz = 0;
    nd -> pos = 0;
    return nd;
}

Node *build(int l, int r) {
    Node *nd = newnode();
    if(l == r) {
        nd -> fa = l;
        nd -> siz = 1;
        nd -> pos = l;
        return nd;
    }
    int mid = (l + r) >> 1;
    nd -> ls = build(l, mid);
    nd -> rs = build(mid + 1, r);
    return nd;
}

Node *Query(Node *nd, int l, int r, int x) {
    if(l == r)    return nd;
    int mid = (l + r) >> 1;
    if(x <= mid)    return Query(nd -> ls, l, mid, x);
    else    return Query(nd -> rs, mid + 1, r, x);
}

int findd(int opt, int a) {
    Node *x = Query(root[opt], 1, n, a);
    if(x -> fa != a)    return findd(opt, x -> fa);
    return x -> fa;
}

Node *Change(Node *nd, int l, int r, int pos1, int pos2) {
    Node *nnd = newnode();
//    nnd -> ls = nd -> ls; nnd -> rs = nd -> rs; nnd -> fa = nd -> fa; nnd -> siz = nd -> siz; nnd -> pos = nd -> pos;
    if(l == r) {
        nnd -> ls = nd -> ls; nnd -> rs = nd -> rs; nnd -> siz = nd -> siz; nnd -> pos = nd -> pos;
        nnd -> fa = pos2;
        return nnd;
    }
    int mid = (l + r) >> 1;
    if(pos1 <= mid) {
        nnd -> rs = nd -> rs;
        nnd -> ls = Change(nd -> ls, l, mid, pos1, pos2);
    } else {
        nnd -> ls = nd -> ls;
        nnd -> rs = Change(nd -> rs, mid + 1, r, pos1, pos2);
    }
    return nnd;
}

Node *Change2(Node *nd, int l, int r, int pos1, int d) {
    Node *nnd = newnode();
//    nnd -> ls = nd -> ls; nnd -> rs = nd -> rs; nnd -> fa = nd -> fa; nnd -> siz = nd -> siz; nnd -> pos = nd -> pos;
    if(l == r) {
        nnd -> ls = nd -> ls; nnd -> rs = nd -> rs; nnd -> fa = nd -> fa; nnd -> siz = nd -> siz; nnd -> pos = nd -> pos;
        nnd -> siz += d;
        return nnd;
    }
    int mid = (l + r) >> 1;
    if(pos1 <= mid) {
        nnd -> rs = nd -> rs;
        nnd -> ls = Change2(nd -> ls, l, mid, pos1, d);
    } else {
        nnd -> ls = nd -> ls;
        nnd -> rs = Change2(nd -> rs, mid + 1, r, pos1, d);
    }
    return nnd;
}

int main() {
    freopen("build.in", "r", stdin);
    freopen("build.out", "w", stdout);
    scanf("%d%d", &n, &m);
    int ans = 0;
    zero = ++ tail;
    zero -> ls = zero, zero -> rs = zero, zero -> fa = 0, zero -> siz = 0, zero -> pos = 0;
    root[0] = build(1, n);
    for(int i = 1; i <= m; i ++) {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        if(t == 1) {
            int u = ans + x, v = ans + y;
            int ux = findd(i-1, u), vx = findd(i-1, v);//////////////是要先找fa在fa的基础上改 
            Node *uu = Query(root[i-1], 1, n, ux);
            Node *vv = Query(root[i-1], 1, n, vx);
            if(uu -> pos == vv -> pos) {///////////如果已经在一起就不用改了 
                root[i] = root[i-1];    continue;
            }
            if(uu -> siz > vv -> siz)    swap(uu, vv);
            root[i] = Change(root[i-1], 1, n, uu -> pos, vv -> pos);
            root[i] = Change2(root[i], 1, n, vv -> pos, uu -> siz);
        } else {
            int u = ans + x, v = ans + y;
            int f = findd(u, v);
            ans = Query(root[u], 1, n, f) -> siz;
            root[i] = root[i-1];
            printf("%d\n", ans);
        }
    }
    return 0;
}

 

2 distribute
2.1 Description
在城市的道路修建完毕之后,国王希望给pure 和dirty 分配这n 座城市。
他给出了这样一个方案:pure 为初始的决定者,从1 号城市开始到n 号城市顺序决定,如果
决定者决定把当前城市分配给pure,则下一座城市分配时决定者将变为dirty,反之亦然。
现在每一座城市i 有一个评定价值vi,pure 和dirty 都希望自己获得最大的收益,收益即获
得的城市价值之和。
假定两者足够聪明,最后pure 将获得多少收益?
2.2 Input
第一行一个整数n,表示城市数。
第二行n 个整数,第i 个整数表示vi。
2.3 Output
一行一个整数,表示pure 最后获得的收益。
2.4 Sample Input
5
1 5 3 2 4
2.5 Sample Output
9
2.6 Hint
城市1 :pure 将其分配给dirty,不获得收益。
城市2 :pure 将其分配给自己,pure 获得5 的收益。
城市3 :dirty 将其分配给自己。
城市4 :pure 将其分配给dirty,不获得收益。
城市5 :pure 将其分配给自己,获得4 的收益。
2.7 Note
对于30% 的数据,n <= 20;
对于50% 的数据,n <= 1000;
对于70% 的数据,n <= 10000;
对于100% 的数据,1 <= n; vi <= 100000。

 

一道很巧妙的DP叻,夸夸$Ru笨$!!

【9.22校内测试】【可持久化并查集(主席树实现)】【DP】【点双联通分量/割点】

懒了。感觉开三维好理解啊。

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int n, v[100005];
LL pre[100005], dp[100005][2][2];

int main() {
    freopen("distribute.in", "r", stdin);
    freopen("distribute.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)    scanf("%d", &v[i]), pre[i] = pre[i-1] + v[i];
    for(int i = n; i >= 1; i --) {
        for(int now = 0; now <= 1; now ++) {
            dp[i][now][1] = pre[n] - pre[i-1] - max(dp[i+1][now ^ 1][1], dp[i+1][now ^ 1][0]);
            dp[i][now][0] = max(dp[i+1][now][0], dp[i+1][now][1]);
        }
    }
    printf("%lld", max(dp[1][0][1], dp[1][0][0]));
    return 0;
}

 

3 find
3.1 Description
然而友善的pure 和dirty 还是决定共同管理城市。现在他们拥有n 座城市,城市与城市之
间建好了m 条无向边。
他们所面临的第一个任务,就是找出所有的“圆边”。一条边被称为圆边,当且仅当存在且仅
存在一个简单环包含这条边。简单环指每个点最多只能经过一次的环。
3.2 Input
第一行两个整数n;m,表示城市数和边数。
第2 行至第m + 1 行,每行两个整数a; b,第i + 1 行表示第i 条边连接a; b 两座城市。
3.3 Output
第一行一个整数,表示圆边数目ans。
接下来一行ans 个整数,表示这些圆边的标号,按从小到大的顺序输出。
3.4 Sample Input1
5 6
1 2
2 3
3 4
4 5
1 4
3 5
3.5 Sample Output1
0
3.6 Sample Input2
5 6
1 2
2 3
3 4
4 5
1 3
3 5
3.7 Sample Output1
6
1 2 3 4 5 6
3.8 Note
对于10% 的数据,n <= 6;
对于30% 的数据,n;m <= 5000;
对于另外20% 的数据,m = n + 1,且保证图连通。
对于100% 的数据,1 <= n;m <= 100000,保证无重边无自环。

分析一下题目,发现题目要求的其实是在求点双联通分量中边数等于点数的所有边。因为我们的目的是把所有环分离出来判断它们的性质,而点双才是环的叠加,边双中可能会又环的重复,如下图:

【9.22校内测试】【可持久化并查集(主席树实现)】【DP】【点双联通分量/割点】

 

如果是找边双,这个图就会整个被放入联通块中,就不符合我们边等于点的判断条件,而我们找点双,从中间这个点开始分成两个点双,两边各自满足边数等于点数就可以判断叻。

【注意】这个不是以前单纯的有向图联通块aaa!!!存的边的数组都要开两倍!!!!!改了一下午QAQAQAQ

可以当割点和点双的模板叻

//点双联通分量模板(割点模板) 
#include<bits/stdc++.h>
using namespace std;

int n, m;

void read(int &x) {
    x = 0; char ch = getchar();
    while(ch > 9 || ch < 0)    ch = getchar();
    while(ch >= 0 && ch <= 9) {
        x = x * 10 + ch - 0;
        ch = getchar();
    }
}

struct Node {
    int u, tov, nex;
    Node(int u = 0, int tov = 0, int nex = 0) :
        u(u), tov(tov), nex(nex) { }
} Edge[200005];

int stot, h[100005];
void add(int u, int v) {
    Edge[++stot] = Node(u, v, h[u]);
    h[u] = stot;
}

vector < int > QAQ[100005];
int num[100005], color[200005], dfn[100005], low[100005], ti, tp, stk[200005], cnt, cut[100005];
void tarjan(int u, int f) {
    dfn[u] = low[u] = ++ti;
    int child = 0;
    for(int i = h[u]; i; i = Edge[i].nex) {
        int v = Edge[i].tov;
        if(v == f)    continue;
        if(!dfn[v]) {
            stk[++tp] = i;/////必须放里面避免重复入栈 
            child ++;/////判断根节点的不在同一点双的儿子个数 
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]) {/////割点的判断条件 如果当前点已经是割点 那么就要取出这个已找出来的点双 
                cut[u] = 1; //////////cut是标记割点 此题不需要用 
                cnt ++;
                int x;
                do {
                    x = stk[tp--];
                    if(!color[x]) {
                        color[x] = cnt;/////某条边在哪个点双中 
                        QAQ[cnt].push_back(x);/////点双记录边的集合
                    }
                    num[cnt] ++;///////当前点双中边的数量 
                } while(Edge[x].u != u || Edge[x].tov != v);
            }
        } else if(dfn[v] < dfn[u]) {
            low[u] = min(low[u], dfn[v]); ////必须有判断条件 因为不加就可能把无向图的反向边当成返祖边 
            stk[++tp] = i;
        }
    }
    if(f == 0 && child == 1)    cut[u] = 0;//////////如果是根节点割点需要特判 
}

int vis[100005], tmp[100005], ans[100005];
int main() {
    freopen("find.in", "r", stdin);
    freopen("find.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i ++) {
        int a, b;
        read(a); read(b);
        add(b, a); add(a, b);
    }
    for(int i = 1; i <= n; i ++)
        if(!dfn[i])    tarjan(i, 0);
    int tot = 0;
    for(int i = 1; i <= cnt; i ++) {
        int tmp = 0;///记录点数 
        for(int j = 0; j < QAQ[i].size(); j ++) {
            int id = QAQ[i][j];
            int u = Edge[id].u, v = Edge[id].tov;
            if(!vis[u])    vis[u] = 1, tmp ++;
            if(!vis[v])    vis[v] = 1, tmp ++;
        }
        if(tmp == num[i]) {/////////////只有边数等于点数时符合条件 
            for(int j = 0; j < QAQ[i].size(); j ++) {
                int id = QAQ[i][j];
                ans[++tot] = ceil(1.0 * id / 2);////////////真正的边的标号 
            }
        }
        for(int j = 0; j < QAQ[i].size(); j ++) {
            int id = QAQ[i][j];
            int u = Edge[id].u, v = Edge[id].tov;
            vis[u] = vis[v] = 0;
        }
    }
    printf("%d\n", tot);
    if(!tot)    return 0;
    if(tot)    {
        sort(ans + 1, ans + 1 + tot);
        for(int i = 1; i <= tot; i ++)    printf("%d ", ans[i]);
    }
    return 0;
}

 

【9.22校内测试】【可持久化并查集(主席树实现)】【DP】【点双联通分量/割点】

上一篇:Javascript 仿苹果导航菜单


下一篇:RequestMapping_请求参数&请求头