当前位置: 首页 > news >正文

5.4学习记录

今天的目标是复习+刷过往的提高课的DP题目:重点是数位DP,状态压缩DP,然后去做一些新的DP题目

然后明天的任务就是把DP的题目汇总,复习一些疑难的问题

方格取数:

题目背景

NOIP 2000 提高组 T4

题目描述

设有 N×N 的方格图 (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。如下图所示(见样例):

某人从图的左上角的 A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B 点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。

输入格式

输入的第一行为一个整数 N(表示 N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 表示输入结束。

输出格式

只需输出一个整数,表示 2 条路径上取得的最大的和。


一次的走法:

f[i1][j1][i2][j2] 表示两个路径,从(1,1)走到(i1,j1),从(1,1)走到(i2,j2)

什么时候两个路径会重合尼?:

显然当他们重合的时候有i1+j1==i2+j2这个是很显然的

所以我们可以用k表示总步数

f[k][i1][i2]表示我们需要考虑的情况(其他状态下一步他们两个一定不会重合)

j1=k-i1    j2=k-i2

下面看我们的状态转移的方程:
四个推广的方向:
1下2下,1下2右,1右2右,1右2下

当然要判断一下这两个点走完过后不能够到同一个位置

如果重合的话,只加一次w(i,j)

如果没有重合的话,就要加上两次

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=15;

int w[N][N];

int f[N*2][N][N];

int n;

int main(){

    cin>>n;

    while(1){

        int x,y,c;

        cin>>x>>y>>c;

        if(x==0&&y==0&&c==0)break;

        w[x][y]=c;

    }

    for(int k=2;k<=2*n;k++){

        for(int i=1;i<=n;i++){

            for(int j=1;j<=n;j++){

                int ii=k-i;

                int jj=k-j;

                if(ii<1||jj<1)continue;

                if(ii>n||jj>n)continue;

                int val=w[i][ii];

                if(i!=j)val+=w[j][jj];

                f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j-1]+val);

                f[k][i][j]=max(f[k][i][j],f[k-1][i][j-1]+val);

                f[k][i][j]=max(f[k][i][j],f[k-1][i][j]+val);

                f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j]+val);

            }

        }   

    }

    cout<<f[2*n][n][n];

}

导弹防御系统:

为了对抗附近恶意国家的威胁,RR 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 33 和高度为 44 的两发导弹,那么接下来该系统就只能拦截高度大于 44 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包含整数 nn,表示来袭导弹数量。

第二行包含 nn 个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0n=0 时,表示输入终止,且该用例无需处理。

输出格式

对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=55;

int a[N];

int n;

int up[N];

int down[N];

int ans=0x3f3f3f3f;

void dfs(int pos,int pu,int pd){

    if(pu+pd>=ans)return;

    if(pos==n+1){

        ans=min(ans,pu+pd);

        return;

    }

    

    //放入上升序列

    int k=0;

    while(k<pu&&a[pos]<=up[k])k++;

    int back=up[k];

    up[k]=a[pos];

    if(k>=pu){

        dfs(pos+1,pu+1,pd);

    }else dfs(pos+1,pu,pd);

    up[k]=back;

    

    //放入下降序列

    k=0;

    while(k<pd&&a[pos]>=down[k])k++;

    back=down[k];

    down[k]=a[pos];

    if(k>=pd){

        dfs(pos+1,pu,pd+1);

    }else dfs(pos+1,pu,pd);

    down[k]=back;

}

int main(){

    while(scanf("%d",&n),n!=0){

        for(int i=1;i<=n;i++)scanf("%d",&a[i]);

        ans=0x3f3f3f3f;

        memset(up,0,sizeof up);

        memset(down,0,sizeof up);

        dfs(1,0,0);

        cout<<ans<<endl;

    }

}

最长公共子序列:

(融合了LIS的nlogn的LCS)

题目描述

