╳灬兲笙疯癫°月是月大叔的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;
}