首页 > 其他分享 >[NOIP2016提高组] 愤怒的小鸟

[NOIP2016提高组] 愤怒的小鸟

时间:2023-01-27 17:55:07浏览次数:55  
标签:NOIP2016 int double 小鸟 ++ res path include 愤怒

洛谷传送门
AcWing

解题思路

\(\qquad\)这题可以转化为一个重复覆盖问题,由于三个点可以确定一条抛物线,而这里的抛物线必定经过原点,所以可以用不是原点的两个点确定一条抛物线。

\(\qquad\)对于一个覆盖情况我们可以用一个二进制数 state 表示,其中从右往左从 \(0\) 开始编号,第 \(i\) 位是 \(1\) 的时候表示打中了,反之没打中。
\(\qquad\)然后我们可以爆搜所有状态,知道一个状态全部为 \(1\),也就是 \((1 << n) - 1\),表示所有的猪都被打中了。对于一个非终点状态,我们找到第一个为 \(0\) 的数,然后枚举所有经过这个点的抛物线,从中间取出一个最小的答案就可以了。

\(\qquad\)为此我们可以进行一些预处理:定义path[i][j]为经过i,j两点的抛物线状态,也是同样用二进制压缩,和state类似,第 \(i\) 位是 \(1\)代表经过,反之没经过。

\(\qquad\)有了path数组之后,我们对于每个状态的转移枚举图上的所有点,新状态也就是老状态和path[x][i]作或运算,就可以很容易地进行转移了。

\(\qquad\)这里提供两种实现方式:爆搜+剪枝和记忆化搜索

爆搜

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 20, M = 1 << 18;
int n, m, T, res, path[N][N], f[M];
struct Point { double x, y; } p[N];

int cmp(double a, double b) 
{
	if (fabs(a - b) <= 1e-6) return 0; // =
	return 1;
}

void calc(int i, int j) 
{
    if (path[i][j]) return ;
	double r1 = p[i].x, c1 = p[i].y;
	double r2 = p[j].x, c2 = p[j].y;
	double a = (c1 / r1 - c2 / r2) / (r1 - r2);
	double b = c1 / r1 - a * r1;

	if (a >= 0) return ; // 开口不朝下
	if (!cmp(r1, r2)) return ; // 垂直点对

	for (int k = 0; k < n; k ++ ) 
	{
		double rk = p[k].x, ck = p[k].y;
		double t = rk * rk * a + b * rk;
		if (!cmp(t, ck)) path[i][j] |= 1 << k;
	}
	
	path[j][i] = path[i][j];
}

void dfs(int st, int cnt)
{
    if (f[st] <= cnt) return ;
    
	if (st == (1 << n) - 1) return void (res = min(res, cnt));
	if (cnt >= res - 1) return ;
	
	int x;
	for (x = 0; x < n; x ++ )
		if (!(st >> x & 1)) break;

	for (int i = 0; i < n; i ++ ) 
		if (path[x][i] && cnt + 1 < res) 
			dfs(st | path[x][i], cnt + 1);
			
	f[st] = min(f[st], cnt);
}

void solve() 
{
	memset(f, 0x3f, sizeof f);
    memset(path, 0, sizeof path);
    
	res = 2e9;
	scanf("%d%d", &n, &m); // m 是无用条件

	for (int i = 0; i < n; i ++ ) 
		scanf("%lf%lf", &p[i].x, &p[i].y);

	for (int i = 0; i < n; i ++ ) 
	{
		path[i][i] = 1 << i;
		for (int j = i + 1; j < n; j ++ ) calc(i, j);
	}

	dfs(0, 0);
	printf("%d\n", res);
	
}

int main() 
{
	scanf("%d", &T);
	while (T -- ) solve();
	return 0;
}

记忆化搜索

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

#define x first
#define y second

using namespace std;
using PDD = pair<double, double> ;

const int N = 20, M = 1 << 18;
int n, m, T, res, path[N][N];
PDD p[N];

int cmp(double a, double b) 
{
	return fabs(a - b) > 1e-6; 
}

