CodeForces 1090G - Combostone题解
题意:
简易版炉石,规定一个怪有两个参数(a,h),a为攻击力,h为生命值,两者均为正整数,初始时场上一个怪都没有,玩家能进行5种操作:
- 召唤一个新的怪上场,其参数为(1,1)。
- 选择一个怪,使其攻击力翻倍。
- 选择一个怪,使其生命值翻倍。
- 选择一个怪,对其进行复制,即召唤一个新生物,其参数与所选生物相同。
- 选择两个不同的怪进行战斗。 参数为(a1,h1) 和(a2,h2)的生物战斗后,它们的参数分别变为(a1,h1-a2) 和(a2,h2-a1)。 如果战斗后怪的生命值为0或更低,则这个怪死亡,不能参与游戏后续行动。
对于每个被召唤的怪,都必须确定在游戏结束时是否仍然存活,如果死亡则要确定在第几个回合死亡。
注意:如果对已死亡的怪进行操作1、2、3、4,则不应有任何效果。 如果对已死亡的怪进行操作5,则会召唤出一个具有相同参数的新怪,但该怪在生成时即被视为已死亡。
思路:
一开始看完题目觉得是非常简单的模拟,也没有许多复杂的情况,定义一个结构体,记录每个怪的攻击力、生命值以及存活情况或死亡的回合数,然后就按输入的操作进行模拟。
但是,因为n ≤ 250000,累计之下攻击力和生命值可能会很大,于是又敲了高精度,结果在第29个测试点超内存了。
需要进行优化,我们可以发现,每个怪的攻击力一定是2的整数次幂,对于攻击力就只需要一个int变量(a)存它的指数就可以了;而对于生命值,可以看作一个2的整数次幂(基础生命值)减去一堆2的整数次幂(一堆被攻击扣除的等于对手攻击力的生命值),我们将这两部分幂的指数分开记录(h和h_deduct),之后就对指数进行处理就行了。此时操作1和2十分简单直接模拟;操作3如果将记录该怪生命值的所有指数都+1会超时,因为是整体翻倍,于是将这个翻倍次数也单独记录(h_doubletimes),相当于提取了一个2的整数次幂到外面;操作4直接复制整个结构体。操作5攻击时将生命值减去的2的整数次幂的指数加入h_deduct(注意:该指数需要减去h_doubletimes),如果加入的指数在h_deduct中已经有了,则需要进行进位处理,即删除h_deduct中出现重复的指数,加入该指数+1的值,如果出现了新的重复,就继续进行进位处理,直到没有重复为止,结束进位处理后再判断h_deduct中最大的指数是否超过了基础生命值的指数h,如果超过则更新存活情况并记录死亡的回合数,并释放h_deduct所占用的内存。
因为h_deduct需要实现快速查询、插入和删除,我一开始用的是set,在第56个点又是超内存。为了节省空间改成vector,用lower_bound查询,还是在第56个点超内存。最后改成unordered_set,用哈希表节省内存,同时又多开了一个变量max_k记录存入h_deduct中最大的指数,第56个点终于不超内存了,超时了!!
其实超内存和超时的原因差不多,毕竟是同一组数据,都是因为h_deduct里面元素很多的同时又通过操作4复制了很多个这样的h_deduct,两者相叠加之后就造成了超内存和超时。但是我们考虑到因为n ≤ 250000,在之前超内存的情况下如果攻击扣除的生命值的指数远远小于基础生命值的指数h(两者差值大于log2(n))话,其实此次攻击对该怪是否死亡几乎没有贡献,因为这样的攻击即使出现250000次也不会导致死亡,除非是此次攻击能使h_deduct一直进位,直接进位到接近基础生命值的指数h的程度。但是这种情况也是有前提的,就是之前所说的“h_deduct里面元素很多的同时又通过操作4复制了很多个这样的h_deduct”两者相叠加导致超内存时才能忽略攻击数值过小的攻击,因为只有造成这种情况的数据才不可能出现一个数值极小的攻击“能使h_deduct一直进位,直接进位到接近基础生命值的指数h的程度”。
也就是说,如果不是那种超内存的数据,不能忽略数值极小的攻击,否则会出现答案错误(我试过第32个测试点就答案错误了)。于是我们就先将所有输入都读完,对输入的数据进行初步的判断(操作4出现的次数是否过多)。如果操作4出现的次数过多(>50000),表明很有可能超内存,就选择忽略数值极小的攻击(<h-20);反之则说明不会超内存,不用忽略数值极小的攻击。
反思:
其实最后这个剪枝(可以这么说吧)的判断并不严谨,数据构造得非常极限的情况下很可能会被hack。
P.S.其实我本来是想写的是那种情况分支比较多、要注意各种情况细节的大模拟题,结果这道题是要各种优化。。。
代码:
set+剪枝
#include<set> #include<cstdio> #define N 250005 using namespace std; struct creature { set<int> h_deduct; int a,h,out,h_doubletimes; }c[N]; int f,op[N],u[N],v[N]; int read() { int x=0; char ch=getchar(); for (;ch<48 || ch>57;ch=getchar()); for (;ch>47 && ch<58;ch=getchar()) x=(x<<1)+(x<<3)+ch-48; return x; } void attack(creature& x,creature& y,int round) { int k=y.a-x.h_doubletimes; if (f>50000 && k<x.h-20) return; //剪枝 for (auto it=x.h_deduct.find(k);it!=x.h_deduct.end() && (*it)==k;++k) it=x.h_deduct.erase(it); x.h_deduct.insert(k); k=*x.h_deduct.rbegin(); if (k>=x.h) { x.out=round; set<int>().swap(x.h_deduct); } } int main() { int n=read(),x=0,k=0,i,j,r; for (i=1;i<=n;++i) { op[i]=read(); switch (op[i]) { case 1: ++x; break; case 2: case 3: u[i]=read(); break; case 4: u[i]=read(); ++f; break; default:u[i]=read(); v[i]=read(); } } for (r=1;r<=n;++r) { x=op[r]; if (x==1) { c[++k].out=-1; c[k].a=c[k].h=1; } else { i=u[r]; switch (x) { case 2: if (c[i].out<0) ++c[i].a; break; case 3: if (c[i].out<0) ++c[i].h_doubletimes; break; case 4: if (c[i].out>0) c[++k].out=r; else c[++k]=c[i]; break; default:j=v[r]; if (c[i].out>0 || c[j].out>0) break; attack(c[i],c[j],r); attack(c[j],c[i],r); } } } printf("%d\n",k); for (i=1;i<k;++i) printf("%d ",c[i].out); if (k) printf("%d\n",c[k].out); return 0; }