zoukankan      html  css  js  c++  java
  • P7520

    切了这题就 AK 省选 day2 了

    先建出支配树(提示的很明显了吧)。最终肯定是要一个 (mathrm O(nq)) 算法对吧。(n,m) 同阶,复杂度都用 (n) 表示。

    首先很显然的事情是,加边之后每个点的支配点集合只会有元素逃跑不会新加。容易发现,设 (a=mathrm{LCA}(x,y))​,如果 (operatorname{dis}(a,y)>1)​,则 (operatorname{subtree}(y))​ 的支配点集合全部改变,方案是 (1 o a o x o y)​ 绕过 (idom_y)​。简单证明一下为什么一定能找到一条这样的绕过 (idom_y)​ 的路径(虽然没啥用两千三百三十三):考虑 (1 o x)​ 的支配点路径 (1=x_0 o x_1 ocdots o x_s=x)​​,由于 (x_i o x_{i+1})​ 的所有可能点集合为 (operatorname{subtree}(x_i)-operatorname{subtree}(x_{i+1})-{x_i})​,容易发现每个点最多出现在其中一段中。那么 (idom_y)​ 出现的那段就是 (x_i=a)​,于是只需要 ban 一次 (idom_y)​,由于不支配,显然能做到。

    但是除了 (operatorname{subtree}(y)) 并不知道其它点的支配点集合改不改变……例如 (y) 能一步到达它的的兄弟 (z),这时候 (operatorname{subtree}(z)) 的支配点集合也全部改变。所以我们需要一个更普遍的结论。

    一个想法是,如果一个点支配点集合改变,是不是一定有它的最近支配点改变?(operatorname{subtree}(y)-{y})​ 全是反例……进一步发现,(z)​ 支配点集合改变当且仅当 (idom_z)​ 改变或 (idom_z)​​ 支配点集合改变,正确性显然。那其实就依然可以只判每个点的最近支配点是否改变​,然后 dfs 一波。

    那其实就是看对每个点 (z)​​,去掉 (idom_z)​ 加上 ((x,y))​ 之后 (1)​ 是否能到 (z)​。由于复杂度允许平方,那这其实就非常简单。不加 ((x,y))​ 的时候去掉 (idom_z)​ 显然不存在 (1 o z)​,但是某些其它点被 (1)​ 到达;还有一些到达 (z)​,但不包括 (1)​。如果 (x)​ 属于前者,(y)​ 属于后者,那就 win 了,否则 lose。那就平方预处理就好了。

    由于允许平方,支配树可以平方暴力建非常好写,方法是去掉每个点 dfs,支配所有不能到的点,每个点取在 dfs 树上 dfn 第二大的支配点作为最近支配点。以及理论上来说 dfs 占的复杂度是 (mathrm O(n(n+q)))​,但其实 (mathrm O(nq))​ 都是在同一棵支配树上 dfs,对这类任务简单的 dfs 有一个小 trick:直接按 dfn 正序或倒序遍历,就不用享受递归大常数和 vector 大常数了。所以 dfs 的复杂度其实只有 (mathrm O!left(n^2 ight))​,你别看这也 2e7 了,果断用链式前向星,不开 O2 AC。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int N=6010;
    int n,m,qu;
    struct addedge{
       int sz,head[N],nxt[N],val[N];
       addedge(){sz=0;memset(head,0,sizeof(head));}
       void ae(int x,int y){
       	nxt[++sz]=head[x],head[x]=sz,val[sz]=y;
       }
    }nei,rnei;
    int dfn[N],nowdfn,mng[N];
    bool cd(int x,int y){return dfn[x]<dfn[y];}
    void dfs(int x=1){
       mng[dfn[x]=++nowdfn]=x;
    //	cout<<x<<" dfn is "<<dfn[x]<<"!
    ";
       for(int i=nei.head[x];i;i=nei.nxt[i]){
       	int y=nei.val[i];
       	if(!dfn[y])dfs(y);
       }
    }
    bool vis[N];
    void dfs_ban(int x=1){
       vis[x]=true;
       for(int i=nei.head[x];i;i=nei.nxt[i]){
       	int y=nei.val[i];
       	if(!vis[y])dfs_ban(y);
       }
    }
    void rdfs(int x=1){
       vis[x]=true;
       for(int i=rnei.head[x];i;i=rnei.nxt[i]){
       	int y=rnei.val[i];
       	if(!vis[y])rdfs(y);
       }
    }
    int idom[N];
    vector<int> son[N];
    bool dp1[N][N],dp2[N][N];
    bool chg[N];
    void dfs_dom(int x=1){
       mng[dfn[x]=++nowdfn]=x;
       for(int i=0;i<son[x].size();i++)dfs_dom(son[x][i]);
    }
    int main(){
       cin>>n>>m>>qu;
       while(m--){
       	int x,y;
       	scanf("%d%d",&x,&y);
       	nei.ae(x,y),rnei.ae(y,x);
       }
       dfs();
       for(int i=1;i<=n;i++){
       	memset(vis,0,sizeof(vis));
       	vis[i]=true;if(!vis[1])dfs_ban();
       	for(int j=1;j<=n;j++)if(!vis[j])/*cout<<j<<" is dominated by "<<i<<"!
    ",*/idom[j]=max(idom[j],i,cd);
       }
    //	for(int i=1;i<=n;i++)cout<<idom[i]<<" ";puts("!");
       for(int i=2;i<=n;i++){
       	memset(vis,0,sizeof(vis));
       	vis[idom[i]]=true;if(!vis[1])dfs_ban();
       	for(int j=1;j<=n;j++)dp1[i][j]=j!=idom[i]&&vis[j];
       	memset(vis,0,sizeof(vis));
       	vis[idom[i]]=true;if(!vis[i])rdfs(i);
       	for(int j=1;j<=n;j++)dp2[i][j]=j!=idom[i]&&vis[j];
       }
       for(int i=2;i<=n;i++)son[idom[i]].pb(i);
       memset(dfn,0,sizeof(dfn)),memset(mng,0,sizeof(mng)),nowdfn=0;
       dfs_dom();
       while(qu--){
       	int x,y;
       	scanf("%d%d",&x,&y);
       	for(int i=2;i<=n;i++)chg[i]=dp1[i][x]&&dp2[i][y];
       	int ans=0;
       	for(int i=2;i<=n;i++)ans+=(chg[mng[i]]|=chg[idom[mng[i]]]);
       	cout<<ans<<"
    ";
       }
       return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    同花顺笔试碰到的一道前端编程题
    闭包会造成内存泄漏吗?
    动态添加和删除节点元素,函数封装
    58同城2018校招前端笔试题总结
    拼多多2018提前批前端笔试总结
    根据字符串字符的个数排序输出
    ES6数组对象新增方法
    【转】浅析BFC及其作用
    《具体数学》学习笔记
    KMP总结
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p7520.html
Copyright © 2011-2022 走看看