给出 1,2,…,n 的两个排列 P1​ 和 P2​ ,求它们的最长公共子序列。

输入格式

第一行是一个数 n。

接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

#include<iostream>

#include<cstdio>

using namespace std;

int a[100001],b[100001],map[100001],f[100001];

int main()

{

int n;

cin>>n;

for(int i=1;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}

for(int i=1;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}

int len=0;

f[0]=0;

for(int i=1;i<=n;i++)

{

int l=0,r=len,mid;

        int ans=0;

if(map[b[i]]>f[len])f[++len]=map[b[i]];//f存的是长度为i的最后一个数的位置,如果和b[i]相等的a[i],可以接在最长的len后面

else //不能接在最后面,就找到所有已有长度中最长的,满足f[ans]<map[b[i]],以此去更新f[ans+1]

{

while(l<=r)

{

    mid=(l+r)/2;

    if(f[mid]>map[b[i]]){//不能接在后面

                r=mid-1;

            }

else if(f[mid]<map[b[i]]){//可以接在f[mid]后面,去找找更长的

                ans=mid;

                l=mid+1;

            }

}

f[ans+1]=min(map[b[i]],f[ans+1]);//可以接在ans后面,尝试去更新ans+1长度的最后一个值的更小的位置

      }

    }

    cout<<len;

    return 0;

}

最长上升子序列:


题目描述

这是一个简单的动规板子题。

给出一个由 n(n≤5000) 个不超过 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n,表示序列长度。

第二行有 n 个整数,表示这个序列。

#include <bits/stdc++.h>

using namespace std;

const int N=5010;

int n;

int a[N];

int f[N];

int main(){

    cin>>n;

    for(int i=1;i<=n;i++){

        scanf("%d",&a[i]);

    }

    int len=0;

    memset(f,0x3f,sizeof f);

    f[0]=0;

    for(int i=1;i<=n;i++){

        int l=0,r=len;

        int ans=0;

        if(f[len]<a[i])f[++len]=a[i];//可以接在最后面

        else{//尝试优化内部结构

            while(l<=r){

                int mid=(l+r)/2;

                if(f[mid]>=a[i]){

                    r=mid-1;

                }else if(f[mid]<a[i]){

                    ans=mid;

                    l=mid+1;

                }

            }    

            //可以接在ans的后面

            f[ans+1]=min(f[ans+1],a[i]);

        }

    }

    

    cout<<len;

}

货币系统:


题目背景

NOIP2018 提高组 D1T2

题目描述

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 n、面额数组为 a[1..n] 的货币系统记作 (n,a)。

在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。

两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 m。

输入格式

输入文件的第一行包含一个整数 T,表示数据的组数。

接下来按照如下格式分别给出 T 组数据。 每组数据的第一行包含一个正整数 n。接下来一行包含 n 个由空格隔开的正整数 a[i]。

输出格式

输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

代码:
#include <bits/stdc++.h>

using namespace std;

const int N=150;

int a[N];

int f[300000];

int n;

int t;

bool st[N];

int main(){

    scanf("%d",&t);

    while(t--){

        scanf("%d",&n);

        for(int i=1;i<=n;i++)scanf("%d",&a[i]);

        sort(a+1,a+1+n);

        memset(f,0,sizeof f);

        f[0]=1;

        int res=0;

        for(int i=1;i<=n;i++){

            if(f[a[i]])continue;

            res++;

            for(int j=a[i];j<=a[n];j++){

                f[j]=f[j]|f[j-a[i]];

            }

        }

        cout<<res<<endl;

    }

}

有依赖的背包问题

有 N个物品和一个容量是 V 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 ii,体积是 vivi,价值是 wiwi,依赖的父节点编号是 pipi。物品的下标范围是 1…N1…N。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N,VN,V,用空格隔开,分别表示物品个数和背包容量。

接下来有 NN 行数据,每行数据表示一个物品。
第 ii 行有三个整数 vi,wi,pivi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1pi=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式