void calc(int i, int j) 
{
    if (path[i][j]) return ;
	double r1 = p[i].x, c1 = p[i].y;
	double r2 = p[j].x, c2 = p[j].y;
	double a = (c1 / r1 - c2 / r2) / (r1 - r2);
	double b = c1 / r1 - a * r1;

	if (a >= 0) return ; // 开口不朝下
	if (!cmp(r1, r2)) return ; // 垂直点对

	for (int k = 0; k < n; k ++ ) 
	{
		double rk = p[k].x, ck = p[k].y;
		double t = rk * rk * a + b * rk;
		if (!cmp(t, ck)) path[i][j] |= 1 << k;
	}
	
	path[j][i] = path[i][j];
}

int dfs(int st, vector<int> &f) 
{ 
    int &v = f[st];
    if (st == (1 << n) - 1)  return 0;
    if (v) return v;
    int x;
    for (x = 0; x < n; x ++ )
        if ((st >> x & 1) == 0) break ;
        
    int res = 2e9;
    for (int i = 0; i < n; i ++ ) 
        if (path[x][i]) res = min(dfs(st | path[x][i], f) + 1, res) ;
        
    return v = res;
}

void solve() 
{
	res = 2e9;
	vector<int> f(M, 0);
	scanf("%d%d", &n, &m); // m 是无用条件

	for (int i = 0; i < n; i ++ ) 
		scanf("%lf%lf", &p[i].x, &p[i].y);

	for (int i = 0; i < n; i ++ ) 
	{
		path[i][i] = 1 << i;
		for (int j = i + 1; j < n; j ++ ) calc(i, j);
	}

	printf("%d\n", dfs(0, f));
	
    memset(path, 0, sizeof path);
}

int main() 
{
	scanf("%d", &T);
	while (T -- ) solve();
	return 0;
}

个人实测爆搜会快一点,只要 \(81ms\)

标签:NOIP2016,int,double,小鸟,++,res,path,include,愤怒
From: https://www.cnblogs.com/StkOvflow/p/17069117.html

相关文章

  • P1941 [NOIP2014 提高组] 飞扬的小鸟 题解
    WC-2023上的题目。线性动态规划P1941[NOIP2014提高组]飞扬的小鸟我们先不管障碍物。设\(f[i][j]\)表示来到点\((i,j)\)的最少点击屏幕数。因为每秒要不上升\(......
  • 来自后端的愤怒!
    相信作为后端开发程序员,多少都掌握一定的前端技术.在技术学习期间就得学习前端技术,用来运行自己的代码.完成项目的初步建设.作为后端程序员,不精通前端技术,可......
  • P1600 [NOIP2016 提高组] 天天爱跑步
    //题目大意:有一棵树,在每个节点上会在Pi时刻出现一个观察员,在该时刻观察员如果观察到路过的运动员,那么该观察员的分数加1;//现在给定m条路径的起点与终点,每个运......
  • [NOIP2016 普及组] 回文日期
    [NOIP2016普及组]回文日期题目背景NOIP2016普及组T2题目描述在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。牛牛习惯用\(8\)位数字表示......
  • struts2.0 s标签_小小鸟
    struts2.0s标签1.Struts2页面开发中常用标签使用说明1.1.往action里传值的使用方式:<inputname="userName"type="text"class="input6"size="15">a.userName属性需......
  • 2022NOIP A层联测34 bs 串 英语作文 计算器 愤怒的小鸟
    T1[图论:并查集维护寻找特殊环]给出一个无向图,点权是0或者1,你可以从任意起点出发,每到达一个点,把这个点的权值放到你构造的字符串的末尾,并且这个点的权值取反。给出K次操作,......
  • NOIP2016Day2T2-蚯蚓
    B:蚯蚓时间限制:1Sec  内存限制:512MB题目描述本题中,我们将用符号LcJ表示对c向下取整,例如:L3.0J=L3.1J......
  • NOIP2016Day1T3-换教室
    概率dp经典题。3、换教室时间限制:1Sec内存限制:512MB题目描述在可以选择的课程中,有2n节课程安排在n个时间段上。在第i(1≤i≤n)个时间段上,两节内容相同的课程同时......
  • NOIP2016Day1T2-天天爱跑步
    2、天天爱跑步时间限制:2Sec  内存限制:512MB题目描述小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。 《天天爱跑步》是一个养成类游戏,需要玩......
  • 玩具谜题(NOIP2016)
    题目链接:​​玩具谜题​​​提高组日常水题。直接模拟,有需要注意的点会在代码后讲解:#include<bits/stdc++.h>usingnamespacestd;intmain(){intn,m;scanf("%......