【34.68%】【BZOJ 3616】War

Time Limit: 20 Sec  Memory Limit: 256 MBSec  Special Judge
Submit: 173  Solved: 60
[Submit][Status][Discuss]

Description

小x所在的世界正在经历一场在k个阵营之间的战争。每个阵营有若干个炮塔,每个炮塔由攻击系统和防御系统组成。第i个炮塔可以攻击到离它欧几里德距离小于等于ri 或者曼哈顿距离小于等于ai的炮塔,被攻击到的炮塔防御系统就会崩溃,同一联盟的炮塔不会被攻击到。每次会随机选择一个炮塔攻击它能打到的所有炮塔,问进行m轮后期望剩下多少个阵营,使得这些阵营拥有的炮塔的防御系统全部完好。防御系统崩溃的炮塔还是会被打到的。值得注意的是,如果一个联盟没有任何炮塔,那么不管怎样它都是完好的。

Input

  输入文件第一行有三个整数n、m、k,分别表示炮塔数目、攻击轮数以及联盟个数。 接下去n行每行有五个正整数xi、yi、ri、ai、pi,其中xi、yi表示炮塔坐标,p表示炮塔所属于阵营。

Output

  输出一行一个实数表示答案,你的输出与标准输出的误差在1e-3以内会被认为是正确的。

Sample Input

2 2 3
0 0 2 2 1
1 1 2 2 2

Sample Output

1.500

HINT

  100%的数据, 0 <= n、m、k <= 35000,1 <= pi <= k,其余所有数字均非负且小于10000,保证在数据生成时不考虑一个点的具体位置,而将它的横纵坐标分开考虑。


Source

【题解】

计算期望的方法是。每个联盟中所有的塔会被多少个其他联盟的塔攻击到,假如为xi(是塔的总数,而不是说这些塔所属的不同联盟的总数);

则期望为∑(1-xi/n)^m  (i∈[1..k])

就是说每一轮中选择剩下的n-x个塔,这个联盟的所有塔就都不会被炸到了。

好好理解理解吧。

然后是实现问题。

以塔的坐标为节点建立kd-tree。用维护的ma_x[2],mi_n[2](最大x,y,最小x,y来确定树中记录的塔是否能被当前枚举的第i号塔炸掉);

如果某个节点的子树的所有坐标中离当前枚举的塔的最大距离都在当前枚举的塔的攻击范围内。那么就不用继续往下搜索了。直接用一个类似线段树的懒惰标记的东西记录该节点包括该节点以下的节点都会被当前枚举的塔炸到(记录的话用bitset);

具体的实现看代码

获取当前枚举的塔和树中的塔的距离的最小值(最大值不用这个)用了估价函数的原理。

就是如果在子树的所有节点所能形成的最大矩形内部。则返回0。

否则返回这个点到这个矩形(不一定存在只是假想的)的边缘的距离。

【代码】

/**************************************************************
Problem: 3616
User: chengchunyang
Language: C++
Result: Accepted
Time:14376 ms
Memory:216980 kb
****************************************************************/

#include <cstdio>
#include <vector>
#include <bitset>
#include <algorithm>
#include <cmath>

using namespace std;

const int MAXN = 40000;

struct point
{
	int d[2], mi_n[2], ma_x[2], bianhao, l, r;
};
//dot[i]记录编号为i的塔在树中的节点编号。
int n, m, k, root, ou[MAXN], man[MAXN], dot[MAXN], number[MAXN], now;
vector <int> a[MAXN];
vector <int> dandu[MAXN];
point t[MAXN];
bitset <MAXN> be_attacked[MAXN];
double ans;

void input_data()
{
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d%d%d%d", &t[i].d[0], &t[i].d[1], &ou[i], &man[i], &number[i]);
		a[number[i]].push_back(i);
		t[i].bianhao = i;
	}
}

bool cmp(point a, point b)
{
	return a.d[now] < b.d[now];
}

void up_data(int rt)
{
	int l = t[rt].l, r = t[rt].r;
	for (int i = 0; i <= 1; i++)
	{
		if (l)
		{
			t[rt].ma_x[i] = max(t[rt].ma_x[i], t[l].ma_x[i]);
			t[rt].mi_n[i] = min(t[rt].mi_n[i], t[l].mi_n[i]);
		}
		if (r)
		{
			t[rt].ma_x[i] = max(t[rt].ma_x[i], t[r].ma_x[i]);
			t[rt].mi_n[i] = min(t[rt].mi_n[i], t[r].mi_n[i]);
		}
	}
}

int build(int begin, int end, int fx)
{
	int m = (begin + end) >> 1;
	now = fx;
	nth_element(t + begin, t + m, t + end + 1, cmp);
	dot[t[m].bianhao] = m;
	for (int i = 0; i <= 1; i++)
		t[m].ma_x[i] = t[m].mi_n[i] = t[m].d[i];
	if (begin < m)
		t[m].l = build(begin, m - 1, 1 - fx);
	if (m < end)
		t[m].r = build(m + 1, end, 1 - fx);
	up_data(m);
	return m;
}

