BZOJ5335 || 洛谷P4589 [TJOI2018]智力竞赛【二分+KM】【DAG最小链覆盖】

Time Limit: 10 Sec Memory Limit: 256 MB

Description

小豆报名参加智力竞赛,他带上了n个好朋友作为亲友团一块来参加比赛。
比赛规则如下:
一共有m道题目,每个入都有1次答题机会,每次答题为选择一道题目回答,在回答正确后,可以从这个题目的后续题目,直达题目答错题目或者没有后续题目。每个问题都会代表一个价值,比赛最后的参赛选手获得奖励价值等价于该选手和他的亲友团没有回答的问题中的最低价值。我们现在知道小豆和他的亲友团实力非常强,能够做出这次竞赛中的所有题目。
小豆想知道在知道题目和后续题目的条件下,他最大能获得价值是多少?

Input

第一行有两个整数n, m。(n ≤ 50, m ≤ 500)
接下来m行,第i+1行表示编号为i的题目的题目信息;
格式如下vi, ki, ai_1, ai_2, …, ai_ki 。
其中vi表示该题目的价值,ki 表示这个题目的后续,
ai_1, ai_2, …, ai_ki 表示这i 个题目的后续题目编号。
1 < n ≤ 50, 1 < m ≤ 500, vi ≤ 10^9, ki, ai_j ≤ m。

Output

如果全部题目都能答对,这输出“AK”,否则输出小豆可以获得的最高奖励价值。


题目分析

假如只考虑能不能全部答对,那么这题显然就是求DAG最小链覆盖
原图传递闭包后二分图匹配,若m-匹配数<=n+1则可以AK

如果不能AK,可以二分mid为没答出来的最小分值
检查是否可以把分值小于mid的所有题都答出来
即只保留原图分值小于mid的结点再做DAG最小链覆盖

一开始匹配写的网络流愉快地T飞了


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
  
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}
 
const int inf=2e9;
const int maxn=1010;
int n,m;
int val[maxn],mx;
int mp[maxn][maxn],edge[maxn][maxn];
int match[maxn],vis[maxn];
 
int dfs(int u)
{
    for(int v=1;v<=m;v++)
    {
        if(!mp[u][v]||vis[v]) continue;
        vis[v]=1;
        if(!match[v]||dfs(match[v]))
        {
            match[v]=u;
            return 1;
        }
    }
    return 0;
}
 
int work(int x)
{
    int res=0;
    memset(match,0,sizeof(match));
    for(int i=1;i<=m;i++)
    {
        if(val[i]>=x) continue; 
        memset(vis,0,sizeof(vis));
        if(dfs(i)) res++;
    }
    return res;
}
 
void build(int x)
{   
    for(int i=1;i<=m;++i)
    for(int j=1;j<=m;++j)
    if(val[i]<x&&val[j]<x&&i!=j) mp[i][j]=edge[i][j];
    else mp[i][j]=0;
}
 
int check(int x)
{   
    build(x); int cnt=0;
    for(int i=1;i<=m;++i) if(val[i]<x) cnt++;
    if(cnt-work(x)<=n+1) return 1;
    else return 0;
}
 
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        val[i]=read(); mx=max(mx,val[i]);
        int k=read(); edge[i][i]=1;
        while(k--) edge[i][read()]=1;
    }
     
    for(int k=1;k<=m;++k)
    for(int x=1;x<=m;++x)
    for(int y=1;y<=m;++y)
    edge[x][y]|=edge[x][k]&edge[k][y];
     
    build(inf);
    if(m-work(inf)<=n+1) printf("AK");
    else
    {
        int L=0,R=mx,ans=0;
        while(L<R)
        {
            int mid=L+R>>1;
            if(check(mid)) L=mid+1,ans=mid;
            else R=mid;
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值