bzoj 1854 //1854: [Scoi2010]游戏

bzoj 1854 //1854: [Scoi2010]游戏   //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1854

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

二分图

//1854: [Scoi2010]游戏
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1854

//从未接触过二分图的可看此文入门https://blog.csdn.net/dark_scope/article/details/8880547二分图算法,此文介绍得相当棒
//二分图https://www.luogu.org/problemnew/solution/P1640?page=3此文介绍不错,照抄如下
/*
建模很巧妙,我们可以以属性为左端点(属性值≤n\le n≤n),装备为右端点做二分图匹配。以样例为例,是这样的。
3
1 2
3 2
4 5

显然,4,5不可能被使用,所以略去。
然后从1到n,按属性值匹配,这里匹配的意义已经很明显了。当属性i无法匹配时,输出i−1
*/
//样例通过,提交Wrong_Answer.2019-8-23 16:24
//int head[10100],cnt=0,n,used[1000010],link[1000010];//此处写成int head[10100],cnt=0,n,used[10100],link[1000010];
//used针对的是女而非男.这次印象深刻了.
//修改,提交Time_Limit_Exceed.这下放心了,二分图的算法没有问题,可以开始优化了.2019-8-23 17:11
//以下为二分图的无优化算法,提交Time_Limit_Exceed.但值得读者参考,提供给大家.
#include <stdio.h>
#include <string.h>
int head[10100],cnt=0,n,used[1000010],link[1000010];//此处写成int head[10100],cnt=0,n,used[10100],link[1000010];
struct node{
    int to,next;//to连线的节点,next下一条边
}e[1000010*2];
void add_edge(int u,int v){//邻接表
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
int max(int a,int b){
    return a>b?a:b;
}
int find(int x){
    int b,v;
    b=head[x];
    while(b){
        v=e[b].to;
        if(!used[v]){
            used[v]=1;
            if(!link[v]||find(link[v])){
                link[v]=x;//v girl x boy
                return 1;
            }
        }
        b=e[b].next;
    }
    return 0;
}
int main(){
    int i,a,b,m=0;
    memset(head,0,sizeof(head)),memset(link,0,sizeof(link));
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d%d",&a,&b),m=max(m,max(a,b)),add_edge(a,i),add_edge(b,i);
    for(i=1;i<=m;i++){
        memset(used,0,sizeof(used));
        if(!find(i))break;
    }
    printf("%d\n",i-1);
    return 0;
}

二分图   时间戳   优化

//通过此文https://blog.csdn.net/wzq_QwQ/article/details/48706075代码,直接看懂时间戳优化.
//其实与memset殊途同归,只是时间戳优化,时间复杂度是O(1),memset时间复杂度是O(n),确实神奇.2019-8-23 17:47
//样例通过,提交AC.2019-8-23 17:50
//摘自https://www.cnblogs.com/orzzz/p/7132766.html
/*
时间戳优化??!
开始以为dfs的时间戳。。。结果发现是第几次执行的时间戳。
因为每次要memset一下vis数组,浪费了大量时间。时间戳巧妙地O(1)解决了这个问题。
初始化零?我只要让你数组里不管是谁都失效就好了。所以vis数组用int来存,第几次执行匈牙利算法内层的循环时间戳就是几。设它为T。vis!=T的,和原来vis=0是等效的,即未遍历过,vis=T的,和原来vis=1是等效的,即已遍历过。每当dfs到下一个点。让这个点的vis=T。所以都不用说O(1),根本就是一个T++就解决了。
真的,时间戳大法真的666。只要是类似的memset问题,都可以这么解决,并不限于匈牙利算法。
*/
//以下为二分图,时间戳,优化,代码.
#include <stdio.h>
#include <string.h>
int head[10100],cnt=0,n,used[1000010],link[1000010],nowtime=0;//此处写成int head[10100],cnt=0,n,used[10100],link[1000010];
struct node{
    int to,next;//to连线的节点,next下一条边
}e[1000010*2];
void add_edge(int u,int v){//邻接表
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
int max(int a,int b){
    return a>b?a:b;
}
int find(int x){
    int b,v;
    b=head[x];
    while(b){
        v=e[b].to;
        if(used[v]!=nowtime){//时间戳优化,确实挺神
            used[v]=nowtime;
            if(!link[v]||find(link[v])){
                link[v]=x;//v girl x boy
                return 1;
            }
        }
        b=e[b].next;
    }
    return 0;
}
int main(){
    int i,a,b,m=0;
    memset(head,0,sizeof(head)),memset(link,0,sizeof(link)),memset(used,0,sizeof(used));;
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d%d",&a,&b),m=max(m,max(a,b)),add_edge(a,i),add_edge(b,i);
    for(i=1;i<=m;i++){
        nowtime++;
        if(!find(i))break;
    }
    printf("%d\n",i-1);
    return 0;
}

并查集

//并查集,摘自https://www.cnblogs.com/BLADEVIL/p/3474189.html
/*
我们可以把一件装备看成一条边,两个属性看成两个点,那么这就相当于读入了一张图
当读入每一个x,y时,我们找到两个点的祖先节点,fx,fy,我们保证祖先节点在该连通块
中编号(装备属性)最大,用flag数组记录能否过第I关,那么两种情况

fx=fy(即fx==fy)
这种情况就是加入这条边之后,图中成了一个环(可能这个环之前就存在),那么对于
一个环,假设是1-x节点的环,我们肯定可以全选择(题目中的选择),之前假设是一颗树
的话,X个节点,我们可以选择x-1个,也就是只有一个点选不了,我们肯定让最大的
点没法选,所以除了祖先以外应该全都是true,那么加上这条边之后,祖先也可以选了,所以
将祖先也就是flag[fx]设成true

fx<>fy(即fx!=fy)
这种情况就是一条边连接两个连通分量,先假设两个连通分量都是树,那么我们这个新的连通分量也是
一颗树,对于这种情况,我们可以多选择一个没选过的点,也就是在fx,fy中选编号小的设成true,那么如果
两个环的话,这条边就没用了,之前已经可以全选了,那一个环一个树的情况,使fx<fy,对于两个祖先,我们可以
选择一个,那么应该选编号小的,但是如果编号小的已经可以选了(就是在环了),我们就应该将fy设成true,这点
应该注意,网上有的标程没判断这个,也A了,只能说数据弱。。。
*/
/*
提供一组数据,测试并查集编写得是否正确
输入
2
1 45
2 100
输出
1
*/
//此文代码写得不错https://blog.csdn.net/qq_38678604/article/details/78570183
//样例通过,提交AC.2019-8-24 18:06
//以下代码为并查集代码,不得不说,该题的并查集在处理具体细节时,难度还是挺大的,就难度而言,并查集较难
#include <stdio.h>
#include <string.h>
#define maxm 10010
int f[maxm],vis[maxm];
int getf(int u){
    return f[u]==u?u:f[u]=getf(f[u]);
}
int max(int a,int b){
    return a>b?a:b;
}
int main(){
    int u,v,f1,f2,i,n,t,m=0;
    memset(vis,0,sizeof(vis));
    scanf("%d",&n);
    for(i=1;i<=10001;i++)f[i]=i;//漏了此行
    for(i=1;i<=n;i++){
        scanf("%d%d",&u,&v),m=max(m,max(u,v));
        f1=getf(u),f2=getf(v);
        if(f1!=f2){
            if(f1>f2)t=f1,f1=f2,f2=t;//目标f1<f2
            if(vis[f1])vis[f2]=1;
            vis[f1]=1,f[f1]=f2;
        }else vis[f1]=1;//f1==f2
    }
    for(i=1;i<=m+1;i++)
        if(!vis[i]){
            printf("%d\n",i-1);
            break;
        }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值