【TJOI2018】 智力竞赛
智力竞赛
题意
小豆报名参加智力竞赛,他带上了n个好朋友作为亲友团一块来参加比赛。
比赛规则如下:一共有m道题目,每个人都有 1 次答题机会,每次答题为选择一道题目回答,在回答正确后,可以从这个题目的后续题目,直到题目答错题目或者没有后续题目。
每个问题都会代表一个价值,比赛最后的参赛选手获得奖励价值等价于该选手和他的亲友团没有回答的问题中的最低价值。
我们现在知道小豆和他的亲友团实力非常强,能够做出这次竞赛中的所有题目。
小豆想知道在知道题目和后续题目的条件下,他最大能获得价值是多少?
Solution
首先明确题意:一个m个点的无向有环图,给n+1条链,问你这些链覆盖的点以外的点的最小值的最大值
一看就是二分,可以用floyd传递闭包判断连不连通
二分权值val,把权值小于val的点全部加入进一个临时的图中,初始化总链数为这些点的总数
然后从每个点的后继中,选择一个还没有被连接过的点,连接上去,总需要的链数就-1,因为这个点可以由这个链访问到,不需要额外的访问了
最后判断一下总链数是否小于等于n+1即可
注意这里的连接不是真的连接而只是标记一下可不可以从另一个点访问过来
code:
#include<bits/stdc++.h>
using namespace std;
struct qwq{
int v;
int nxt;
}edge[200010];
int head[200010];
int cnt=-1;
void add(int u,int v){
edge[++cnt].nxt=head[u];
edge[cnt].v=v;
head[u]=cnt;
}
bool vis[510];
int pre[510];
bool find(int u){
for(int i=head[u];~i;i=edge[i].nxt){
int v=edge[i].v;
if(vis[v])continue;
vis[v]=true;
if(!pre[v]){
pre[v]=u;
return true;
}
if(find(pre[v])){
pre[v]=u;
return true;
}
}
return false;
}
int v[510];
bool mp[510][510];
bool now[510][510];
int n,m;
bool check(int val){
memset(now,0,sizeof(now));
cnt=-1;
memset(head,-1,sizeof(head));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(mp[i][j]&&v[i]<val&&v[j]<val){
now[i][j]=true;
}
}
}
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i){
if(!now[i][k])continue;
for(int j=1;j<=n;++j){
now[i][j]|=now[i][k]&now[k][j];
}
}
}
int tmp=0;
for(int i=1;i<=n;++i){
if(v[i]>=val)continue;
tmp++;
for(int j=1;j<=n;++j){
if(now[i][j]){
add(i,j);
}
}
}
for(int i=1;i<=n;++i){
if(v[i]>=val)continue;
memset(vis,0,sizeof(vis));
tmp-=find(i);
}
return tmp<=m;
}
int main(){
scanf("%d%d",&m,&n);
m++;
int top=0;
for(int i=1;i<=n;++i){
int tmp;
scanf("%d%d",&v[i],&tmp);
top=max(top,v[i]);
for(int j=1;j<=tmp;++j){
int u;
scanf("%d",&u);
mp[i][u]=true;
}
}
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
mp[i][j]|=mp[i][k]&mp[k][j];
}
}
}
int l=1,r=top+1;
while(l<r){
int mid=(l+r+1)/2;
if(check(mid)){
l=mid;
}
else {
r=mid-1;
}
}
if(l==top+1)puts("AK");
else printf("%d\n",l);
}