战争「思维题」

战争「思维题」

题目描述

内部题不放了

样例

样例输入

2
5
12 34 45 5
10
5
10 15 43 20
5

样例输出

possible
4 100
impossible

思路分析

ps:感谢 yxy 给我讲明白了这道题
太久没水题解了来水一个

\(part1\)——找普适性规律

  • 对于 impossible 的情况很简单,就是每个士兵都可以在最后一击中存活下来,统计一下最小值即可
  • 对于 possible 的情况,\(40pts\) 的直接模拟也很简单,想优化关键在于如何快速地统计伤害
  • 为了方便找到一个具有普适性的规律,我们假设在每次枚举小 w 的位置的时候,小 w 后面的人的编号是 \(0\),然后顺次编号
  • 接下来我们让 \(0\) 开始攻击,很容易这时候死的士兵全都是奇数,即 \(mod%2=1\) ,然后继续模拟会发现,每次死的人间距都是相同的,而这恰好是在一些数 \(mod\) 某个值下才具有的性质,第二轮就是 \(mod4=2\),第三轮就是 \(mod8=4\),发现这个规律是个二的指数级增长,恰好对应了每次都死一半减少,也就有 \(log\) 级轮数
  • 所以接下来要做的就是在这 \(log\) 轮中统计出答案,因为每轮小 w 都只会受到来自他前面那一个活着的士兵的伤害,所以记录这一个编号即可。根据上面的死亡规律,我们其实就可以很方便地处理,如果它的编号是不符合上面的规律的,就记录一次伤害,否则说明他被鲨了,这时候需要更新这个士兵的编号
  • 仍然是根据死亡的规律,你会发现每次死都是隔一个人死一个,那么如果一个士兵在本轮死了,那么在上一轮与它相邻的那个活着的士兵就一定活了下来(因为凶手就是他)

\(part2\)——规律应用

  • 上面的规律成立的前提是我们将编号进行了修改,但是如果每次枚举小 w 的位置都修改的话,显然还是会 \(TLE\) 掉,而且更关键的是要保证让 \(1\) 号先动手
  • 这时候就需要考虑强制性让其满足规律,编号的处理很简单,减去小 w 的位置从而获得相对位置即可,关键是如何让 \(1\) 先动手,还要不破坏规律。
  • 规律好像和需求产生冲突了???貌似两个根本不可能同时满足,然而并不是。既然不是 \(1\) 先开始鲨,我们就让 \(1\) 前面的鲨一个"假人",即把这个位置空出来,似有实无,到 \(1\) 再开始鲨士兵。这时候将序列改成这个样子: 1_2_3_4_5_1 2 3 4 5,其中 _ 代表假人。

(早知道这篇题解这么难写就不写了:-))


\(Code\)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define R register
#define N 1000010
#define ll long long
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const ll inf = 1e15;
int a[N << 2],n,pos;
ll Min,ans;
ll solve(){
	ll res = 0;
	int cur = 1;
	int p = 2*n-2+pos/2;//p代表对小 w 造成伤害的士兵
	while(1) {
		if(p == pos + 1) break;
		cur *= 2;
		if((p - pos - 1) % cur == (cur >> 1)) {//p-pos-1即为相对位置,使其编号满足规律成立的条件
			p = p - cur / 2;//p变为上一轮的前一个
			continue;
		}
		res += a[p]; //没死就记录下伤害
	}
	return res;
}
int main(){
	int T;T = read();
	while(T--) {
		Min = inf;
		ans = inf;
		n = read();
		for(R int i = 1;i < n;++i) {
			int x = read();
			if(x < Min) Min = x;
			a[i * 2 - 1] = x;//对序列进行一下修改,没放的位置就是假人
			a[2 * n - 2 + i] = x;
		}
		int F = read();
		if(Min > F) {
			puts("impossible");
			continue;
		}
		int loc;
		for(R int i = 1;i < n;++i) {//枚举小 w 的位置
			if(a[i * 2 + 1] > F) continue;
			pos = 2 * i;
			R int cur = solve();
			if(ans > cur || (ans == cur && i == n - 1)) {
				ans = cur;
				loc = i + 1;
			}
		}
		if(loc == n) loc = 1;
		puts("possible");
		printf("%d %lld\n",loc,ans+F);
	}
	return 0;
}
posted @ 2020-09-22 17:21  HH_Halo  阅读(151)  评论(2编辑  收藏  举报