洛谷 2272 & bzoj 1093 [ZJOI2007]最大半连通子图 题解(强连通分量,思维)

该博客介绍了如何解决最大半连通子图问题,通过分析题意,将问题转化为寻找强连通分量并进行缩点。将原图转换为DAG后,问题简化为寻找最长链。利用动态规划方法求解,并在求解过程中记录相同解的数量。

原题链接:
洛谷
bzoj

题意简述

给定一个图,点数1e5,边数1e6。定义一个“半联通”的子图为:对于任意点 ( u , v ) (u,v) (u,v),满足: u u u能到 v v v v v v能到 u u u。求最大的半联通子图点数,并求出满足像这样最大的解有多少个。

思路

(这个题是我做志愿者的时候做的。。。背着老师偷偷做题真tm爽。。。)

看起来这个“半联通”和“强连通”有点像,只不过一个是,一个是。但就是因为这个改成,就不好做了。

但是我们想一想,一个强连通分量是不是一个半联通子图?(woc我个智障想了半天才想到这一点)是的!
所以我们把强连通分量缩点即珂。由于要求最大点数,所以缩点时要维护这个分量里的点数。
然后图就变成了一个 D A G DAG DAG。在 D A G DAG DAG里就好做很多了。而且有一个明显的结论: D A G DAG DAG中的半联通子图一定是一个链。

如果你不知道为啥,看这里的解释
blog1.jpg
此时显然我们选择了两条链。但是这两个红色的点就无法到达了,两边都不能到达,所以连“半联通”也不算。所以我们只能选择一条链。

然后就变成了带权的最长链问题了。 d p dp dp即珂。
然后求个数?套路的,我们都 d p dp dp了,在能取到相同的 d p dp dp值时,用 + = += +=记录个数即珂。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 100100
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define CLS(x) memset(x,0,sizeof(x))
 
    class Graph
    {
        public:
            int *head;
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }*Ed;
            int MAX;
 
            void SET(int len)
            {
                MAX=(len<<1)+100;
                Ed=new Edge[MAX];
                head=new int[MAX];
 
                memset(head,-1,sizeof(int)*MAX);
                memset(Ed,-1,sizeof(Edge)*MAX);
                EdgeCount=0;
            }
            void clear()
            {
                memset(head,-1,sizeof(int)*MAX);
                memset(Ed,-1,sizeof(Edge)*MAX);
                EdgeCount=0;
            }
 
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }
            void Add2(int u,int v,int w)
            {
                AddEdge(u,v,w);AddEdge(v,u,w);
            }
 
            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
 
    }G;
    struct Edge
    {
        int u,v,w;
    }E[1001000];
    bool operator<(Edge b,Edge a)
    {
        return make_pair(a.u,a.v)<make_pair(b.u,b.v);
    }
    bool operator==(Edge a,Edge b){return !(a<b) and !(b<a);}
    int ecnt=0;void AddEd(int u,int v,int w){E[++ecnt]=(Edge){u,v,w};}
 
    int n,m,x;
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Input()
    {
        R1(n),R1(m),R1(x);
        G.SET(1000000);
        CLS(E);
        for(int i=1;i<=m;++i)
        {
            int u,v;
            R1(u),R1(v);
            G.AddEdge(u,v,1);
            AddEd(u,v,1);
        }
    }
 
    //normal
    int DFSid[N],low[N],time=0;
    bool In[N];stack<int>STK;
    int SCCid[N],SCCcnt;
    //addition
    int size[N];
    void Tarjan(int u)
    {
        DFSid[u]=low[u]=++time;
        In[u]=1;STK.push(u);
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (!DFSid[v])
            {
                Tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if (In[v])
            {
                low[u]=min(low[u],low[v]);
            }
        }
        if (DFSid[u]==low[u])
        {
            ++SCCcnt;
            int top;
            do
            {
                top=STK.top();STK.pop();
                In[top]=0;
                SCCid[top]=SCCcnt;
 
                ++size[SCCcnt];
            } while (top!=u);
        }
    }
    int dp[N][2];
    //dp[i][0]:max length
    //dp[i][1]:how many dp[i][0] exists
    queue<int>Q,EmptyQ;
    int indeg[N];
    void Topo_DP()
    {
        Q=EmptyQ;
        for(int i=1;i<=SCCcnt;++i)
        {
            if (!indeg[i])
            {
                Q.push(i);
                dp[i][0]=size[i];
                dp[i][1]=1;
            }
        }
        while(!Q.empty())
        {
            int u=Q.front();Q.pop();
            for(int i=G.Start(u);~i;i=G.Next(i))
            {
                int v=G.To(i);
 
                --indeg[v];
                if (indeg[v]==0)
                {
                    Q.push(v);
                }
 
                if (dp[u][0]+size[v]==dp[v][0])
                {
                    dp[v][1]+=dp[u][1];
                    dp[v][1]%=x;
                }
                else if (dp[u][0]+size[v]>dp[v][0])
                {
                    dp[v][1]=dp[u][1];
                    dp[v][0]=dp[u][0]+size[v];
                }
            }
        }
 
 
        int pos=0;
        int cnt=0;
        for(int i=1;i<=SCCcnt;++i)
        {
            if (dp[i][0]>dp[pos][0]) pos=i,cnt=dp[i][1];
            else if (dp[i][0]==dp[pos][0]) cnt+=dp[i][1],cnt%=x;
        }
        printf("%d\n%d\n",dp[pos][0],cnt);
    }
    void Soviet()
    {
        for(int i=1;i<=n;++i)
        {
            if (!DFSid[i]) Tarjan(i);
        }
 
        CLS(E);ecnt=0;
        for(int i=1;i<=n;++i)
        {
            for(int j=G.Start(i);~j;j=G.Next(j))
            {
                int v=G.To(j);
                if (SCCid[i]!=SCCid[v])
                {
                    AddEd(SCCid[i],SCCid[v],1);
 
                }
            }
        }
        sort(E+1,E+ecnt+1);
        G.clear();
        for(int i=1;i<=ecnt;++i)
        {
            if (!(E[i]==E[i-1]))
            {
                G.AddEdge(E[i].u,E[i].v,1);
                ++indeg[E[i].v];
            }
        }
        Topo_DP();
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("download_testdatas\\semi3.in","r",stdin);
//            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的,问题就转化为了在这个中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿,如果它的两个儿都被遍历过了,那么我们就可以计算出从它的左儿到它的右儿的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值