【刷题】BZOJ 1093 [ZJOI2007]最大半连通子图

Description

  一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意
两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。若G'=(V',E')满足V'?V,E'是E中所有跟V'有关的边,
则称G'是G的一个导出子图。若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图。若G'是G所有半连通子图
中包含节点数最多的,则称G'是G的最大半连通子图。给定一个有向图G,请求出G的最大半连通子图拥有的节点数K
,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出C对X的余数。

Input

  第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整
数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤1
00000, M ≤1000000;对于100%的数据, X ≤10^8

Output

  应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.

Sample Input

6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4

Sample Output

3
3

Solution

考虑如果我们把图缩强连通分量后,原图中的一个半联通分量就是缩点后的DAG的一条链
所以题目要求的就是DAG上的最长链的长度及方案数
这个的话拓扑dp一下就好了

#include<bits/stdc++.h>
#define ui unsigned int
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
const int MAXN=100000+10,MAXM=1000000+10;
int n,m,Mod,e,beg[MAXN],nex[MAXM],to[MAXM],Be[MAXN],cnt,DFN[MAXN],LOW[MAXN],Visit_Num,Stack[MAXN],Stack_Num,In_Stack[MAXN],sum[MAXN],f[MAXN],ans1,in[MAXN];
ll g[MAXN],ans2;
struct node{
    int u,v;
    inline bool operator < (const node &A) const {
        return u<A.u||(u==A.u&&v<A.v);
    };
    inline bool operator == (const node &A) const {
        return u==A.u&&v==A.v;
    };
};
node side[MAXM];
std::queue<int> q;
template<typename T> inline void read(T &x)
{
    T data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
    x=data*w;
}
template<typename T> inline void write(T x,char ch='\0')
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
    if(ch!='\0')putchar(ch);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
inline void insert(int x,int y)
{
    to[++e]=y;
    nex[e]=beg[x];
    beg[x]=e;
}
inline void Tarjan(int x)
{
    DFN[x]=LOW[x]=++Visit_Num;
    Stack[++Stack_Num]=x;
    In_Stack[x]=1;
    for(register int i=beg[x];i;i=nex[i])
        if(!DFN[to[i]])Tarjan(to[i]),chkmin(LOW[x],LOW[to[i]]);
        else if(In_Stack[to[i]]&&DFN[to[i]]<LOW[x])LOW[x]=DFN[to[i]];
    if(DFN[x]==LOW[x])
    {
        int temp;++cnt;
        do{
            temp=Stack[Stack_Num--];
            In_Stack[temp]=0;
            Be[temp]=cnt;
            sum[cnt]++;
        }while(temp!=x);
    }
}
inline void toposort()
{
    for(register int i=1;i<=cnt;++i)
        if(!in[i])q.push(i),f[i]=sum[i],g[i]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(register int i=beg[x];i;i=nex[i])
        {
            if(f[x]+sum[to[i]]==f[to[i]])(g[to[i]]+=g[x])%=Mod;
            else if(f[x]+sum[to[i]]>f[to[i]])f[to[i]]=f[x]+sum[to[i]],g[to[i]]=g[x];
            in[to[i]]--;
            if(!in[to[i]])q.push(to[i]);
        }
    }
}
int main()
{
    read(n);read(m);read(Mod);
    for(register int i=1;i<=m;++i)
    {
        int u,v;read(u);read(v);
        insert(u,v);
        side[i]=(node){u,v};
    }
    for(register int i=1;i<=n;++i)
        if(!DFN[i])Tarjan(i);
    e=0;memset(beg,0,sizeof(beg));
    for(register int i=1;i<=m;++i)side[i].u=Be[side[i].u],side[i].v=Be[side[i].v];
    std::sort(side+1,side+m+1);
    m=std::unique(side+1,side+m+1)-side-1;
    for(register int i=1;i<=m;++i)
        if(side[i].u!=side[i].v)insert(side[i].u,side[i].v),in[side[i].v]++;
    toposort();
    for(register int i=1;i<=cnt;++i)chkmax(ans1,f[i]);
    for(register int i=1;i<=cnt;++i)
        if(f[i]==ans1)(ans2+=g[i])%=Mod;
    printf("%d\n%lld\n",ans1,ans2);
    return 0;
}

转载于:https://www.cnblogs.com/hongyj/p/9516671.html

题目描述 有一个 $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++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值