L3-034 超能力者大赛
知乎上有这样一个话题:“全世界范围内突然出现 666 个超能力者,击败对手就能获得对手的超能力,最终获胜者将如何自处?”
现在让我们来将这个超能力者大赛的规则具体化:给定 N 位超能力者(超能力者从 0 到 N−1 编号,你的编号为 0),分布在全世界 M 座城市中(城市从 0 到 M−1 编号),比赛从第 1 天开始,到第 D 天结束。每位超能力者有一个能力值 Ei(i=0,⋯,N−1)。
如果你的能力值大于等于对手的能力值,就可以将其击败,并且对方的能力值立刻直接累加到你的能力值上。但是,当你每次到达一座城市,击败一位对手后,就会惊动这座城市中所有剩下的超能力者(包括联盟)。这座城市中剩下的所有个体能力值小于等于你的超能力者就会立刻团结起来形成联盟,联盟的能力值等于所有联盟成员能力值的总和。联盟将从此作为一个整体进攻或防御你,你可将联盟等价地理解为一个超能力者,它还可能在后续的战斗中继续与其它弱小的同城选手或联盟合并。第二天,这个城市中任何能力值大于你的对手就会找上门来,而你为了自保,就只能赶紧离开这个城市……
此外,还有如下补充规定:
- 你每天只能进行 1 场战斗,击败一个超能力者(包括联盟),或者被一个能力值大于你的超能力者(包括联盟)击败。
- 比赛中间没有一天可以休息,你或者在战斗,或者在去往另一个城市的路上。
- 当你到达一个城市时,会在到达的第二天进行战斗。
- 当你结束战斗要离开这个城市时,是第二天开始出发。
下面我们为你设计一套贪心算法,就请你验证一下,这个算法能否让你得到这个大赛的冠军。算法步骤很简单:
- 第 1 步:从所有超能力者(包括联盟)中找出一个与自己能力值最接近、同时自己能够击败的对手,用最短时间去到对方的城市,在到达后第二天击败之;
- 第 2 步:如果该城市中没有能力值大于你的超能力者(包括联盟),按照规则逐一击败之;如果该城市中已经没有超能力者、或你第二天没有必胜把握,则回到第 1 步。
重复上述步骤,直到你:
- 击败了所有超能力者;或
- 你已经无路可逃(即剩下的所有超能力者的能力值都大于你);或
- 比赛终止时间到。
第 1 步中有可能遇到多个符合条件的对手,并列情况下优先选最近的;距离也并列的情况下选途径城市最少的;再有并列就选城市编号最小的。
输入格式:
输入在第一行中给出 4 个正整数:N(≤105),为参加比赛的超能力者人数;M(≤200),为比赛涉及的城市数量;Me 为城市间直达通路的数量;D(≤1000)为比赛时长(以“天”为单位)。
随后 N 行,第 i 行(i=0,⋯,N−1)给出第 i 位参赛者的信息,格式为:
所在城市编号 能力值
其中能力值
是不超过 1000 的正整数。
接下来是城市通路信息,分 Me 行,每行给出一对城市间的双向直接通路信息,格式为:
城市1 城市2 通行时间
其中通行时间
为不超过 D/2 的正整数,以“天”为单位。题目保证每条道路的信息只出现一次,没有重复。
输出格式:
顺次输出你每一步的活动,每个活动占一行,格式为:
Get e at city on day d.
表示第 d
天在编号为 city
的城市击败了一个能力值为 e
的对手。
Move from city1 to city2.
表示从编号为 city1
的城市到达了编号为 city2
的城市。
WIN on day d with e!
表示在第 d
天成为唯一的赢家,此时你的能力值是 e
。
Lose on day d with e.
表示在第 d
天走投无路成为输家,此时你的能力值是 e
。
Game over with e.
表示比赛结束你没能成为唯一赢家,此时你的能力值是 e
。注意:比赛最后一天是先判断输赢,才判断结束的。即:如果最后一天剩下所有人都比你厉害,是判你输,而不是 Game over。
解题思路
暴力模拟
代码
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>
#define N 100010
#define M 210
using namespace std;
int n,m,e,d;
int mp[M][M],path[M][M];//城市路径长度、途径城市数量
int nowpos,nowability,nowday,cnt=0;//现在位置,能力值,时间,击败数
vector<int> city[N];//每个城市的超能力者
struct node{//超能力者
int pos,ability,lose;
}player[N];
int move(){//按第一步要求找下一个对手
int id=0,diff=1e9,mind=1e9,minpath=1e9,mincity=1e9;
for(int i=1;i<=n;i++){
if(player[i].lose||nowability<player[i].ability) continue;
int fg=1;
if(nowpos==player[i].pos){//如果当前所在城市中有打不过的,就必须换城市
for(int j=0;j<city[player[i].pos].size();j++){
int k=city[player[i].pos][j];
if(player[k].ability>nowability) fg=0;
}
}
if(!fg) continue;
if(diff>nowability-player[i].ability){
id=i;
diff=nowability-player[i].ability;
mind=mp[nowpos][player[i].pos];
minpath=path[nowpos][player[i].pos];
mincity=player[i].pos;
}
else if(diff==nowability-player[i].ability){
if(mind>mp[nowpos][player[i].pos]){
id=i;
mind=mp[nowpos][player[i].pos];
minpath=path[nowpos][player[i].pos];
mincity=player[i].pos;
}
else if(mind==mp[nowpos][player[i].pos]){
if(minpath>path[nowpos][player[i].pos]){
id=i;
minpath=path[nowpos][player[i].pos];
mincity=player[i].pos;
}
else if(minpath==path[nowpos][player[i].pos]){
if(mincity>player[i].pos) id=i,mincity=player[i].pos;
}
}
}
}
return id;
}
void defeat(int id){//击败对手
if(nowpos!=player[id].pos){
printf("Move from %d to %d.\n",nowpos,player[id].pos);
nowday+=mp[nowpos][player[id].pos];//加上移动到对手城市时间
nowpos=player[id].pos;
if(nowday==d) return;//时间不够
}
printf("Get %d at %d on day %d.\n",player[id].ability,nowpos,++nowday);//击败对手
player[id].lose=1; cnt++;
nowability+=player[id].ability;
vector<int> tmp;
int flag=1,sum=0,p=0;
for(int i=0;i<city[nowpos].size();i++){//将当前城市剩余超能者中<=你的能力值的集合成一个联盟
int j=city[nowpos][i];
if(player[j].lose) continue;
if(player[j].ability>nowability) flag=0,tmp.push_back(j);//第二天没有必胜把握
else{
player[j].lose=1; cnt++;//视为个人均灭亡,但联盟存活(下面有存联盟)
if(!p) p=j;
sum+=player[j].ability;//联盟总能力值
}
}
if(sum) player[p].lose=0, cnt--, player[p].ability=sum, tmp.push_back(p);//联盟存活
city[nowpos]=tmp;//更新城市超能力者集合
if(!sum||sum>nowability) flag=0;//没有超能力者或没有必胜把握
if(!flag||nowday==d) return;//打不过或时间不够
printf("Get %d at %d on day %d.\n",player[p].ability,nowpos,++nowday);//击败联盟
player[p].lose=1; cnt++;
nowability+=player[p].ability;
}
void over(){//结束
if(cnt==n) printf("WIN on day %d with %d!",nowday,nowability);//击败所有对手
else{
if(move()) printf("Game over with %d.",nowability);//不是唯一赢家
else printf("Lose on day %d with %d.",++nowday,nowability);//无路可走
}
}
void solve(){
while(nowday<d){
int id=move();//寻找下一个对手
if(!id||nowday+mp[nowpos][player[id].pos]>d){//无路可走或时间不够
over(); return;
}
defeat(id);
if(cnt==n){//击败所有对手
over(); return;
}
}
over();
}
int main(){
scanf("%d%d%d%d",&n,&m,&e,&d);
scanf("%d%d",&nowpos,&nowability);
n--;
for(int i=1;i<=n;i++){//对手信息
scanf("%d%d",&player[i].pos,&player[i].ability);
player[i].lose=0;
city[player[i].pos].push_back(i);
}
memset(mp,0x3f,sizeof mp);
memset(path,0x3f,sizeof path);
for(int i=1;i<=e;i++){//城市图
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
mp[u][v]=mp[v][u]=min(mp[u][v],w);
path[u][v]=path[v][u]=1;
}
for(int i=0;i<m;i++) mp[i][i]=0,path[i][i]=0;
for(int k=0;k<m;k++)//floyd最短路
for(int i=0;i<m;i++)
for(int j=0;j<m;j++)
if(mp[i][j]>mp[i][k]+mp[k][j]){
mp[i][j]=mp[i][k]+mp[k][j];
path[i][j]=path[i][k]+path[k][j];
}
else if(mp[i][j]==mp[i][k]+mp[k][j]&&path[i][j]>path[i][k]+path[k][j])
path[i][j]=path[i][k]+path[k][j];
if(n==0) printf("WIN on day %d with %d!",1,nowability);//击败所有对手
else solve();
return 0;
}