int get_manhadun(int rt, int x, int y, int fanwei)
{
	int d[2];
	d[0] = x; d[1] = y;
	int temp1 = 0;
	for (int i = 0; i <= 1; i++)
	{
		temp1 += max(d[i] - t[rt].ma_x[i], 0);
		temp1 += max(t[rt].mi_n[i] - d[i], 0);
	}
	int temp2 = 0;
	for (int i = 0; i <= 1; i++)
		temp2 += max(abs(d[i] - t[rt].ma_x[i]), abs(d[i] - t[rt].mi_n[i]));
	if (temp2 <= fanwei)//如果最远都在里面了则肯定整个子树都在攻击范围内
		return 1;
	if (temp1 <= fanwei)//如果最近在。则只能说明有一部分是在攻击范围内的,不能一概而论
		return 2;
	return 0;//两个都不满足就全都在攻击范围外。就没有必要往下找了
}

int sqr(int x)
{
	return x*x;
}

int get_ou(int rt, int x, int y, int fanwei)//欧几里得距离和曼哈顿距离的类似
{
	int temp1 = 0, d[2];
	d[0] = x; d[1] = y;
	for (int i = 0; i <= 1; i++)
	{
		int a, b;
		a = d[i] - t[rt].ma_x[i];
		b = t[rt].mi_n[i] - d[i];
		if (a > 0)
			temp1 += sqr(a);
		if (b > 0)
			temp1 += sqr(b);
	}
	int temp2 = 0;
	for (int i = 0; i <= 1; i++)
		temp2 += max(sqr(t[rt].ma_x[i] - d[i]), sqr(t[rt].mi_n[i] - d[i]));
	if (temp2 <= sqr(fanwei))
		return 1;
	if (temp1 <= sqr(fanwei))
		return 2;
	return 0;
}

void sear_ch(int rt, int num, int x, int y)
{//num是枚举的塔的编号,rt是这个节点的编号。x,y是第num个塔的横纵坐标
	int gujia_man;
	gujia_man = get_manhadun(rt, x, y, man[num]);
	if (gujia_man == 1)
	{
		be_attacked[rt][num] = 1;//这个节点所代表的塔以及它所在的子树能被id塔攻击到
		return;
	}
	int gujia_ou;
	gujia_ou = get_ou(rt, x, y, ou[num]);
	if (gujia_ou == 1)
	{
		be_attacked[rt][num] = 1;//同理
		return;
	}
	if (!gujia_man && !gujia_ou)//两种距离都没办法攻击到。则没有必要往下找
		return;
	if ((sqr(t[rt].d[0] - x) + sqr(t[rt].d[1] - y)) <= sqr(ou[num]) ||
		abs(t[rt].d[0] - x) + abs(t[rt].d[1] - y) <= man[num])
		dandu[rt].push_back(num); //这是单个的情况就是说没办法确定子树是不是能被炸到
	if (t[rt].l)
		sear_ch(t[rt].l, num, x, y);
	if (t[rt].r)
		sear_ch(t[rt].r, num, x, y);
}

void down_push(int rt)
{
	int l = t[rt].l, r = t[rt].r;
	if (l)
	{
		be_attacked[l] |= be_attacked[rt];//往下传递这个节点能被那些塔攻击到。
		down_push(l);
	}
	if (r)
	{
		be_attacked[r] |= be_attacked[rt];
		down_push(r);
	}
	int len = dandu[rt].size();
	for (int i = 0; i <= len - 1; i++)//这些是单独只有o能被攻击到的,不一定它的子树也能被攻击到。
		be_attacked[rt][dandu[rt][i]] = 1;
}

void get_ans()
{
	root = build(1, n, 0);
	for (int i = 1; i <= n; i++)
	{
		int x = t[dot[i]].d[0], y = t[dot[i]].d[1];
		sear_ch(root, i, x, y);
	}
	down_push(root);
	ans = 0;
	for (int i = 1; i <= k; i++)
	{
		bitset <MAXN> temp;
		temp.reset();
		int len = a[i].size();
		for (int j = 0; j <= len - 1; j++)
		{
			int x = a[i][j];
			x = dot[x];
			temp |= be_attacked[x];
		}
		for (int j = 0; j <= len - 1; j++)
			temp[a[i][j]] = 0;
		int num = temp.count();
		ans += pow(1 - (num*1.0) / n, m*1.0);
	}
}

void output_ans()
{
	printf("%.3lf
", ans);
}

int main()
{
	//freopen("F:\rush.txt", "r", stdin);
	input_data();
	get_ans();
	output_ans();
	return 0;
}




原文地址:https://www.cnblogs.com/AWCXV/p/7632242.html