zoukankan      html  css  js  c++  java
  • [SCOI2010]连续攻击游戏

    解题报告——连续攻击游戏

    题干

    lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。 游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个属性值为3的装备攻击boss……以此类推。 现在lxhgww想知道他最多能连续攻击boss多少次?

    Input

    输入的第一行是一个整数N,表示lxhgww拥有N种装备 接下来N行,是对这N种装备的描述,每行2个数字,表示第i种装备的2个属性值

    Output

    输出一行,包括1个数字,表示lxhgww最多能连续攻击的次数。

    Sample

    in:3 1 2 3 2 4 5

    out:2

    Hint

    对于30%的数据,保证N < =1000
    对于100%的数据,保证N < =1000000

    整活

    上来一看以为是个水题,上去一个暴力试试水,喜提40分。转念一想发现对于同一次攻击我们要做出选择,假如有两件装备,它们都能打出2的攻击,但一件还能打出3的攻击,另一件还能打出5的攻击,那我们优先用哪一件呢?留着攻击力大的那个吗?并不是,这不像咱们玩游戏,你攻击力再大,你根本用不上又有什么卵用?也不一定就留攻击力小的那个,道理还是一样的,有可能别的武器也能达到这个攻击力,但却达不到那个较高的攻击力。所以,由于这种不确定,我们并不能直接从属性值上做出抉择。

    原始版的暴力代码(笑)放上来给大家乐呵乐呵

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<vector>
    #include<cstring>
    #define ll long long
    #define Dio ios::sync_with_stdio(0)
    using namespace std;
    const int maxn=1000000+1;
    int n;
    vector<int> belong[maxn];
    struct node{
        bool if_used;
    }tool[maxn];
    int Check(){
        int i;
        for(i=1;i<=n;i++){
            if(belong[i].size()==0) return 0;
            int cnt=0;
            for(int j=0;j<belong[i].size();j++){
                int id=belong[i][j];
                //printf("i=%d id=%d if_used=%d
    ",i,id,tool[id].if_used);
                if(!tool[id].if_used){
                    tool[id].if_used=1;
                    cnt++;
                    break;
                }
            }
            if(cnt==0) return i-1;
        }
        return n;
    }
    int main(){
        cin>>n;
        int l,r;
        for(int i=1;i<=n;i++){
            cin>>l>>r;
            tool[i].if_used=0;
            belong[l].push_back(i);
            belong[r].push_back(i);
        }
        cout<<Check()<<endl;
        return 0;
    }
    

    正解

    关于这道题的正解有很多,包括但不限于并查集、贪心、二分图、DFS、BFS等等,这里说一个我觉得比较好理解的BFS(我才不承认是别的我不会呢)。

    我们知道:一个装备两个属性中只有一个能起作用。如果我们把两个属性值当做两个点来连线,那么我们可以感性地想象这条线上有一只猫,这只猫要不然趴在一头,要不然趴在另一头,哪个值被覆盖代表着这个装备提供哪个属性值(我理解为伪双向边,看似双向,实则单向,第一次通过这条边时的方向决定了以后通过这条边的方向)。如果将属性值作为边的两端来这样建一张图,对于每一个连通图,因为每一只猫都能覆盖旁边的一个点,我们可以很轻松的想象到,如果说这个图中有m条边,n个点,那么一定可以有min(n,m)个点被覆盖。(奇妙的比喻转自洛谷 @Windows_XP,我jio得很形象)

    对于一张连通图,边数一定不小于点数(除非他是一棵树),然后根据伪双向边的特点,我们可以确定图上所有的点都是可达的。若是一棵树,显然有一个点是无法取到的,根据贪心的思想,我们肯定想办法让最大的那个点取不到。通过对BFS时的点数和边数维护比较得出答案(直接看代码吧)。

    而对于树中最大的点如何处理呢?每一次BFS都会遍历整个连通块,其他的BFS是够不到这个最大点的。所以最大的那个点无法被其他的BFS覆盖。那么我们将它的状态视为if_can[x]=1,即它绝对无法被覆盖。

    没有与任何点相连的点,也可以看做一个树。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<cstring>
    #define ll long long
    #define Dio ios::sync_with_stdio(0)
    using namespace std;
    const int maxn=1000000+1;
    int n;
    struct Edge{
        int to,next;
    }edge[maxn<<1];
    int head[maxn],len=0;
    void Add(int u,int v){
        edge[++len].to=v;
        edge[len].next=head[u];
        head[u]=len;
    }
    int vis[maxn],if_can[maxn];
    bool Check(int u){
        if(vis[u]) return (!if_can[u]);
        vis[u]=1;
        int maxp=0,nump=0,nume=0;
        //分别记录该连通块内的最大点,总点数,总边数
        queue<int> q;
        q.push(u);
        while(!q.empty()){
            int x=q.front();
            q.pop();
            maxp=max(maxp,x);//更新最大点
            nump++;
            for(int i=head[x];i;i=edge[i].next){
                nume++;
                int v=edge[i].to;
                if(!vis[v]) vis[v]=1,q.push(v);
            }
        }
        nume>>=1;//因为是伪双向边,实际上真正的边数是经过边数的一半
        if(nume<nump) if_can[maxp]=1;//一个块内边数小于点数,显然一棵树,那么让最大的那个取不到
        return (!if_can[u]);
    }
    int main(){
        Dio;
        cin>>n;
        for(int i=1;i<=n;i++){
            int u,v;
            cin>>u>>v;
            Add(u,v),Add(v,u);
        }
        int k=1;//从1开始判断每个点是否可达
        while((vis[k]&&!if_can[k])||Check(k))
        //vis[k]和!if_cnt[k]的顺序不能换,只有vis[]过了这个点它的是否可达才是有意义的,后面的Check()就是为vis为0时准备的
            k++;
        cout<<k-1<<endl;
        return 0;
    }
    
  • 相关阅读:
    js注意点:数组比较大小方法及数组与对象的区别
    ubuntu开通ftp虚拟用户
    linux(centos)禁止升级内核的办法
    解决ubuntu新建用户后,tab键不能使用的问题
    ubuntu下php7+mysql+nginx安装笔记
    使用nginx+lua+GraphicsMagick实现图片自动 裁剪
    mysql5.7配置文件(仅供参考)
    ubuntu下安装基于Apache的SVN服务器
    Linux CentOS 7.X 如何修改内核启动默认顺序
    Linxu下Redis安装
  • 原文地址:https://www.cnblogs.com/Zfio/p/12822641.html
Copyright © 2011-2022 走看看