输出一个整数,表示最大价值。

数据范围

1≤N,V≤1001≤N,V≤100
1≤vi,wi≤1001≤vi,wi≤100

父节点编号范围:

内部结点:1≤pi≤N1≤pi≤N;

根节点 pi=−1pi=−1

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=110;

int f[N][N];

int v[N],w[N];

unordered_map<int,vector<int>>e;

int n,m;

void dfs(int u){

    for(int to:e[u]){

        dfs(to);

        for(int j=m;j>=w[u];j--){//循环 子树 使用体积,最少是w[u]

            for(int k=w[u];k<=j;k++)//循环 根 用的体积,最少是w[u]

                f[u][j]=max(f[u][j],f[u][k]+f[to][j-k]);//f[u][k]+f[to][j-k]

        }

    }

    

    for(int j=w[u];j<=m;j++)f[u][j]+=v[u];

    

}

int main(){

    scanf("%d%d",&n,&m);

    int root=0;

    for(int i=1;i<=n;i++){

        int a,b,c;

        scanf("%d%d%d",&b,&a,&c);

        v[i]=a;

        w[i]=b;

        if(c==-1)root=i;

        else e[c].push_back(i);

    }

    dfs(root);

    cout<<f[root][m];

}

状态压缩dp基础:

二进制枚举

#include<bits/stdc++.h>

using namespace std;

int n;

void op(int x){

    for(int i=0;i<n;i++){

        if((x>>i)&1){

            cout<<i+1<<' ';

        }

    }

    

}

int main(){

    scanf("%d",&n);

    for(int i=0;i<1<<n;i++){

        op(i);

        if(i+1!=1<<n)cout<<endl;

    }

}

棋盘型状态压缩dp

蒙德里安的梦想:


求把 N×MN×M 的棋盘分割成若干个 1×21×2 的长方形,有多少种方案。

例如当 N=2,M=4N=2,M=4 时,共有 55 种方案。当 N=2,M=3N=2,M=3 时,共有 33 种方案。

如下图所示:

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 NN 和 MM。

当输入用例 N=0,M=0N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

#include <bits/stdc++.h>

using namespace std;

// 定义常量 N 表示棋盘的列数相关的一个上限值,M 表示所有可能的状态数(2 的 N 次方)

const int N = 12, M = 1 << N;  

// f 数组用于动态规划,第一维表示列数,第二维表示每一列的所有可能状态,存储方案数

long long f[N][M];  

// st 数组用于存储每种状态是否有奇数个连续的 0,若有奇数个连续 0 则该状态无效,否则为 true

bool st[M];  

// state 是一个二维数组(这里用 vector<vector<int>> 实现),用于记录每一种状态下合法的前一列状态

vector<vector<int>> state(M);  

int m, n;  // m 表示棋盘的列数,n 表示棋盘的行数

