您的位置:首页 > 其它

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

2017-05-30 23:55 483 查看
题目链接

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

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

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

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

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

第i张传送卷轴的造价是

a

i

ai元,可以使用

c

i

ci次,当然每个卷轴只能造一次。

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

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

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;

v
11499
oid 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: