2019南昌邀请赛重现G.Winner(Tarjan+建图)

https://nanti.jisuanke.com/t/40259

题意:就是n个人比赛,每个人都有三种模式下的能力值,然后主持人主持比赛,可以任意选择两个人在同一模式比赛,能力值大的赢,输的人淘汰,直到最后只有一个人获胜。m次询问,问某个人最后是否可能赢

题解:有顺序关系!!,如果看出强连通分量的话,难点就是建图了,先说怎么与强连通分量靠上(先拿暴力卡过去hh(其实也是同学教我的))。

为了让一个人最后赢,贪心的想每个模式排序后的最大能力值的人一定最后可能赢,所以下一个人想最后可能赢,他只要在其余两个模式下任意一个模式下赢了第一个赢的人,那他就会最后赢,后面的人依次这样操作。

又因为有三个模式所以顺序多了,每个人赢的顺序可以看成这样

(1,2,3表示人的编号,输指赢),每个人都可能是第一个赢的(最后赢),下一个人就会赢第一个人。所以建图就是每个模式排序,让输的指向赢的,三种形成一个大图,然后跑强连通分量

细节就是注意三个模式的最大能力者都是定位第一个赢的,那么强连通分量必须是有这三个人的强连通的分量

#include <cstdio>
#include <stack>
#include <cstring>
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
const int maxn=2e5+10;
int n,m,idx=0,cnt=1,Bcnt=0;
int head[maxn*3];
int vis[maxn];
int dfn[maxn],low[maxn];
//int Belong[100];//belong表示每个点属于的缩完之后的哪一个点
stack <int> s;
vector<int>vec[maxn];
int vvv[maxn];
int na,nb,nc;
struct edge{
    int v,next;
}e[maxn*3];
void adde(int u,int v){
	e[cnt].next=head[u];
    e[cnt].v=v;
    head[u]=cnt++;
}
void tarjan(int u){
    int v;
    dfn[u]=low[u]=++idx;//每次dfs,u的次序号增加1
    s.push(u);//将u入栈
    vis[u]=1;//标记u在栈内
    for(int i=head[u];i!=-1;i=e[i].next){//访问从u出发的边
        v=e[i].v;
        if(!dfn[v]){//如果v没被处理过
            tarjan(v);//dfs(v)
            low[u]=min(low[u],low[v]);//u点能到达的最小次序号是它自己能到达点的最小次序号和连接点v能到达点的最小次序号中较小的
        }
        else if(vis[v])
		    low[u]=min(low[u],dfn[v]);//如果v在栈内,u点能到达的最小次序号是它自己能到达点的最小次序号和v的次序号中较小的
    }     
    if(dfn[u]==low[u]){//发现是整个强连通分量子树里的最小根。
        Bcnt++;
        do{
            v=s.top();
            if(v==na||v==nb||v==nc) vvv[Bcnt]=1;
            s.pop();
            vis[v]=0;//表示出栈 
            vec[Bcnt].push_back(v);
            //Belong[v]=Bcnt;
        }while(u != v);
    }
}
struct node{
	int v;
	int id;
}a[maxn];
bool cmp(node a,node b){
	return a.v>b.v;
}
int main(){
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=3;i++){
		for(int j=1;j<=n;j++)
		  cin>>a[j].v,a[j].id=j;
	    sort(a+1,a+n+1,cmp);
	    vis[a[1].id]=1;
	    if(i==1) na=a[1].id;
	    if(i==2) nb=a[1].id;
	    if(i==3) nc=a[1].id;
		for(int j=1;j<n;j++)
			adde(a[j].id,a[j+1].id);//输家指赢家 
	}
    for(int i=1;i<=n;i++) 
	    if(!dfn[i]) tarjan(i);//保证每个点去找 
    for(int i=1;i<=Bcnt;i++){
         if(vvv[i]){
         	int len=vec[i].size();
         	for(int j=0;j<len;j++){
         		vis[vec[i][j]]=1;
         	//	cout<<i<<"---"<<vec[i][j]<<" ";
			}
		 }
		// cout<<endl<<endl;
    }
    while(m--){
    	int x;
    	cin>>x;
    	if(vis[x]) cout<<"YES"<<endl;
    	else cout<<"NO"<<endl;
	}
    return 0;
}

(直接就是现学的板子,套上就能用);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值