wxyww

一个OIER

我们要有最朴素的生活,与最遥远的梦想。即使明日天寒地冻,路远马亡。


Noip2016Day2T3 愤怒的小鸟

题目链接

problem

平面内有n个点,每次可以确定一条过原点且开口向上的抛物线,将这条抛物线上所有的点都删去。问最少需要删几次可以删掉全部的点。

solution

n比较小,直接状压一下。因为已经确定了要过原点。所以每两个点都可以确定一条抛物线。预处理出所有抛物线以及每条抛物线可以删掉的点。

然后记忆化搜索,枚举每次选择的抛物线。转移即可。

注意精度!

确定抛物线的方法就用解二元一次方程组的方法即可。具体如下:

设抛物线的二次项系数为$a$,一次项系数为$b$ ,两个点的坐标分别为$(x_i,y_i),(x_j,y_j)$。

记$k_1=x_i^2,k_2=x_i,k_3=y_i,k_4=x_j^2,k_5=x_j,k_6=y_j$

然后就是解方程组

\[\left\{ \begin{aligned} k_1a+k_2b=k_3& &(1)\\ k_4a+k_5b=k_6& &(2) \end{aligned} \right.\]

由$(1)$得$b=\frac{k_3-k_1a}{k_2}$,代回$(2)$得$a=\frac{k_2k_6-k_3k_5}{k_2k_4-k_1k_5}$

code

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
const double eps = 1e-9;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}
int tot;
bool calc(double a,double b,double x,double y) {
	return fabs(a * x * x + b * x - y) <= eps;
}
double x[20],y[20],a[400],b[400];
int sol[400];
int n,m,f[1 << 20];
int dfs(int now) {
	if(!now) return f[now] = 0;
	if(f[now] != -1) return f[now];
	int ret = 100000;
	for(int i = 1;i <= tot;++i) {
		int t = now & sol[i];
		if(t != now) ret = min(ret,dfs(t) + 1);
	}
	return f[now] = ret;
}
int main() {
	int T = read();
	while(T--) {
		tot = 0;
		memset(f,-1,sizeof(f));
		n = read(),m = read();
		for(int i = 1;i <= n;++i) scanf("%lf%lf",&x[i],&y[i]);
		
		for(int i = 1;i <= n;++i) {
			for(int j = i + 1;j <= n;++j) {
				if(fabs(x[i] - x[j]) <= eps) continue;
				++tot;
				double k1 = x[i] * x[i],k2 = x[i],k3 = y[i],k4 = x[j] * x[j],k5 = x[j],k6 = y[j];
				a[tot] = ((k6 * k2 - k3 * k5)) / ((k4 * k2 - k1 * k5));
				b[tot] = (k3 - k1 * a[tot]) / k2;
				if(a[tot] >= 0) --tot;
			}
		}
		
		
		for(int i = 1;i <= tot;++i) {
			
			sol[i] = (1 << n) - 1;
			
			for(int j = 1;j <= n;++j) 
				if(calc(a[i],b[i],x[j],y[j])) sol[i] ^= (1 << (j - 1));	
		}
		
		for(int i = 1;i <= n;++i) {
			++tot;
			sol[tot] = ((1 << n) - 1) ^ (1 << (i - 1));
		}
		
		printf("%d\n",dfs((1 << n) - 1));
	}
	
	return 0;
}

最近的文章

Noip2015Day2T3 运输计划

题目链接problem一棵n个点带边权的树,有m个条路径。选择一条边,将其权值变为0,使得长度最长的路径长度最小。求该长度最小为多少。solution其实仔细一想并不难。删除一条边会导致所有经过这条边的路径长度减少该边长度。所有没经过这条边的路径长度不变。所以我们只需要知道没经过该边的路径中的长度最大值,以及经过该边的路径中长度最大值。显然经过该边的路径长度最大值我们可以当做最长路径的最大值。现在只要对于每条边都能够计算出没经过该边的路径长度最大值即可。我们发现并不需要对于每条边都求出该值...…

LCA继续阅读
更早的文章

Noip2016Day1T2 天天爱跑步

题目链接problemsolution这是一道一个顶六个的好题!!!说一下各档部分分怎么写吧。先看一下$S_i=1$和$T_i=1$的部分分怎么写。如果$S_i=1$当且仅当第$i$个点的深度$dep_i=w_i$时,该点可以观察到人。且观察到的人数为终点位于其子树内的人数。如果$T_i=1$这时第$i$个点能观察到的人数就是子树内起点深度=$dep_i+w[i]$的人数。用个桶记录下来这个值。如果得到该子树内$dep[s_i]=dep_i+w[i]$的数量呢?其实很简单。当第一次$bfs...…

LCA,差分继续阅读