木棒交叉 Intersect

2023-10-12 01:50
文章标签 交叉 intersect 木棒

本文主要是介绍木棒交叉 Intersect,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原题大意如下:

        有 n 根木棒(木棒可以理解为无限长 其实就是一根直线),n根木棒可能互相平行也可能相交,但不可能存在三根即以上的木棒交于同一点。然后就是输入好几个 n 和 b 让你判断这n根木棒存不存在一种摆放方式让它们有 b 个交点。

        然后数据的范围我记不清了,等明天回学校把原题找出来再说qwq。(反正好像是O(n^3)是正解)


        我呢,一看到这个题就感觉这玩意儿不就是找规律吗?然后我就开始画图......(就挺离谱的)然后就是痛苦的分析过程(在考场上真的是要把我烦死了)...

        首先,我们知道对于每个数 n 我们可以用一个简简单单的 dfs 来找出他所有的加法拆分。就比如5可以拆分为:1+1+1+1+1、1+1+1+2、1+2+2、1+1+3、1+4、2+3、5。用代码写出来就是这样:

#include<bits/stdc++.h>
using namespace std;int n = 0;
int m = 0;
int a[100001] = { 1 };void print(int x){for(int i = 1; i <= x-1; i++){printf("%d + ", a[i]);}printf("%d\n", a[x]);
}void dfs(int x){                            //当前项for(int i = a[x-1]; i <= m; i++){       //后面的一定比前面的大所以从前一项开始枚举if(i == n) continue;a[x] = i;m -= i;if(m == 0) print(x);else dfs(x+1);m += i;}
}int main(){scanf("%d", &n);m = n;dfs(1);return 0;
}

然后我们对每次拆分出来的数组a进行一个操作就可以求出 n 根木棒能摆出的交点个数的其中一种情况。但是这要怎么做呢?说来也很简单,拿 n = 7 为例。7 显然能被拆分为 1 + 1 + 1 + 2 + 2,我们对于这一种拆分,认为7根木棒被分成了5组,每组的所有木棒都互相平行且于其他组的木棒都不平行,然后我们将每一组木棒依次摆放到一个平面上。还是以7 = 1 + 1 + 1 + 2 + 2 为例:

        1. 我们把第一组(其实只有一根)放在平面上,显然这时平面上是没有交点的(平面上增加了1\times0个交点)。

        2. 我们把第二组(其实也只有一根)放到平面上,因为这一组与第一组的木棒不平行(而且他们还无限长)所以平面上就增加了1(1 \times 1)个交点了。

        3. 我们把第三组(还是只有一根)放到平面上,因为这一组与第一组和第二组的木棒都不平行,且不存在三根即以上的木棒交于一点的情况,所以平面上必然又增加了2(2 \times 1)个交点了。

        4. 我们把第四组(这次是两根了qwq)放到平面上(如下左图),因为这两根木棒与其他的木三组棒也都互相不平行所以平面上就会增加6(3\times2)个交点

       5.我们把最后一组(也是两根)放到平面上,与前面同理(如下右图),平面上就会多出10(5\times2)个点。

 

        综上所述我们知道这七根木棍在这种排列组合下能够构成的交点个数为 0 + 1 + 2 + 6 + 10 = 19 个点。而根据我们的推导过程我们又可以发现把第 i 组放进平面的时候能够增加的点的个数 p_i 等于前 i - 1 组已经放进平面的木棒的数量sum_i 乘上第 i 组的木棒的数量 a_i

        即p_i = sum_{i-1} \times a_i。由此我们可以得到总点数为:\sum_{i=1}^{k}p_i = \sum_{i=1}^{k}sum_{i-1} \times a_i。所以我们就得到了下面的代码:

#include<bits/stdc++.h>
using namespace std;#define MAXN 100100int q = 0;
int n = 0; int m = 0;
int b = 0;
int a[100001] = { 1 };void print(int x){for(int i = 1; i <= x-1; i++){printf("%d + ", a[i]);}printf("%d\n", a[x]);
}vector<int> v[MAXN];
void dfs(int x){                            //当前项for(int i = a[x-1]; i <= m; i++){       //后面的一定比前面的大所以从前一项开始枚举if(i == n) continue;a[x] = i;m -= i;if(m == 0){//print(x);int sum = 0; int ans = 0;for(int j = 1; j <= x; j++){ans += sum * a[j];sum += a[j];}v[n].push_back(ans);}else dfs(x+1);m += i;}
}int main(){scanf("%d", &q);while(q--){scanf("%d%d", &n, &b);if(b == 0){printf("%d\n", 1);continue;}m = n; bool is = false;fill(a, a + n + 1, 1);if(!v[n].size()){dfs(1);	}for(int j = 0; j < v[n].size(); j++){if(v[n][j] == b){is = true; break;}}if(is){printf("%d\n", 1);}else{printf("%d\n", 0);}}return 0;
}

         然后我们就愉快的得到了80分(然而考试的时候我的 dfs 出了一点小问题就只打到了50分,越想越气...)


        然而,我们不能仅仅满足于这 80 分!于是在赛后,我看了看我们亲爱的 zhengguisheng 教练下发的题解,发现这居然是一道动归题,当时就给我整不会了。所以就有了这篇复盘的题解。下面我们来看一个 DP 的解。

        我凭借这我前面的思路很容易想到一个正着走的 DP ,我们设f_{i, j} 表示当前考虑的 i 根木棒(这 i 跟木棒于后面的木棒都不平行),是否可能构成 j 个交点。状态转移时沿用我们前面的思路,设下一组木棒的个数为 k ,和前面一样,每一组中的木棒都互相平行 。我们可以知道当把这 k 根木棒放入平面时,会增加i\times k 个交点。所以我们可以得到一个状态转移方程:

                                                          f_{i, j} = \bigcup_{k=1}^{i} f_{i-k, j - (i-k) \times k}

        很显然,这个DP 的初始化为f_{i, 0}=True 。

        我们可以看到,枚举 i 为 n 次,枚举 j 为 n * (n - 1) / 2 次,枚举 k 又为 n 次。所以这个 DP 的时间复杂度会达到 O(n^4)。再一看题解,好家伙居然前面的内容跟我想的一摸一样。再仔细一看,还是只能通过 80% 的数据..... 它后面还有优化。


        终于要将到正解了。

        既然正着 DP 不行,我们就倒着来。我们研究当前的方案和全满(也就是交点数达到最大值:\frac{n\times (n-1)}{2})相差了多少。如果某一个方案把木棒总根数分为 k 组,每组i_1, i_2, \cdots i_k 根,那么这个方案这全满也就相差了\frac{n \times (n-1)}{2} - \sum sum_{i-1} \times a_i = \sum_{j=1}^{k} \frac{i_j \times (i_j - 1)}{2}个交点。据此,我们可以得到另一种 DP的方案。

        我们令 g_i 表示于全满状态差 i 个交点时(这个地方题解上面写错了,害得我看了好久)至少需要多少跟木棒。我们可以轻松(极其艰难)地得到一个方程:

                                                         g_i = \min_{j=1}^{N} j + g_{i - \frac{j \times (j-1)}{2}}

        这玩意儿啥意思呢,就是说我们取每一个 j 从 1 到 n 然后找到 g_{i - j\times(j-1)/2} 也就是找到能够达到  没有把这 j 个互相平行的木棒放入平面时的交点数(其实就是这一组又 j 根木棒)  的最小木棒根数。然后在用这玩意儿加上 j 就是能够达到 i 个交点的最小木棒根数了。

        这样的一个 DP 的时间复杂度很显然就是 O(n^3) 的了。这样就能愉快的通过  100% 的数据了qwq。下面是 zengguisheng 教练给出的标准代码:

# include <bits/stdc++.h>
using namespace std;namespace Base{# define mr make_pairtypedef long long ll;typedef double db;const int inf = 0x3f3f3f3f, INF = 0x7fffffff;const ll  infll = 0x3f3f3f3f3f3f3f3fll, INFll = 0x7fffffffffffffffll;template<typename T> void read(T &x){x = 0; int fh = 1; double num = 1.0; char ch = getchar();while (!isdigit(ch)){ if (ch == '-') fh = -1; ch = getchar(); }while (isdigit(ch)){ x = x * 10 + ch - '0'; ch = getchar(); }if (ch == '.'){ch = getchar();while (isdigit(ch)){num /= 10; x = x + num * (ch - '0'); ch = getchar();}}x = x * fh;}template<typename T> void chmax(T &x, T y){x = x < y ? y : x;}template<typename T> void chmin(T &x, T y){x = x > y ? y : x;}
}using namespace Base;const int N = 700;
int num[N + 10], dp[N * N + 10];int main(){//freopen("intersect.in", "r", stdin);//freopen("intersect.out", "w", stdout);for (int i = 1; i <= N; i++) num[i] = i * (i - 1) / 2;memset(dp, inf, sizeof(dp));dp[0] = 0;for (int i = 0; i <= N * (N - 1) / 2; i++)for (int j = 1; j <= N; j++){if (i + num[j] > N * (N - 1) / 2) continue;chmin(dp[i + num[j]], dp[i] + j);}int opt;read(opt);while (opt--){	int n, m;read(n); read(m);if (m > n * (n - 1) / 2){printf("0\n");continue;}if (dp[n * (n - 1) / 2 - m] <= n)printf("%d\n", 1);else printf("%d\n", 0);}return 0;
}

