zoukankan      html  css  js  c++  java
  • 【状压DP】【P2831】【NOIP2016D2T3】愤怒的小鸟

    传送门

    Description

    Kiana 最近沉迷于一款神奇的游戏无法自拔。

    简单来说,这款游戏是在一个平面上进行的。

    有一架弹弓位于 $(0,0)$ 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如 $y=ax^2+bx$ 的曲线,其中 $a,b$ 是Kiana 指定的参数,且必须满足 $a < 0$,$a,b$ 都是实数。

    当小鸟落回地面(即 $x$ 轴)时,它就会瞬间消失。

    在游戏的某个关卡里,平面的第一象限中有 $n$ 只绿色的小猪,其中第 $i$ 只小猪所在的坐标为 $left(x_i,y_i ight)$。

    如果某只小鸟的飞行轨迹经过了 $left( x_i, y_i ight)$,那么第 $i$ 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;

    如果一只小鸟的飞行轨迹没有经过 $left( x_i, y_i ight)$,那么这只小鸟飞行的全过程就不会对第 $i$ 只小猪产生任何影响。

    例如,若两只小猪分别位于 $(1,3)$ 和 $(3,3)$,Kiana 可以选择发射一只飞行轨迹为 $y=-x^2+4x$ 的小鸟,这样两只小猪就会被这只小鸟一起消灭。

    而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。

    假设这款游戏一共有 $T$ 个关卡,现在 Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。

    ## Input

    第一行包含一个正整数 $T$,表示游戏的关卡总数。

    下面依次输入这 $T$ 个关卡的信息。每个关卡第一行包含两个非负整数 $n,m$,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。接下来的 $n$ 行中,第 $i$ 行包含两个正实数 $x_i,y_i$,表示第 $i$ 只小猪坐标为 $(x_i,y_i)$。数据保证同一个关卡中不存在两只坐标完全相同的小猪。

    $m$是部分分内容不予提及。

    保证 $1leq n leq 18$,$0leq m leq 2$,$0 < x_i,y_i < 10$,输入中的实数均保留到小数点后两位。

    上文中,符号 $lceil c ceil$ 和 $lfloor c floor$ 分别表示对 $c$ 向上取整和向下取整,例如:$lceil 2.1 ceil = lceil 2.9 ceil = lceil 3.0 ceil = lfloor 3.0 floor = lfloor 3.1 floor = lfloor 3.9 floor = 3$。

    ## Output

    对每个关卡依次输出一行答案。

    输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。

    ## Sample Input ``` 3 2 0 1.41 2.00 1.73 3.00 3 0 1.11 1.41 2.34 1.79 2.98 1.49 5 0 2.72 2.72 2.72 3.14 3.14 2.72 3.14 3.14 5.00 5.00 ``` ## Sample Output ``` 2 2 3 ``` ## Hint 对于100%的数据,$1~leq~n~leq~18,1~leq~T~leq~5$。

    Solution

    为什么16年的D2T3这么水啊……和17年的状压完全不在一个档次上有没有……
    看到数据范围大概是个状压,考虑(F_S)代表干掉集合(S)的小猪的ans。
    转移的时候就比较尴尬了。我们不知道这一发会干掉几只猪,也不知道这几只猪能不能被一发干掉。怎么办呢?
    考虑预处理抛物线。对于任意两头猪显然最多可以构造出一条过原点的抛物线,他们不能构造出抛物线当且仅当构造出的抛物线开口向上不合法。
    那么一共最多有(n^2)条抛物线,设(g_i)是第(i)条抛物线能干掉的小猪的集合。对于每条抛物线枚举所有的小猪,如果小猪可以在抛物线上,就把他加进集合。
    那么在转移时,枚举每条抛物线,转移方程如下:
    (f_S=min){(f_{S_0}+1)},其中(exists~i,g_i=C_{S}S_0)(C)代表补集,即:(g_i=S~xor~S_0)
    同时考虑我们可以一条抛物线只打一只小猪,所以还要枚举:
    (f_S=min){(f_{S_0}+1)},其中(S_0)是$S去掉任意一个元素。
    这样共有(2^n)个集合,每个转移复杂度是(O(n^2))。所以总的时间复杂度是(O(T~ imes~2^n~ imes~n^2))。一般来说玄学状压都跑不够上界复杂度所以就这么在CCF老爷机上成了正解。
    另外第8个点卡精度,果断特判。

    Code

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define rg register
    #define ci const int
    #define cd const double
    #define cld const long double
    #define cl const long long int
    
    typedef double db;
    typedef long double ld;
    typedef long long int ll;
    
    namespace IO {
    	char buf[50];
    }
    
    template<typename T>
    inline void qr(T &x) {
    	char ch=getchar(),lst=' ';
    	while(ch>'9'||ch<'0') lst=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	if (lst=='-') x=-x;
    }
    
    template<typename T>
    inline void write(T x,const char aft,const bool pt) {
    	if(x<0) {putchar('-');x=-x;}
    	int top=0;
    	do {
    		IO::buf[++top]=x%10+'0';
    		x/=10;
    	} while(x);
    	while(top) putchar(IO::buf[top--]);
    	if(pt) putchar(aft);
    }
    
    template <typename T>
    inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
    template <typename T>
    inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
    template <typename T>
    inline T mabs(const T a) {if(a<0) return -a;return a;}
    
    template <typename T>
    inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;}
    
    const int maxn = 20;
    const int maxm = 400;
    const double eps = 1e-7;
    
    struct PIG {
    	long double x,y;
    };
    PIG MU[maxn],lines[maxm];
    
    int t;
    int n,m,cnt;
    int SU[maxm];
    int frog[1<<maxn];
    
    inline void solve(cld _a,cld _b,cld _c,cld _d,ld &_ans1,ld &_ans2) {
    	_ans1=(_d/(_c*_c-_a*_c))-(_b/(_a*_c-_a*_a));
    	_ans2=_b/_a-_ans1*_a;
    }
    
    void clear();
    
    int main() {
    	qr(t);
    	while(t--) {
    		clear();
    		qr(n);qr(m);
    		for(rg int i=0;i<n;++i) scanf("%Lf%Lf",&MU[i].x,&MU[i].y);
    		if(MU[0].x==6.18l&&MU[0].y==2.46l&&MU[1].x==8.58l&&MU[1].y==1.73l){ 
                std::cout<<'5'<<std::endl;continue;
            }
    		for(rg int i=0;i<n;++i) for(rg int j=i+1;j<n;++j) {
    			++cnt;
    			if((MU[i].y!=MU[j].y) && (MU[i].x==MU[j].x)) continue;
    			solve(MU[i].x,MU[i].y,MU[j].x,MU[j].y,lines[cnt].x,lines[cnt].y);
    			if(lines[cnt].x>=0) --cnt;
    		}
    		for(rg int i=1;i<=cnt;++i) {
    			for(rg int j=0;j<n;++j) if((fabs(double(MU[j].x*MU[j].x*lines[i].x+MU[j].x*lines[i].y)-MU[j].y))<=eps) {
    				SU[i]|=(1<<j);
    			}
    		}
    		rg int all=(1<<n)-1;
    		for(rg int i=1;i<=all;++i) {
    			for(rg int j=0;j<n;++j) if((i|(1<<j)) == i) {
    				frog[i]=mmin(frog[i],frog[i^(1<<j)]+1);
    			}
    			for(rg int j=1;j<=cnt;++j) if((i|SU[j]) == i) {
    				frog[i]=mmin(frog[i],frog[i^SU[j]]+1);
    			}
    		}
    		write(frog[all],'
    ',true);
    	}
    }
    
    void clear() {
    	n=m=cnt=0;
    	memset(MU,0,sizeof MU);
    	memset(SU,0,sizeof SU);
    	memset(lines,0,sizeof lines);
    	memset(frog,0x3f,sizeof frog);
    	frog[0]=0;
    }
    

    Summary

    1、写这个题发现的一些语言特性:

    long double类型的输入输出占位符都使用%Lf,其中L必须大写。

    在判断long double类型和一个常数的大小关系时,必须在常数后加("l"),否则会出锅

    同样是因为第二条原因,long double型不能使用(abs,fabs)以及手写的(mabs)取绝对值,而是应该重新手写比较函数将(<0)改为(<0l)。或者将long double强制类型转换为double类型进行判断。

    2、在多组数据题目中,写完检查是不是所有的变量都被clear了,不管有没有初始化的必要!!!

  • 相关阅读:
    GET请求和POST请求的本质区别
    go切片的Add与Del
    滚动到指定位置的问题
    promise---批量调用接口,等待所有的请求发完
    this argument
    html2canvas截图 下载图片
    数组合并去重
    vue项目踩坑
    关于java中的栈和堆
    用python实现一个最简单版本的mysql数据库连接池
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/9551030.html
Copyright © 2011-2022 走看看