int main() {

    // 持续读入 n 和 m,只要 n 和 m 不同时为 0,就继续执行循环体

    while (cin >> n >> m, n || m) {

        // 遍历所有可能的状态(用二进制数表示,范围是 0 到 2 的 n 次方减 1)

        for (int i = 0; i < (1 << n); i++) {

            int cnt = 0;  // 用于记录连续的 0 的个数

            bool isValid = true;  // 标记某种状态是否没有奇数个连续的 0,初始设为 true

            // 遍历当前状态的每一位(对应棋盘的每一行),判断是否存在奇数个连续的 0

            for (int j = 0; j < n; j++) {

                // 判断状态 i 的二进制表示中第 j 位是否为 1

                if ((i >> j) & 1) {  

                    // 如果当前位为 1,检查前面连续的 0 的个数是否为奇数

                    if (cnt & 1) {

                        // 如果是奇数个连续的 0,则该状态不合法,标记为 false 并跳出循环

                        isValid = false;

                        break;

                    }

                    // 重置连续 0 的计数器,因为当前位为 1,新的连续 0 计数开始

                    cnt = 0;

                }

                else {

                    // 当前位为 0,连续 0 的计数器加 1

                    cnt++;

                }

            }

            // 检查最后一段连续的 0 的个数是否为奇数,如果是则状态不合法

            if (cnt & 1)  

                isValid = false;

            // 将状态 i 是否有奇数个连续 0 的情况记录到 st 数组中

            st[i] = isValid;

        }

        // 遍历第 i 列的所有状态(用二进制数表示,范围是 0 到 2 的 n 次方减 1)

        for (int j = 0; j < (1 << n); j++) {

            // 清空上一次操作遗留的状态,避免影响本次状态的判断

            state[j].clear();

            // 遍历第 i - 1 列的所有状态(用二进制数表示,范围是 0 到 2 的 n 次方减 1)

            for (int k = 0; k < (1 << n); k++) {

                // 判断第 i 列的状态 j 和第 i - 1 列的状态 k 是否没有冲突(按位与为 0)

                // 并且它们合并后的状态是合法的(st[j | k] 为 true)

                if ((j & k) == 0 && st[j | k])

                    // 如果满足条件,则将第 i - 1 列的状态 k 记录为第 i 列状态 j 的合法前一状态

                    state[j].push_back(k);  

            }

        }

        // 动态规划部分开始,初始化 f 数组为 0

        memset(f, 0, sizeof f);  

        // 初始化第 0 列,状态为 0 的方案数为 1

        f[0][0] = 1;

        // 遍历每一列(从第 1 列到第 m 列)

        for (int i = 1; i <= m; i++) {

            // 遍历当前列(第 i 列)的所有状态 j(用二进制数表示,范围是 0 到 2 的 n 次方减 1)

            for (int j = 0; j < (1 << n); j++) {  

                // 遍历第 i - 1 列中与当前状态 j 合法的所有状态 k

                for (auto k : state[j])  

                    // 当前列状态 j 的方案数等于上一列(第 i - 1 列)状态 k 的方案数之和

                    f[i][j] += f[i - 1][k];    

            }

        }

        // 输出第 m 列,状态为 0 的方案数

        cout << f[m][0] << endl;

    }

    return 0;

}

最短hamilton路径:

题目描述

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i−1 到 j−1 的距离(记为 a[i−1,j−1])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

代码:
#include <bits/stdc++.h>

using namespace std;

const int N=21;

int g[N][N];

int f[1<<N][N];

int n;

int main(){

    cin>>n;

    for(int i=0;i<n;i++)

        for(int j=0;j<n;j++)

            cin>>g[i][j];

    memset(f,0x3f,sizeof f);

    f[1][0]=0;

    int m=1<<n;

    for(int i=0;i<m;i++){//遍历所有集合

        for(int j=0;j<n;j++){//从集合中的j

            if((1<<j)&i){//j在i中

                for(int k=0;k<n;k++){//走到k

                    if(k!=j&&(1<<k)&i){//j不等于k,并且k在i中

                        f[i][j]=min(f[i][j],f[i-(1<<j)][k]+g[k][j]);//从k转移过来

                    }

                }

            }

        }

    }

    cout<<f[m-1][n-1];

    return 0;

}

玉米田:


农夫约翰的土地由 M×NM×N 个小方格组成,现在他要在土地里种植玉米。

非常遗憾,部分土地是不育的,无法种植。

而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。

现在给定土地的大小,请你求出共有多少种种植方法。

土地上什么都不种也算一种方法。

输入格式

第 11 行包含两个整数 MM 和 NN

第 2..M+12..M+1 行:每行包含 NN 个整数 00 或 11,用来描述整个土地的状况,11 表示该块土地肥沃,00 表示该块土地不育。

输出格式

输出总种植方法对 108108 取模后的值。

代码:
#include<bits/stdc++.h>

using namespace std;

const int mod=1e9;

const int N=15;

