BZOJ2851 Violet 0 极限满月

(这个题目想法太神了、、我看了好多神犇的讨论才弄懂、、

(鉴于我看的时候理解得很困难,我就讲详细点、

 

要存储下所有的元素、直接放在一个树结构或者什么里面显然是不行的、、

考虑第i个集合里的所有数字都不会超过i,我们从它所有儿子的号码向i连有向边最终构成的将会是一个DAG、、

这样我们就可以把Ai存储为第i个点的所有前驱结点了、

 

再考虑Bi的含义、

Bi就是第i个结点的所有前驱结点Bj的交集和它自己、

于是我们可以想到(其实我根本想不到),这跟ZJOI2012灾难里的灾难点有着一样的性质、

(i的灾难点的定义就是删掉该点之后i成为不可达的点、具体可以参见这题的题意、

定义Bi为i点的灾难点的集合,那么显然上面的含义可以被解释为:

第i个结点的灾难集就是它所有前驱结点的灾难集的交集和它自己、

(这还是挺好理解的,如果这个交集内的点被删掉,也就意味这i不可能从其任何一个父亲达到,所以i就不可达了、

 

那么现在问题就分成了两部分了、、建立灾难树和求若干个灾难集的并集、

 

先说建立灾难树、

顾名思义,灾难树就是对于某一个点它的所有祖先的移除都会导致其不可达的一个树、

它的构造我们可以用类似数学归纳法的方法得到、

如果我们已经构造出了到第k-1个点为止的灾难树,现在要加入第k个点、

对第k个点而言,它的所有前驱必须都不可达的情况下才会导致它不可达、

于是求k的所有前驱在灾难树中结点的lca,把第k个点挂到这个点下面、

 

再说求并集、

如果要动态做、、我目前还真不会、、

但是这个题目只要求离线算法、那么我们把每一个问题分解到它所涉及的每一个点上、

然后做DFS、

对于每个点,我们把所有它所涉及的所有问题的答案更新、

(具体方法应该不用说了、、

 

那么这题就解决了、、不得不再次感叹这个想法实在是太神了、、

 

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define pb push_back
using namespace std;
 
const int maxn=200005;
 
vector<int> son[maxn],req[maxn],linkto[maxn];
int dep[maxn],fa[maxn][20],done[maxn],ans[maxn];
int n,q,cur,csize,lca;
char fint;
 
int getlca(int x,int y){
    int p,step=0;
    if (dep[x]<dep[y])  swap(x,y);
    p=dep[x]-dep[y];
    while   (p){
        if  (p&1)    x=fa[x][step];
        p/=2;step++;
    }
    step=0;
    while   (x!=y){
        if (fa[x][step]!=fa[y][step] || (fa[x][step]==fa[y][step] && !step)){
            x=fa[x][step];
            y=fa[y][step];
            step++;
        }
        else step--;
    }
    return x;
}
 
void dfs(int now){
    int cc=req[now].size();
    for (int i=0;i<cc;i++)
        if (done[req[now][i]]==-1){
            ans[req[now][i]]+=dep[now];
            done[req[now][i]]=now;
        }
        else{
            ans[req[now][i]]+=dep[now]-dep[getlca(now,done[req[now][i]])];
            done[req[now][i]]=now;
        }
    cc=son[now].size();
    for (int i=0;i<cc;i++)
        dfs(son[now][i]);
}
 
void scan(int &x){
    fint=getchar();
    while   (fint<'0' || fint>'9')  fint=getchar();
    x=fint-'0';
    fint=getchar();
    while   (fint>='0' && fint<='9'){
        x=x*10+fint-'0';
        fint=getchar();
    }
}
 
int main(){
    scan(n);
    for (int i=1;i<=n;i++){
        scan(csize);
        for (int j=1;j<=csize;j++){
            scan(cur);
            linkto[i].pb(cur);
        }
    }
    dep[0]=1;
    for (int i=1;i<=n;i++){
        if  (!linkto[i].size()){
            fa[i][0]=0;
            dep[i]=2;
            son[0].pb(i);
            continue;
        }
        lca=linkto[i][0];
        for (int j=1;j<linkto[i].size();j++)
            lca=getlca(lca,linkto[i][j]);
        son[lca].pb(i);dep[i]=dep[lca]+1;
        cur=0;fa[i][cur]=lca;
        while (fa[i][cur]){
            fa[i][cur+1]=fa[fa[i][cur]][cur];
            cur++;
        }
    }
    scan(q);
    for (int i=1;i<=q;i++){
        scan(csize);
        for (int j=1;j<=csize;j++){
            scan(cur);
            req[cur].pb(i);
        }
    }
    memset(done,-1,sizeof done);
    dfs(0);
    for (int i=1;i<=q;i++)
        printf("%d\n",ans[i]-1);
    return 0;
}

 

posted @   JS_Shining  阅读(743)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示