喵哈哈村的魔法大师 【SCC+缩点+floyd求传递闭包+最大匹配+01 背包】

题目链接

╳灬兲笙疯癫°月是月大叔的ID,他是一个掌握着429种魔法的魔法大师,最擅长的技能就是搞事,今天他又要开始搞事了。

现在有一个由n个点,m条边组成的有向图,每个点上面都有一个魔法家族。

现在月大叔为了通知别人参加他的魔法大会,于是他就要拜访所有的家族。

由于是有向图,所以可能存在过去,就无法回来的情况,但是没有关系。

月大叔可以创造传送卷轴,传送卷轴可以传送到任意一个点。

第i张传送卷轴的造价是
a
i
ai元,可以使用
c
i
ci次,当然每个卷轴只能造一次。

现在问你月大叔最少需要多少钱,就可以拜访所有的人了。

注意一开始月大叔就得使用一次传送魔法噢。

INPUT
输入第一行包含一个正整数
t
(
1

t

100
)
t(1≤t≤100) ,表示有t组数据

对于每组数据:

第一行三个整数n,m,k。表示有
n
(
1

n

500
)
n(1≤n≤500)个点,
m
(
1

m

n

(
n

1
)
/
2
)
m(1≤m≤n∗(n−1)/2)条边,
k
(
1

k

500
)
k(1≤k≤500)种魔法卷轴

接下来m行,每行两个整数u,v,表示u到v之间有一条有向边,可能存在自环、重边的情况。

接下来k行,每行两个整数
a
i
(
1

a
i

500
)
ai(1≤ai≤500),
c
i
(
1

c
i

500
)
ci(1≤ci≤500),分别表示卷轴的造价和使用次数。
OUTPUT
对于每组测试数据的询问,输出有多少对即可。
对于每组测试数据的询问,输出最少花多少钱即可
如果无解的话,请输出”-1”
SAMPLE INPUT
1
5 5 3
5 2
2 5
1 4
1 1
2 1
4 2
4 1
4 3
SAMPLE OUTPUT
4
SOLUTION
玲珑杯”ACM比赛 Round #15

错的思路,scc+缩点+统计入度为0的个数+01背包,其实是不对的,对与DAG图的理解还是太差了(主要还是太混乱了 ),才导致的。 仔细看下面的,就会明白 为什么会错了。

前提知识 务必理解
1scc+缩点 【模板】

2最小路径覆盖【例题解释】 // 最好 和本题 对比 ,搞清楚什么时候是 可相交的最小路径覆盖,什么时候是 不可相交的最小路径覆盖。 一般图的最小路径覆盖求的是不相交的(题目上会有说明,不会走重复点之类的提示)。。而这道题不是,没有强调不能够重复,所以我们要用floyd求一下才可以。,

3floyd 求传递闭包 例题
floyd 求传递闭包作用 ,由于我也是第一次接触这个(虽然离散课刚结课Σ( ° △ °|||)︴) 。就找了个感觉还算典型的例题来理解下;

对DAG 图的理解
1建议 将这道题和下面这道题仔细对比一下
例题点我
DAG 图中,从一个入度为0 的点 确实是可以到达 与其相连的所有点,但是这道题目是,拜访一遍就是确确实实的走一遍(选择一个方向的话,走到头就走不动了,不能够返回原入度为0的点),而上面的例题是 从入度为0的点 可以向好多方向打电话,相当于走了好几条路。 所以这是不同的。。
所以就引出了 最小路径覆盖(最大匹配求解)这个概念。==》走 互不相交的路径,问最少要走几条才可以走完全部的点。
是不是和这道题已经很接近了,但还不是,这道题题的意思就是 可以走重复的路径,即是要 求可以相交的型的 最小路径覆盖(所以这里要先用Floyd求传递闭包,然后在求解 最大匹配)。最后再来一下 01 背包求一下就行。

先来个dalao的代码

#include <bits/stdc++.h>

using namespace std;
const int MAXN=130100;
struct node
{
    int v;
    int nxt;
};

