洛谷2831(NOIP2016)[愤怒的小鸟]--状压DP

本文介绍了解决洛谷2831问题的一种算法——状压动态规划(DP)。该方法通过定义状态gi,j表示经过两点的二次函数能覆盖的节点状态,fs表示已覆盖节点状态,实现求最小数量的二次函数覆盖所有坐标点的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【链接】
洛谷2831

【题目大意】

求经过所有的坐标的二次函数的个数最小值。

【解题报告】

状压DP。

定义 gi,j 表示经过第 i 个点和第j个点的二次函数所能经过的节点状态。

定义 fs 表示目前已经经过的节点状态。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20,maxm=(1<<18)+5;
int T,n,m,f[maxm],g[maxn][maxn];
struct wjd
{
    double x,y;
    bool operator < (const wjd &a) const{
        return x<a.x;
    }
}a[maxn];
int Fcmp(double x,double y) {if (fabs(x-y)<1e-10) return 0; if (x<y) return -1; return 1;}
void Work()
{
    scanf("%d%d",&n,&m); m=(1<<n)-1;
    for (int i=0; i<n; i++) scanf("%lf%lf",&a[i].x,&a[i].y);
    sort(a,a+n);
    memset(g,0,sizeof(g));
    for (int i=0; i<n-1; i++)
     for (int j=i+1; j<n; j++)
      if (Fcmp(a[i].x,a[j].x))
       {
        double A,B; A=(a[i].y-a[i].x*a[j].y/a[j].x)/(a[i].x*a[i].x-a[i].x*a[j].x);
        if (Fcmp(A,0)>=0) continue; B=(a[i].y-A*a[i].x*a[i].x)/a[i].x;
        for (int k=0; k<n; k++) if (!Fcmp(A*a[k].x*a[k].x+B*a[k].x,a[k].y)) g[i][j]|=1<<k;
       }
    memset(f,63,sizeof(f)); f[0]=0;
    for (int s=0; s<m; s++)
     for (int i=0; i<n; i++)
      if (!(s&(1<<i)))
       {
        f[s|(1<<i)]=min(f[s|(1<<i)],f[s]+1);
        for (int j=i+1; j<n; j++)
         f[s|g[i][j]]=min(f[s|g[i][j]],f[s]+1);
       }
    printf("%d\n",f[m]);
}
int main()
{
    freopen("2831.in","r",stdin);
    freopen("2831.out","w",stdout);
    scanf("%d",&T);
    while (T--) Work();
    return 0;
}
<think>嗯,用户问的是NOIP2016提高组第三题“海港”的解题思路和算法分析。首先,我得回忆一下这个题目的具体要求。根据引用里的信息,题目大概是关于处理到达海港的船只,每艘船有乘客的国籍数据,需要实时统计最近24小时内的不同国籍数量。用户可能想要一个高效的算法,因为直接遍历所有乘客肯定会超时。 首先,题目中的时间窗口是24小时,也就是86400秒。每次新船到达时,需要移除所有超过24小时的旧数据,然后统计剩下的国籍数。直接每次检查所有记录显然不行,时间复杂度会很高,特别是当数据量大时。 那如何处理时间窗口呢?滑动窗口或者队列结构可能有用。因为每次新船到达时,只需要移除时间超过当前时间-86400的记录,这样可以用队列来维护乘客记录,保证队列里的时间都在窗口内。这样可以避免每次全量检查,只需从队头开始移除过期的。 然后统计国籍数量。这里的问题是如何高效地维护国籍的出现次数。每添加一个乘客,对应国籍的计数加1;当移除过期乘客时,对应国籍的计数减1,当减到0时,总的国家数减1。这样需要维护一个哈希表或字典来记录每个国籍的数量。 但乘客是按船到达的,而每艘船可能有多个乘客。所以可能需要将每个乘客的信息分开存储,或者按批次处理。比如,队列中的每个元素是一批乘客(同一艘船的),并记录他们的到达时间和国籍列表。当处理新船时,先检查队列头部的船是否超出时间窗口,如果是,就依次将这些乘客的国籍计数减少,直到所有过期的船都被移除。 这样,每次处理新船时,需要先处理队列中过期的船,然后添加当前船的乘客,并更新国家计数。这样时间复杂度应该是线性的,因为每个乘客只会被加入和移除一次队列。 可能的难点是,如何处理同一时间多艘船的情况,或者时间跨度较大的情况。比如,如果有很多船都在时间窗口内,那么队列中的元素可能很多,但每次处理时只需要移除超过时间窗口的部分,而不是全部。 举个例子,假设当前时间是ti,那么队列中保存的是所有到达时间大于ti-86400的船。当新船到达时间ti时,我们需要将队列中所有时间<=ti-86400的船移除,并更新国籍计数。然后,将当前船的乘客加入队列,并更新计数。这样就能保证队列里的数据都在窗口内,统计国家数的时候只需查看当前哈希表中计数大于0的国家数量。 这样,主要的数据结构是队列,每个元素包含时间和该船的所有乘客国籍,以及一个哈希表记录国籍的出现次数,还有一个变量记录当前的国家数量。 另外,需要考虑时间ti的范围。题目中ti可能很大,但每次只需要比较相对时间差是否超过86400,所以不需要担心时间溢出问题。 可能的优化点:队列中的每个元素可以存储该船的时间和国籍列表,这样处理过期船时,逐个出队,并减少对应国籍的计数。当国籍计数减到0时,总的国家数减1。 现在,总结一下步骤: 1. 初始化一个队列,用来保存每艘船的到达时间和乘客国籍列表。 2. 维护一个哈希表(字典)记录每个国籍的出现次数。 3. 维护一个变量count记录当前的有效国家数。 4. 对于每艘新到达的船: a. 检查队列头部的船是否超过当前时间ti-86400。如果是,依次出队,并减少对应国籍的计数。如果某个国籍的计数减到0,则count减1。 b. 将当前船的乘客国籍加入哈希表,如果该国籍之前计数为0,则count加1。 c. 将当前船的信息入队。 d. 输出当前的count值。 这样,每个乘客的国籍被处理两次(入队和出队),时间复杂度是O(K),其中K是所有乘客的总数,应该可以通过题目中的时间限制。 需要注意的点: - 处理时间时,要确保比较的是船的时间是否在窗口内。例如,当前船的时间是ti,那么窗口的有效范围是ti-86400+1到ti?或者ti-86400到ti?需要看题目具体要求,比如,是否包括刚好24小时前的船。比如,题目中的描述可能是“在24小时内的”,也就是ti - t <= 86400,或者严格小于? 根据引用3中的样例输入,比如第一艘船时间1,第二艘船时间4,第三艘时间10。假设样例输出是3、4、4。这说明当第三艘船时间为10时,前两艘船是否在窗口内取决于窗口是否包含10-86400到10。但样例中的时间可能较小,所以需要明确比较条件是 ti - prev_t >=86400吗? 比如,假设当前处理的时间是ti,那么队列中保留的是那些船的时间 > ti -86400。当处理新的ti时,将所有队列中时间<=ti -86400的船移除。 例如,ti=86400,那么移除所有时间<=0的船。ti=86401,则移除时间<=1的船。这样,每个船在队列中的时间必须严格大于ti-86400,否则会被移除。 所以在代码中,当处理新船时,先循环检查队列头部的船的时间是否<=当前ti -86400,如果是,就出队,并减少该船所有乘客的国籍计数,如果国籍计数减到0,则总count减1。然后处理当前船,将乘客国籍加入哈希表,若加入前计数为0,则count加1,然后将该船的信息入队。 这样,每个乘客被处理两次,时间复杂度是线性的。 所以,这样的算法应该可以满足题目的要求,避免超时。 现在,用户可能还需要具体的代码实现,或者更详细的步骤解释。比如,如何处理队列中的每个船的数据结构,如何记录每个船的乘客国籍列表,以及如何维护哈希表和count变量。 例如,在Python中,可以用一个deque作为队列,每个元素是(时间,国籍列表)。哈希表可以用字典。然后遍历每个新船,先处理过期数据,再处理当前数据。 可能的代码结构: from collections import deque q = deque() nationality_count = {} current_count = 0 n = int(input()) for _ in range(n): ti, ki, *nationalities = map(int, input().split()) # 处理过期船只 while q and q[0][0] <= ti - 86400: old_t, old_nations = q.popleft() for nation in old_nations: nationality_count[nation] -= 1 if nationality_count[nation] == 0: current_count -= 1 # 处理当前船的乘客 new_nations = nationalities[:ki] for nation in new_nations: if nationality_count.get(nation, 0) == 0: current_count += 1 nationality_count[nation] = nationality_count.get(nation, 0) + 1 q.append( (ti, new_nations) ) print(current_count) 不过,这里需要注意,当处理旧的船只时,可能需要遍历该船的所有国籍,并逐个减少计数。例如,假设一个船有多个相同国籍的乘客,那么每个乘客都需要处理,不能合并,因为每个乘客的国籍都会影响计数。 但是,如果同一艘船上有多个相同国籍的乘客,那么在出队时,每个都要减少一次计数。比如,如果一艘船有3个国籍为2的乘客,那么在移除该船时,国籍2的计数减3。当减到0时,current_count减1。 但原题中的输入中,每艘船的ki个乘客可能有重复的国籍。例如,引用3中的样例输入: 3 1 4 4 1 2 2 2 2 2 3 10 1 3 其中第二艘船有ki=2,两个乘客国籍都是3。 那么在处理这艘船时,加入哈希表中国籍3的计数是加2。当该船被移除时,国籍3的计数减2,如果原来计数是2,减后变为0,则current_count减1。 所以,在队列中的每个元素需要保存该船的所有乘客国籍的列表,包括重复的国籍。例如,第一艘船的国籍列表是[4,1,2,2],这样出队时,每个国籍都要处理一次。这样,如果同一艘船中有多个相同国籍的乘客,在出队时会逐个减少计数,可能导致多次减到0的情况,但实际上只有当最后一个减少到0时才需要调整current_count。 比如,假设国籍2的当前计数是3,在出队时减去2次,变成1,那么current_count不需要变化。但如果减到0的话,就需要减少current_count。 所以,代码中需要确保每个乘客的国籍都被处理,这样哈希表的计数正确,而current_count的增减只在计数从0到1或1到0的时候触发。 所以,上述代码中的处理是正确的,因为当处理旧船时,遍历该船的所有国籍,逐个减少计数,如果某个nation的计数减到0,则current_count减1。 但是在加入新船时,可能多个相同国籍的乘客被加入,此时,只有当该国籍的计数从0变为1时,current_count才加1。其他情况下(比如原本已经有该国籍的计数),current_count不变。 这样,current_count始终维护的是当前不同国籍的数量。 这个逻辑是正确的。 综上,解题思路是使用队列维护时间窗口内的船只,哈希表维护国籍的出现次数,并在处理每艘新船时,先移除过期的船,再添加当前船的乘客国籍,最后输出当前的国籍数量。</think>### NOIP2016提高组第三题'海港'解题思路 #### 问题分析 题目要求统计每艘船到达后,过去24小时内所有乘客的不同国籍数量[^3]。核心挑战在于高效处理时间窗口内的数据,避免暴力遍历导致超时。 #### 算法设计 1. **滑动窗口**:使用队列维护时间窗口内的船只信息。每次新船到达时,移除所有超过24小时的旧数据。 2. **哈希表计数**:记录每个国籍的乘客数量。当某国籍的计数从0变为1时,总国家数加1;当计数减到0时,总国家数减1。 #### 具体步骤 1. **初始化队列和哈希表**: - 队列存储船只的到达时间和乘客国籍列表。 - 哈希表`nationality_count`记录国籍出现次数。 - 变量`current_count`表示当前有效国家数。 2. **处理每艘船**: - **移除过期数据**:检查队列头部船只的时间是否超过当前时间$t_i - 86400$,若过期则出队,并减少对应国籍计数。若某国籍计数归零,则`current_count`减1。 - **添加新数据**:将当前船的乘客国籍加入哈希表。若国籍计数从0变为1,则`current_count`加1。 - **输出结果**:每处理完一艘船,输出`current_count`。 #### 代码示例 ```python from collections import deque q = deque() nationality_count = {} current_count = 0 n = int(input()) for _ in range(n): data = list(map(int, input().split())) ti, ki = data[0], data[1] nations = data[2: 2 + ki] # 移除过期船只 while q and q[0][0] <= ti - 86400: old_t, old_nations = q.popleft() for nation in old_nations: nationality_count[nation] -= 1 if nationality_count[nation] == 0: current_count -= 1 # 处理当前船只 for nation in nations: if nationality_count.get(nation, 0) == 0: current_count += 1 nationality_count[nation] = nationality_count.get(nation, 0) + 1 q.append((ti, nations)) print(current_count) ``` #### 复杂度分析 - 时间复杂度:$O(K)$,其中$K$为总乘客数,每个乘客仅被加入和移除队列一次。 - 空间复杂度:$O(K)$,队列和哈希表存储所有乘客信息。 #### 优化点 1. **合并重复国籍**:若同一艘船的乘客国籍重复,可合并计数减少操作次数。 2. **延迟删除**:在极端时间跨度下,队列可能存储大量过期数据,但实际操作中队列长度受窗口限制,无需额外优化[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值