UVa10213 多少块土地
有一块椭圆形的地。在边界上选 n ( 0 ≤ n< 2 31 2^{31} 231 )个点并两两连接得到 n(n-1)/2 条线段。它们最多能把地分成多少个部分?
样例:
Sample Input
4
1
2
3
4
Sample Output
1
2
4
8
如图所示:
根据拓扑学的欧拉公式, V-E+F=2 ,式中, V 是顶点数, E 是边数, F 是面数。展开在平面图中, F 包含最外层的“无限面”,欧拉公式仍然成立。
首先枚举一条从固定点出发的对角线,例如枚举固定点 A ,对角线 AD ,则它的左边有 BC 2 个点,右边有 EF 2 个点,左右点之间的连线有 2*2 个。那这些连线在对角线 AD 上形成的交点最多有 2*2 个( G 是其中之一), 2*2 个交点和 AD 两点把线段 AD 切分成了 2*2+1 段( GH 是其中一段)。我们要注意 G 在以 A 为固定点 AD 为对角线、以 D 为固定点 AD 为对角线、以 B 为固定点 BF 为对角线、以 F 为固定点 BF 为对角线这 4 中情况重复计数了,而边 GH 在以 A 为固定点 AD 为对角线、以 D 为固定点 AD 为对角线这 2 种情况重复计数了。
所以枚举一条从固定点(一共有 n 个固定点,所以最后要乘 n )出发的对角线,它的左边有 i 个点,右边有 n-2-i 个点。左右点的连线在这条对角线上形成
i
(
n
−
2
−
i
)
i(n-2-i)
i(n−2−i) 个交点,得到
i
(
n
−
2
−
i
)
+
1
i(n-2-i)+1
i(n−2−i)+1 条线段。每个交点被重复计算 4 次,每条线段被重复计算 2 次。
V
=
n
+
n
4
∑
i
=
0
n
−
2
i
(
n
−
2
−
i
)
V=n+\frac n 4\sum_{i=0}^{n-2}i(n-2-i)
V=n+4ni=0∑n−2i(n−2−i)
E
=
n
+
n
2
∑
i
=
0
n
−
2
(
i
(
n
−
2
−
i
)
+
1
)
E=n+\frac n 2\sum_{i=0}^{n-2}(i(n-2-i)+1)
E=n+2ni=0∑n−2(i(n−2−i)+1)
根据欧拉公式(减去 1 是因为最外层的土地不算):
F
=
E
−
V
+
2
−
1
=
n
(
n
−
1
)
2
+
n
4
∑
i
=
0
n
−
2
i
(
n
−
2
−
i
)
+
1
F=E-V+2-1=\frac {n(n-1)} 2+\frac n 4\sum_{i=0}^{n-2}i(n-2-i)+1
F=E−V+2−1=2n(n−1)+4ni=0∑n−2i(n−2−i)+1
这样程序的复杂度就是 O(n),最多 3500 道题, n 最大 2 31 2^{31} 231 , 3500 × 2 31 > 7 × 1 0 12 3500\times 2^{31}>7\times10^{12} 3500×231>7×1012,如果每秒 3 亿次运算,需要计算五个小时。明显超时。
此处重点在于
∑
\sum
∑ 运算,
∑
i
(
n
+
1
−
i
)
=
n
(
n
+
1
)
(
n
+
2
)
6
\sum i(n+1-i)=\frac {n(n+1)(n+2)} 6
∑i(n+1−i)=6n(n+1)(n+2) ,该公式可以使用数学归纳法证明(证明过程在下文中)。
所以,
F = n ( n − 1 ) 2 + n ( n − 1 ) ( n − 2 ) ( n − 3 ) 24 + 1 F=\frac {n(n-1)} 2+\frac {n(n-1)(n-2)(n-3)} {24}+1 F=2n(n−1)+24n(n−1)(n−2)(n−3)+1
相关公式证明
设
a
n
=
∑
i
(
n
+
1
−
i
)
a_n=\sum i(n+1-i)
an=∑i(n+1−i)
注意
∑
i
(
n
+
1
−
i
)
=
∑
i
=
0
n
+
1
i
(
n
+
1
−
i
)
=
∑
i
=
1
n
i
(
n
+
1
−
i
)
\sum i(n+1-i)=\sum_{i=0}^{n+1} i(n+1-i)=\sum_{i=1}^{n} i(n+1-i)
∑i(n+1−i)=∑i=0n+1i(n+1−i)=∑i=1ni(n+1−i)
- n=1 时, a 1 = 1 × 1 = 1 × 2 × 3 6 a_1=1\times 1=\frac {1\times2\times3} 6 a1=1×1=61×2×3 ,等式成立。
- 假设 n=k 时,
a
k
=
∑
i
(
k
+
1
−
i
)
=
k
(
k
+
1
)
(
k
+
2
)
6
a_k=\sum i(k+1-i)=\frac {k(k+1)(k+2)} 6
ak=∑i(k+1−i)=6k(k+1)(k+2)
式子左边展开为 1 × k + 2 × ( k − 1 ) + 3 × ( k − 2 ) + . . . + k × 1 1\times k+2\times(k-1)+3\times(k-2)+...+k\times 1 1×k+2×(k−1)+3×(k−2)+...+k×1 - 则 n=k+1 时,
a
k
+
1
=
∑
i
(
k
+
1
+
1
−
i
)
=
1
×
(
k
+
1
)
+
2
×
k
+
3
×
(
k
−
1
)
+
4
×
(
k
−
2
)
+
.
.
.
+
k
×
2
+
(
k
+
1
)
×
1
a_{k+1}=\sum i(k+1+1-i) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\\~~~~~=1\times (k+1)+2\times k+3\times(k-1)+4\times(k-2)+...+k\times 2+(k+1)\times 1
ak+1=∑i(k+1+1−i) =1×(k+1)+2×k+3×(k−1)+4×(k−2)+...+k×2+(k+1)×1
下面我们来看
a
k
a_k
ak 到
a
k
+
1
a_{k+1}
ak+1 的变化:

