[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\n",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;
}
Update by 2020.10.28
突然脑抽回来看看,发现按正常人的思路这就是一个裸的二分图...于是补一个二分图的代码。
直接每个属性向所有可能的装备连边即可,跑匈牙利最大匹配。
现在想想,如果当初疫情不是天天抄题解,而是自己真正用心去学了的话,现在也不会这样吧...
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int n, t;
int girl[maxn];
int vis[maxn];
struct edge {
int nex, to;
}e[maxn << 1];
int head[maxn], tot;
void Add(int u, int v) {
e[++tot] = (edge) { head[u], v };
head[u] = tot;
}
int Find(int u) {
for (int i = head[u]; i; i = e[i].nex) {
int v = e[i].to;
if (vis[v] == t) continue;
vis[v]++;
if (!girl[v] || Find(girl[v])) {
girl[v] = u;
return 1;
}
}
return 0;
}
int main() {
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
scanf("%d", &n);
int a, b, mx = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d %d", &a, &b);
Add(a, i);
Add(b, i);
if (a > mx) mx = a;
if (b > mx) mx = b;
}
int flag = 1;
for (int i = 1; i <= mx; ++i) {
t = i;
if (!Find(i)) {
flag = 0;
printf("%d\n", i - 1);
break;
}
}
if (flag) printf("%d\n", mx);
return 0;
}