这篇关于木棒交叉 Intersect的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/192394

相关文章

交叉编译python

1.解决python源码,进入源码目录 2.先编译本地版本的python。直接使用命令 ./configure --prefix=/home/KAS-300/python3.8 --enable-optimizationsmake -j8make install 3.把生成的python可执行文件临时加入PATH export PATH=/home/KAS-300/python3.8/

逐行讲解Transformer的代码实现和原理讲解:计算交叉熵损失

LLM模型:Transformer代码实现和原理讲解:前馈神经网络_哔哩哔哩_bilibili 1 计算交叉熵目的 计算 loss = F.cross_entropy(input=linear_predictions_reshaped, target=targets_reshaped) 的目的是为了评估模型预测结果与实际标签之间的差距,并提供一个量化指标,用于指导模型的训练过程。具体来说,交叉

【深度学习 误差计算】10分钟了解下均方差和交叉熵损失函数

常见的误差计算函数有均方差、交叉熵、KL 散度、Hinge Loss 函数等,其中均方差函数和交叉熵函数在深度学习中比较常见,均方差主要用于回归问题,交叉熵主要用于分类问题。下面我们来深刻理解下这两个概念。 1、均方差MSE。 预测值与真实值之差的平方和,再除以样本量。 均方差广泛应用在回归问题中,在分类问题中也可以应用均方差误差。 2、交叉熵 再介绍交叉熵损失函数之前,我们首先来介绍信息

libmad音频解码库-Linux交叉编译移植

下载并解压libmad-0.15.1b.tar.gz 下载链接:https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz $tar -xvf libmad-0.15.1b.tar.gz$cd libmad-0.15.1b 1、先执行下面的命令:这条命令是为了适配高版本的gcc,因为高版本的gcc已经将-fforce-mem去除了:

回归预测 | Matlab基于贝叶斯算法优化XGBoost(BO-XGBoost/Bayes-XGBoost)的数据回归预测+交叉验证

回归预测 | Matlab基于贝叶斯算法优化XGBoost(BO-XGBoost/Bayes-XGBoost)的数据回归预测+交叉验证 目录 回归预测 | Matlab基于贝叶斯算法优化XGBoost(BO-XGBoost/Bayes-XGBoost)的数据回归预测+交叉验证效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现基于贝叶斯算法优化X

hi 平台 opencv4.8 交叉编译

echo "参数是 'arm_hi'"         current_path=$(pwd)         nproc=32         # arm-linux-gnueabihf-gcc, Cross-Toolchain PATH         export PATH="/opt/aarch64-v01c01-linux-musl-gcc/bin:$PATH"

神经网络多分类任务的损失函数——交叉熵

神经网络多分类任务的损失函数——交叉熵 神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到的一个n维数组作为输出结果。数组中的每一个维度(也就是每一个输出节点)对应一个类别。在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出都为0。 以识别手写数字为例,0~9共十个类别。识别数字1,神经网

本地电脑交叉编译ffmpeg 到 windows on arm64

本地电脑交叉编译ffmpeg 到 windows on arm64 我这里有编译好的win on arm 的 ffmpeg : https://github.com/wmx-github/ffmpeg-wos-arm64-build 使用 llvm-mingw 工具链 https://github.com/mstorsjo/llvm-mingw/releases 前缀 aarch64-w64-

交叉编译概念

交叉编译概念 目录 交叉编译概念1. 什么是交叉编译2. 交叉编译的作用3. 交叉编译器4. 交叉编译工具链5. 交叉编译的一般步骤6. 交叉编译实例 1. 什么是交叉编译 交叉编译是指在一个平台上编译代码,使其能够在另一个不同的平台上运行的过程。这种编译方式通常用于开发嵌入式系统、移动设备和其他受限环境中的应用程序。 交叉编译是使用一种编译器(称为交叉编译器),该编译器在

设置交叉编译工具链的环境变量

1 环境变量的意义 环境变量相当于操作系统的全局变量。每一个环境变量对操作系统来说都是唯一的,名字和所代表的意义都是唯一的。Linux系统可以有很多个环境变量。其中有一部分是Linux系统自带的,还有一些是我们自己来扩充的。这里需要使用的环境变量是PATH。PATH是系统自带的,含义是系统在查找可执行程序时会搜索的路径范围。 使用echo $PATH命令查看当前PATH环境变量,如下图: 2