相减之后可以看到是一个等差数列,根据等差数列求和公式可以知道:
a
k
+
1
−
a
k
=
1
+
2
+
3
+
.
.
.
+
k
+
(
k
+
1
)
=
(
k
+
1
)
(
k
+
2
)
2
a_{k+1}-a_k=1+2+3+...+k+(k+1)=\frac {(k+1)(k+2)} 2
ak+1−ak=1+2+3+...+k+(k+1)=2(k+1)(k+2)
∴
a
k
+
1
=
k
(
k
+
1
)
(
k
+
2
)
6
+
(
k
+
1
)
(
k
+
2
)
2
=
(
k
+
1
)
(
k
+
2
)
(
k
+
3
)
6
=
[
k
+
1
]
(
[
k
+
1
]
+
1
)
(
[
k
+
1
]
+
2
)
6
\therefore a_{k+1}=\frac {k(k+1)(k+2)} 6+\frac {(k+1)(k+2)} 2\\=\frac {(k+1)(k+2)(k+3)} 6\\=\frac {[k+1]([k+1]+1)([k+1]+2)} 6
∴ak+1=6k(k+1)(k+2)+2(k+1)(k+2)=6(k+1)(k+2)(k+3)=6[k+1]([k+1]+1)([k+1]+2)
证明完毕。
完整代码
代码中的 BigInteger 使用了 zypang1的 [中等 ] 比较完整的 BigInteger 高精度整数类( C++实现) 的代码。
//#define LOCAL
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
struct BigInteger
{
vector<int> s;
static const int BASE = 10000;
static const int WIDTH = 4;
void standardize()
{
for (int i = s.size() - 1; i >= 0; --i)
{
if (s[i] == 0)
s.pop_back();
else
break;
}
if (s.empty())
s.push_back(0);
}
BigInteger &operator=(long long num)
{
s.clear();
do
{
s.push_back(num % BASE);
num /= BASE;
} while (num > 0);
return *this;
}
BigInteger &operator=(const string &num)
{
s.clear();
int len = (num.size() - 1) / WIDTH + 1;
int x = 0;
for (int i = 0; i < len; ++i)
{
int end = num.size() - i * WIDTH;
int start = max(0, end - WIDTH);
sscanf(num.substr(start, end - start).c_str(), "%d", &x);
s.push_back(x);
}
standardize();
return *this;
}
BigInteger operator+(const BigInteger &rhs) const
{
int size = max(s.size(), rhs.s.size());
int carry = 0;
BigInteger ans;
for (int i = 0; i < size; ++i)
{
int sum = carry;
if (i < s.size())
sum += s[i];
if (i < rhs.s.size())
sum += rhs.s[i];
carry = sum / BASE;
ans.s.push_back(sum % BASE);
}
if (carry > 0)
{
ans.s.push_back(carry);
}
return ans;
}
BigInteger operator*(const BigInteger &rhs) const
{
BigInteger ans;
for (int i = 0; i < rhs.s.size(); ++i)
{
BigInteger lans;
for (int k = 0; k < i; ++k)
lans.s.push_back(0);
int carry = 0;
for (int j = 0; j < s.size(); ++j)
{
int result = rhs.s[i] * s[j] + carry;
carry = result / BASE;
lans.s.push_back(result % BASE);
}
while (carry > 0)
{
lans.s.push_back(carry % BASE);
carry /= BASE;
}
ans = ans + lans;
}
return ans;
}
BigInteger operator-(const BigInteger &rhs) const
{
BigInteger ans;
int carry = 0;
for (int i = 0; i < s.size(); ++i)
{
int diff = s[i] - carry;
if (i < rhs.s.size())
diff -= rhs.s[i];
carry = 0;
while (diff < 0)
{
++carry;
diff += BASE;
}
ans.s.push_back(diff);
}
ans.standardize();
return ans;
}
BigInteger operator/(int rhs) const
{
BigInteger ans;
vector<int> t;
long long rmder = 0;
for (int i = s.size() - 1; i >= 0; --i)
{
long long temp = rmder * BASE + s[i];
long long div = temp / rhs;
rmder = temp % rhs;
t.push_back(div);
}
for (int i = t.size() - 1; i >= 0; --i)
ans.s.push_back(t[i]);
ans.standardize();
return ans;
}
friend ostream &operator<<(ostream &out, const BigInteger &rhs)
{
out << rhs.s.back();
for (int i = rhs.s.size() - 2; i >= 0; --i)
{
char buf[5];
sprintf(buf, "%04d", rhs.s[i]);
cout << string(buf);
}
return out;
}
bool operator<(const BigInteger &rhs) const
{
if (s.size() != rhs.s.size())
return s.size() < rhs.s.size();
for (int i = s.size() - 1; i >= 0; --i)
{
if (s[i] != rhs.s[i])
return s[i] < rhs.s[i];
}
return false;
}
};
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif // LOCAL
int S, n;
cin >> S;
while (S--)
{
cin >> n;
BigInteger ans;
ans = n;
BigInteger one;
one = 1;
ans = ans * (ans - one) / 2 + ans * (ans - one) * (ans - one - one) * (ans - one - one - one) / 24 + one;
cout << ans << endl;
}
return 0;
}
本文深入探讨UVa10213问题,即如何通过在椭圆边界上选取点并连接线段来最大化土地分割数量。利用拓扑学的欧拉公式,解析了算法背后的数学原理,提供了复杂度为O(n)的解决方案,并附带了完整的代码实现。

882

被折叠的 条评论
为什么被折叠?