node edge[MAXN];//边数
int head[550];
int n,m,k;
int Stop,Bcnt,Dindex;//栈头,强通块数,时间戳
int DFN[550],LOW[550];//首时间戳,最近回溯点(根)
int Stap[550];//答案栈
int instack[550];//是否在栈中
int Belong[550];//这个点属于第几个强连通块(点)
int cnt=0;
void add_edge(int u,int v)
{
    edge[cnt].v=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
    cnt++;
}
void tarjan(int i)
{
    int j;
    DFN[i]=LOW[i]=++Dindex;
    instack[i]=1;
    Stap[++Stop]=i;
    for (int e=head[i]; e!=-1; e=edge[e].nxt)
    {
        j=edge[e].v;
        if (!DFN[j])//儿子没遍历
        {
            tarjan(j);//遍历
            if (LOW[j]<LOW[i])//如果儿子已经形成环
                LOW[i]=LOW[j];//父亲也要在回溯的时候进入环
        }
        else if (instack[j]&&DFN[j]<LOW[i])//邻接的在栈里,所以是大大
            LOW[i]=DFN[j];//把这个点归到大大那
    }
    if (DFN[i]==LOW[i])//这个点的根是自己
    {
        Bcnt++;//多了一个强连通分量
        do
        {
            j=Stap[Stop--];//退栈
            instack[j]=0;//标记
            Belong[j]=Bcnt;//标记
        }
        while (j!=i);
    }
}
void solve()
{
    int i;
    Stop=Bcnt=Dindex=0;//栈头,强通块数,时间戳
    memset(DFN,0,sizeof(DFN));
    memset(instack,0,sizeof(instack));
    for (int i=1; i<=n; i++)
        if (!DFN[i])
            tarjan(i);
}
struct bian
{
    int f,t;
} st[MAXN];
int aa[550];
int bb[550];
int dp[3050];
bool mp[550][550];
bool vis[550];
int link[550];
bool dfs(int u)
{
    for(int i=1; i<=Bcnt; i++)
    {
        if(i==u)continue;
        if(mp[u][i]&&!vis[i])
        {
            vis[i]=1;
            if(link[i]==-1||dfs(link[i]))
            {
                link[i]=u;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    int T;
    scanf ("%d",&T);
    while (T--)
    {
        scanf ("%d%d%d",&n,&m,&k);
        memset(head,-1,sizeof(head));
        cnt = 0;
        for (int i=0; i<m; i++)
        {
            scanf ("%d%d",&st[i].f,&st[i].t);
            add_edge(st[i].f,st[i].t);
        }
        solve();
        memset(mp,0,sizeof(mp));
        for (int i=0; i<m; i++)
        {
            if (Belong[st[i].f]!=Belong[st[i].t])
            {
                int u = Belong[st[i].f];
                int v = Belong[st[i].t];
                mp[u][v]=true;
            }
        }
        for(int k=1;k<=Bcnt;++k){
           for(int i=1;i<=Bcnt;++i){
             for(int j=1;j<=Bcnt;++j){
                 if(mp[i][k]&&mp[k][j])
                   mp[i][j]=true;
             }
           }
        }

        memset(link,-1,sizeof(link));
        int ccnt = 0;
        for(int i=1; i<=Bcnt; i++)
        {
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ccnt++;
        }

        int need=Bcnt-ccnt,sum = 0;
        //printf("%d\n",need);
        for (int i=0; i<k; i++) scanf ("%d%d",&aa[i],&bb[i]),sum+=bb[i];
        if(sum<need){
            printf("-1\n");
            continue;
        }

        for (int i=0; i<=1010; i++) dp[i]=0x3f3f3f3f;
        dp[0]=0;
        int ans=0x3f3f3f3f;
        for (int i=0; i<k; i++)
        {
            for (int j=1010; j>=0; j--)
            {
                if (j-bb[i]<0) break;
                dp[j]=min(dp[j],dp[j-bb[i]]+aa[i]);
                if (j>=need) ans=min(dp[j],ans);
            }
        }
        printf ("%d\n",ans);
    }
    return 0;
}

我的代码

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define LL long long
const int M = 1000;
const int MAXM = 230100;
const double PI = acos(-1.0);
const double eps = 1e-8;
inline int read(){
    int x=0,f=1; char ch=getchar();
    while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return x*f;
}
/*--------------------------------------*/
struct Edge {
    int from,to,next;
}edge[MAXM];
int head[M],top;
int low[M],dfn[M];
int dfs_clock;
int Instack[M];
stack<int>S;
int sccno[M],scc_cnt; 
int n,m,K;

void init()
{
    top=0;
    memset(head,-1,sizeof(head));
    while(!S.empty()) S.pop();  
} 
void addedge(int a,int b)   // 重边or自环 求scc的时候,不影响,不必要去除 
{
    /*if(a==b) return;  
    int i;
    for(int i=head[a];i!=-1;i=edge[i].next)
    if(edge[i].to==b) return;*/
    Edge e={a,b,head[a]};
    edge[top]=e;head[a]=top++;
}
void getmap()
{
    int i,j,a,b;
    while(m--)
    {
       scanf("%d%d",&a,&b);
        addedge(a,b);
    }
}
void tarjan(int now)
{
    int nexts;
    dfn[now]=low[now]=++dfs_clock;
    S.push(now);Instack[now]=1;
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        Edge e=edge[i];
        if(!dfn[e.to])  
        {
            tarjan(e.to);
            low[now]=min(low[e.to],low[now]);
        }
        else if(Instack[e.to]) 
        low[now]= min(low[now],dfn[e.to]);
    }
    if(low[now]==dfn[now])
    {
        scc_cnt++;
        for(;;)
        {
            nexts=S.top();S.pop();
            sccno[nexts]=scc_cnt;
            Instack[nexts]=0;
            if(nexts==now) break;
        }
    }
}
void find_cut(int le,int ri)
{
    int i,j;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(sccno,0,sizeof(sccno));
    memset(Instack,0,sizeof(Instack));
    scc_cnt=dfs_clock=0;
    for(i=le;i<=ri;i++)
    if(!dfn[i]) tarjan(i);
 } 

int mp[M][M]; //  邻接矩阵存缩点后的图 
void suodian()
{
    memset(mp,0,sizeof(mp));
    for(int i=0;i<top;i++)  // 一个scc表示一个点来进行缩点 
    {
        int now=sccno[edge[i].from];
        int nexts=sccno[edge[i].to];
        if(now!=nexts) 
            mp[now][nexts]=1;
    }
}

int pipei[M],vis[M];
int find(int x)
{
    for(int i=1;i<=scc_cnt;i++)
    {
        if(!vis[i]&&mp[x][i])
        {
            vis[i]=1;
            if(pipei[i]==-1||find(pipei[i]))
            {
                pipei[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
        // 传递闭包
void floyd()  
{
     for(int k=1;k<=scc_cnt;++k){
           for(int i=1;i<=scc_cnt;++i){
             for(int j=1;j<=scc_cnt;++j){
                  mp[i][j]= mp[i][j]||mp[i][k]&&mp[k][j];
             }
           }
        }
}
int cost[M],num[M];
int dp[4000];///*****
void solve()
{
    // 最大匹配 
    int ge=0;
    memset(pipei,-1,sizeof(pipei));
    for(int i=1;i<=scc_cnt;i++)
    {
        memset(vis,0,sizeof(vis));
        ge+=find(i);
    }
    int need=scc_cnt-ge;  //最小路径覆盖==顶点数--最大匹配数 

    int sum=0;
    for (int i=0; i<K; i++) 
    {
        scanf ("%d%d",&cost[i],&num[i]);
        sum+=num[i];
    }
        if(sum<need){ // 传送 魔法不够 
            printf("-1\n");
        }
        else 
        {
             // 01 背包 
        for (int i=0; i<=1010; i++) dp[i]=inf;
        dp[0]=0;
        int ans=inf;
        for (int i=0; i<K; i++)
        {
            for (int j=1010; j>=0; j--)
            {
                if (j-num[i]<0) break;
                dp[j]=min(dp[j],dp[j-num[i]]+cost[i]);
                if (j>=need) ans=min(dp[j],ans);
            }
        }
        printf ("%d\n",ans);
        }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&K);
        init();
        getmap();
        find_cut(1,n);
        suodian();
        floyd();
        solve();
     }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值