int f[N][1<<N];

int n,m;

vector<int> t;

unordered_map<int,vector<int>> e;

int g[N];

int op(int x){

    return ((x+mod)%mod+mod)%mod;

}

bool check(int x){//连续的两个不能都是1

    if(x&(x<<1))return false;

    return true;

}

int main(){

    cin>>n>>m;

    for(int i=0;i<(1<<m);i++){

        if(check(i))t.push_back(i);

    }

    for(int i:t){

        for(int j:t){//和j匹配的i

            if((i&j)==0)e[i].push_back(j);

        }

    }

    

    for(int i=1;i<=n;i++){

        for(int j=0;j<m;j++){

            int tmp;

            cin>>tmp;

            if(tmp==0){

                g[i]+=(1<<j);

            }

        }

    }//读入状态

    

    f[0][0]=1;

    for(int i=1;i<=n+1;i++){

        for(int j:t){

            if(j&g[i])continue;//没有种在坏地上

            for(int k:e[j]){

                if(k&g[i-1])continue;//上一行也合法

                f[i][j]=op(f[i][j]+f[i-1][k]);

            }

        }

    }

    cout<<f[n+1][0];

}

炮兵阵地:


题目描述

司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。

一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 N 和 M

接下来的 N 行,每一行含有连续的 M 个字符,按顺序表示地图中每一行的数据。

输出格式

一行一个整数,表示最多能摆放的炮兵部队的数量。

代码:
#include <bits/stdc++.h>

using namespace std;

const int N=110,M=(1<<10);

int s[N]={0},cnt[M];

int n,m;

int state[M];

int f[2][M][M]={0};

bool check (int st){

    for(int i=0;i<10;i++){

        if(((st>>i)&1)&&(((st>>(i+1))&1)||((st>>(i+2))&1)))//3个连续的地方只有1个1

            return false;

    }

    return true;

}

int count(int st){

    int res=0;

    for(int i=0;i<11;i++){

        if((st>>i)&1)res++;

    }

    return res;

}

int main(){

    cin>>n>>m;

    for(int i=1;i<=n;i++){

        for(int j=0;j<m;j++){

            char tmp;

            cin>>tmp;

            if(tmp=='H')

                s[i]+=(1<<j);

        }

    }

    

    int cn=0;

    int t=1<<m;

    

    for(int i=0;i<t;i++){//遍历所有的状态

        if(check(i)){//i状态合法

            state[cn++]=i;//将其加入到state种

            cnt[i]=count(i);//同时计算状态中的炮兵数量

        }

    }

    

    for(int i=1;i<=n+2;i++)//遍历到n+2,最后的答案是n+2行的状态是0

        for(int j=0;j<cn;j++)//第i-1行的状态

            for(int k=0;k<cn;k++)//第i行的状态

                for(int u=0;u<cn;u++){//i-2行的状态

                    int a=state[k],b=state[j],c=state[u];

                    

                    if((a&c)||(b&c)||(a&b))continue;//3行里面不能有炮兵重叠

                    if(s[i]&a||s[i-1]&b)continue;//山地冲突

                    if(i>=2&&s[i-2]&c)continue;//山地冲突

                    f[i&1][j][k]=max(f[i&1][j][k],f[i-1&1][u][j]+cnt[a]);

                }

    cout<<f[n+2&1][0][0];//state[0]对应状态0000000000

}

愤怒的小鸟:


题目背景

NOIP2016 提高组 D2T3

题目描述

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

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

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

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

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

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

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

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

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

这款神奇游戏的每个关卡对 Kiana 来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在【输入格式】中详述。

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

输入格式

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

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

如果 m=0,表示 Kiana 输入了一个没有任何作用的指令。

如果 m=1,则这个关卡将会满足:至多用 n/3+1⌉ 只小鸟即可消灭所有小猪。

如果 m=2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少 n/3⌋ 只小猪。

