闇の連鎖
【题目】
传说中的暗之连锁被人们称为 Dark。Dark 是人类内心的 黑暗的产物,古今中外的勇者们都试图打倒它。经过研究, 你发现 Dark 呈现无向图的结构,图中有 N 个节点和两类边, 一类边被称为主要边,而另一类被称为附加边。Dark 有 N – 1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 M 条附加边。 你的任务是把 Dark 斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一 旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切 断。但是你的能力只能再切断 Dark 的一条附加边。现在你想要知道,一共有多少种方案可 以击败 Dark。注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断 一条附加边才算击败了 Dark。
【题意】
有N-1(节点数-1)条主要边,使节点相通。再加上M条附加边。我们只能删除一条主要边后,才能删除附加边。而我们需要算出,必须先删除一条主要边并且也必须删除一条附加边后,能把图变成不连通的两截的方法有多少种。
【题解】
本来拥有的N-1(节点数-1)条主要边,让图变成了一颗树。在这种情况下,只要删除一条主要边就能让树变成不连通的两截。而加上附加边后,条件就要变了。因为附加边的关系,使连接的两节点x,y之间的路径都多了一条可连接的边,我们可以说,两点之间的路径上的边,被新添加的边覆盖了(如图,黑色为被覆盖0次的主要边,蓝色为被覆盖1次的主要边,粉色为被覆盖两次的主要边,棕色为附加边)。
而这时,要让图变成两截只有两种方法:
- 选择覆盖边数位0的边,如上图中唯一的黑边。进行删除,然后随便删除一条附加边。那么就达到了胜利的条件。
- 选择覆盖边数位1的边,如上图中的蓝边。进行删除,然后删除让它被覆盖的那条附加边。
除此以外,不管是直接删除一条主要边就能把图变成两截,却没有附加边可以删除。或者删除一条主要边或者附加边,不能使图变成两截。都是失败的。
所以要找到总的胜利数的做法,就是利用树的最近公共祖先和树上差分进行解题。找到附加边连接的x,y的最近公共祖先,最近公共祖先的标记值-2,而x,y分别加一。以此表明,这条路径上的边,被覆盖了。而查找这条路径的被覆盖次数,只需要dfs遍历一遍树,对自己的值跟子树的值的和相加。如果最终统计为0,就表示跟父亲的连接的边没有被覆盖过,可以直接删除这条边和任意的一条附加边m(如果m等于0,那么加的也是0,这样可以顺便排除掉没有附加边可以删除的情况)。如果统计为1,就表示跟父亲连接的边被覆盖过一次,只能删除当前边和与其关联的一条边,所以为1中情况,总数加1。而最终树的dfs遍历完成后,期间加的总数,就是我们需要的结果。
时间复杂度:O(N+M)
#include<iostream> #include<cstring> #include<sstream> #include<string> #include<cstdio> #include<cctype> #include<vector> #include<queue> #include<cmath> #include<stack> #include<list> #include<set> #include<map> #include<algorithm> #define fi first #define se second #define MP make_pair #define P pair<int,int> #define PLL pair<ll,ll> #define lc (p<<1) #define rc (p<<1|1) #define MID (tree[p].l+tree[p].r)>>1 #define Sca(x) scanf("%d",&x) #define Sca2(x,y) scanf("%d%d",&x,&y) #define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z) #define Scl(x) scanf("%lld",&x) #define Scl2(x,y) scanf("%lld%lld",&x,&y) #define Scl3(x,y,z) scanf("%lld%lld%lld",&x,&y,&z) #define Pri(x) printf("%d\n",x) #define Prl(x) printf("%lld\n",x) #define For(i,x,y) for(int i=x;i<=y;i++) #define _For(i,x,y) for(int i=x;i>=y;i--) #define FAST_IO std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); #define STOP system("pause") #define ll long long const int INF=0x3f3f3f3f; const ll INFL=0x3f3f3f3f3f3f3f3f; const double Pi = acos(-1.0); using namespace std; template <class T>void tomax(T&a,T b){ a=max(a,b); } template <class T>void tomin(T&a,T b){ a=min(a,b); } const int N=1e5+5; const int M=2e5+5; int dis[N],f[N][20],d[N],head[N]; int idx=0,t=0,ans=0; int n=0,m=0; struct E{ int v; int nxt; }edge[M<<1]; void bfs(){ queue<int> q; q.push(1); f[1][0]=1; d[1]=1; while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];~i;i=edge[i].nxt){ int v=edge[i].v; if(d[v]) continue; d[v]=d[u]+1; f[v][0]=u; for(int j=1;j<=t;j++) f[v][j]=f[f[v][j-1]][j-1]; q.push(v); } } } int LCA(int u,int v){ if(d[u] > d[v]) swap(u,v); _For(i,t,0) if(d[f[v][i]] >= d[u]) v=f[v][i]; if(u==v) return u; _For(i,t,0){ if(f[u][i]!=f[v][i]){ u=f[u][i]; v=f[v][i]; } } return f[u][0]; } void get_ans(int u){ for(int i=head[u];~i;i=edge[i].nxt){ int v=edge[i].v; if(v==f[u][0]) continue; get_ans(v); dis[u]+=dis[v]; } if(u!=1 && dis[u]==0) ans+=m; else if(u!=1 && dis[u]==1) ans++; } void add_edge(int u,int v){ edge[idx]=E{v,head[u]}; head[u]=idx++; } void init(int n){ idx=0; t=(int)(log(n)/log(2)) + 1; For(i,1,n){ head[i]=-1; } } int main(){ Sca2(n,m); init(n); For(i,1,n-1){ int u,v; Sca2(u,v); add_edge(u,v); add_edge(v,u); } bfs(); For(i,1,m){ int u,v; Sca2(u,v); dis[u]++; dis[v]++; dis[LCA(u,v)]-=2; } get_ans(1); printf("%d\n",ans); }