保证 1≤n≤180≤m≤20<xi,yi<10,输入中的实数均保留到小数点后两位。

上文中,符号 c 和 c 分别表示对 c 向上取整和向下取整,例如:⌈2.1⌉=⌈2.9⌉=⌈3.0⌉=⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3

输出格式

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

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

代码:

#include<bits/stdc++.h>

using namespace std;

typedef pair<double,double> pdd;

const int N=20;

bool same(double x,double y){

    if(fabs(x-y)<1e-6)return 1;

    return 0;

}

int t,n,m;

pdd a[N];

int f[N][N];

int g[1<<N];

int main(){

    scanf("%d",&t);

    while(t--){

        cin>>n>>m;

        for(int i=0;i<n;i++){

            cin>>a[i].first>>a[i].second;

        }

        memset(f,0,sizeof f);

        

        for(int i=0;i<n;i++){

            f[i][i]=1<<i;

            

            for(int j=0;j<n;j++){

                if(i==j)continue;

                

                double x1=a[i].first,y1=a[i].second;

                double x2=a[j].first,y2=a[j].second;

                if(same(x1,x2))continue;

                

                double aa=(y1/x1-y2/x2)/(x1-x2);

                if(aa>0)continue;

                double bb=y1/x1-aa*x1;

                int st=0;

                for(int k=0;k<n;k++){

                    double x=a[k].first,y=a[k].second;

                    if(same(aa*x*x+bb*x,y)){

                        st+=1<<k;

                    }

                }

                f[i][j]=st;//表示从依据i和j建立的抛物线覆盖的点集合

            }

        }

    

        memset(g,0x3f,sizeof g);

        g[0]=0;

        for(int i=0;i<1<<n;i++){

            int x=0;

            for(int j=0;j<n;j++){//找到没有被覆盖的点

                if(i>>j&1){

                    continue;

                }else {

                    x=j;

                    break;

                }

            }

            for(int j=0;j<n;j++){

                g[i|f[x][j]]=min(g[i|f[x][j]],g[i]+1);

            }

        }

        cout<<g[(1<<n)-1]<<endl;

        

    }

    

}

明天从区间Dp开始看

相关文章:

  • 基于springboot3+mybatis整合,使用mybatisPlus插件自动完成简单的 增删改查操作
  • 2021年第十二届蓝桥杯省赛B组C++题解
  • 编程学习思考
  • vulkanscenegraph显示倾斜模型(6.4)-多线程下的记录与提交
  • Temp Mail 1.7.0 | 创建和管理临时邮箱,防止垃圾邮件骚扰,保护隐私安全
  • Javase 基础加强 —— 04 集合2.0
  • MIT 6.S081 2020 Lab2 system calls 个人全流程
  • 运维--计划任务
  • 深入理解Java垃圾回收机制
  • chrome 浏览器怎么不自动提示是否翻译网站
  • 「一针见血能力」的终极训练手册
  • PATHWAYS: 用于机器学习的异步分布式数据流
  • 广东省考备考(第一天5.4)—判断(对称)
  • 【AI提示词】 复利效应教育专家
  • USB Type-C是不是全方位优于其他USB接口?
  • 什么是JDBC
  • Oracle OCP认证考试考点详解083系列05
  • PISI:眼图1:眼图相关基本概念
  • PCB实战篇
  • 一格一格“翻地毯”找单词——用深度优先搜索搞定单词搜索
  • 贵州黔西市游船倾覆事故最后一名失联人员被找到,但已无生命体征
  • 两千万粉丝网红“大LOGO”带货茶叶被指虚假宣传,涉事茶企被立案调查
  • 看着不爽就滚蛋!郑州大学第一附属医院一科室公众号被曝运营人员辱骂他人
  • 市场驱动的系统改造:丹麦零弃风经验研究
  • “五一”前两日湖北20多家景区实施限流
  • 中青报:“爸妈替我在线相亲”,助力还是越界?