Project Euler 311
题目
Biclinic Integral
Quadrilaterals
\(ABCD\) is a convex, integer sided
quadrilateral with \(1 \le AB < BC < CD
< AD\).
\(BD\) has integer length. \(O\) is the midpoint of \(BD\). \(AO\) has integer length.
We’ll call \(ABCD\) a biclinic
integral quadrilateral if \(AO = CO \le
BO = DO\).
For example, the following quadrilateral is a biclinic integral
quadrilateral:
\(AB = 19, BC = 29, CD = 37, AD = 43, BD =
48\) and \(AO = CO = 23\).
![]()
Let \(B(N)\) be the number of
distinct biclinic integral quadrilaterals \(ABCD\) that satisfy \(AB^2+BC^2+CD^2+AD^2 \le N\).
We can verify that \(B(10 000) =
49\) and \(B(1 000 000) =
38239\).
Find \(B(10 000 000 000)\).
解决方案
把 \(O\) 放在原点,令对角线 \(BD\) 在 \(x\) 轴上:那么有\(B=(s,0), D=(-s,0)\),其中\(BO=DO=s=\dfrac{BD}{2}.\)由于 \(AB,AD\) 都是整数,我们立刻得到):
\[AB^2+AD^2 =
2(AO^2+BO^2)=2(r^2+s^2).\]
左边为整数,因此 \(2s^2\)
必须为整数。若 \(BD\) 为奇数,则 \(s\) 为半整数,从而 \(2s^2\) 为半整数,矛盾。故\(BD\) 必须为偶数,且 \(s\in\mathbb Z\)。
设\(r=AO=CO,
s=BO=DO.\)用三角形中线定理可得:对任意点 \(P\),\(PB^2+PD^2
= 2(PO^2+BO^2).\)
代入 \(P=A\),得到\(AB^2+AD^2 = 2(AO^2+BO^2)=2(r^2+s^2).\)
同理代入 \(P=C\),得到\(BC^2+CD^2 = 2(CO^2+DO^2)=2(r^2+s^2).\)
相加得\(AB^2+BC^2+CD^2+AD^2 =
4(r^2+s^2).\)
因此题目条件\(AB^2+BC^2+CD^2+AD^2 \le
N\),等价于\(r^2+s^2 \le \dfrac
N4.\)令\(L=\left\lfloor\dfrac{N}{4}\right\rfloor.\)
对任意整数 \(x<y\),令\(u=\dfrac{x+y}{2},
v=\dfrac{y-x}{2},\)则\(x=u-v,
y=u+v,\)并且\(x^2+y^2 = (u-v)^2+(u+v)^2
= 2(u^2+v^2).\)
在本题中:
- 顶点 \(A\) 对应 \((AB,AD)\)
- 顶点 \(C\) 对应 \((BC,CD)\)
- 对角线中点体系对应 \((AO,BO)=(r,s)\)
于是同一个数\(k=r^2+s^2\)会需要 3
种表示:\(k = r^2+s^2,k = u_A^2+v_A^2,k =
u_C^2+v_C^2.\)
令 \(R(k)\) 表示:将 \(k\) 写成 \(a^2+b^2\) 的方式数(只计正整数,且 \(1\le a\le b\))。
那么每个满足条件的 biclinic 四边形(在给定严格边序约束下)等价于从
\(R(k)\) 个分解里选出 3
个互不相同的分解来对应“底”和“两条臂”的组合(细节会带来对称修正,但最终落在同一个简洁公式上)。最终可化为:
\[B(N)=\sum_{k\le L}
\binom{R(k)}{3}.\]
使用两平方和定理:若\(k\)的质因子分解满足:\(\displaystyle{k=2^e\prod_{p\equiv1\pmod4}p^{\alpha_p}\prod_{q\equiv3\pmod4}q^{2\beta_q}}\),也就是说,对于所有\(q\equiv 3 \pmod4\)的质因子\(q\),其对应的指数都满足\(\beta_q\)整数(对于其它质因子则不做要求),则存在平方和表示,并且无符号且有序的表示数为\(S=\{(a,b):a,b\ge 0\}\):
\[
r_2(k)=\prod_{p\equiv1\pmod4}(\alpha_p+1).
\]
考虑两个需要注意的解:
- 若 \(k\) 是平方数:\((\sqrt k,0),(0,\sqrt k)\)在 \(S\),
- 若 \(k=2t^2\):\((t,t)\),那么在 \(S\)。
因此,令示性函数\(\sigma_1(k)\)表示\(k\)是平方数,示性函数\(\sigma_2(k)\)表示\(k\)是满足\(k=2t^2\),那么有
\[R(k)=R_{\ge0}(k)-\sigma_1(k)-\sigma_2(k)=\frac{r_2(k)-\sigma_1(k)-\sigma_2(k)}{2}.\]
令 \(L=N/4\)。我们对 \(k\) 的因子结构拆分为两部分:
- \(m\):由所有 \(p\equiv1\pmod4\)
的素因子(及其幂)构成
- 剩余部分:只允许\(2^e\)以及所有
\(q\equiv3\pmod4\) 以偶次幂出现(即
\(q^{2u}\))
将“剩余部分可取的个数”预处理为一个前缀计数表 \(w[x]\),表示:不超过 \(x\) 的数中,形如 \(4^a\cdot s^2\)(其中 \(s\) 只含 \(q\equiv3\pmod4\)
的素因子)的数量。注意,这里不是取\(2^e\)是希望通过两次查询(\(4^a\cdot s^2,4^a\cdot 2 \cdot
s^2\)),防止占用空间过大。
然后 DFS 枚举 \(m\)
的素因子幂次组合,计算贡献即可:
- 主项:\(w\left[\left\lfloor
\dfrac{L}{m}\right\rfloor\right]\cdot \dbinom{R(m)}{3}\)
- \(2\) 奇次:\(w\left[\left\lfloor
\dfrac{L}{2m}\right\rfloor\right]\cdot \dbinom{R(m)}{3}\)
为了让贡献尽可能尽早出现(也就是\(\dbinom{R(k)}{3}> 0\)),这就意味着\(R(k)\ge 3\),最小满足贡献的\(M\)是\(5^2\cdot
13=325\),因此剩余部分的积不超过\(\dfrac{L}{325}\)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
| #include <bits/stdc++.h> #include "tools.h" using namespace std; typedef long long ll;
ll N = 1e10;
ll L;
int Kmax;
vector<uint32_t> w_pref;
vector<int> primes1;
ll ans;
vector<uint8_t> sieve_odd(int limit) { if (limit < 2) return {}; int size = limit / 2 + 1; vector<uint8_t> s(size, 1); s[0] = 0;
int r = (int)std::sqrt((long double)limit); for (int p = 3; p <= r; p += 2) { if (!s[p / 2]) continue;
ll step = 2LL * p; ll start = 1LL * p * p; for (ll x = start; x <= limit; x += step) s[(int)(x / 2)] = 0; } return s; }
vector<int> primes_mod4_3_upto(int limit) { auto s = sieve_odd(limit); vector<int> res; for (int p = 3; p <= limit; p += 4) { if (s[p / 2]) res.push_back(p); } return res; }
vector<uint32_t> build_w_prefix(int K) { if (K <= 0) return vector<uint32_t>(1, 0);
int sqrtK = (int)int_square(K);
vector<int> primes3 = primes_mod4_3_upto(sqrtK);
vector<int> squares; squares.reserve(1 << 16);
function<void(int, int)> gen_s = [&](int curr, int idx) { squares.push_back(curr * curr);
for (int j = idx; j < (int)primes3.size(); ++j) { int p = primes3[j]; ll x = 1LL * curr * p; if (x > sqrtK) break;
while (x <= sqrtK) { gen_s((int)x, j + 1); x *= p; } } }; gen_s(1, 0);
vector<uint8_t> mark(K + 1, 0); for (int b : squares) { ll x = b; while (x <= K) { mark[(int)x] = 1; x *= 4; } }
vector<uint32_t> w(K + 1, 0); uint32_t acc = 0; for (int i = 1; i <= K; ++i) { acc += mark[i]; w[i] = acc; } return w; }
inline ll C3(ll n) { return n * (n - 1) * (n - 2) / 6; }
void dfs(ll m, int idx, ll prod, int e) { ll P = prod * (e + 1);
if (P >= 5) { ll X = L / m;
ll R_even2 = P / 2;
ll R_odd2 = (P & 1) ? ((P + 1) / 2) : R_even2;
ans += (ll)w_pref[X] * C3(R_even2);
ans += (ll)w_pref[X / 2] * C3(R_odd2); }
int p = primes1[idx];
ll mp = m * (ll)p; if (mp <= L) dfs(mp, idx, prod, e + 1);
ll prod2 = prod * (e + 1); for (int j = idx + 1; j < (int)primes1.size(); ++j) { int q = primes1[j]; ll mq = m * (ll)q; if (mq > L) break; dfs(mq, j, prod2, 1); } }
ll B(ll N) { L = N / 4; if (L <= 0) return 0;
Kmax = (L >= 325) ? (int)(L / 325) : (int)L;
w_pref = build_w_prefix(Kmax);
int Pmax = (L >= 25) ? (int)(L / 25) : (int)L; auto odd_sieve = sieve_odd(Pmax);
primes1.clear(); for (int p = 5; p <= Pmax; p += 4) { if (odd_sieve[p / 2]) primes1.push_back(p); }
ll cb = (ll)cbrt((long double)L); while ((cb + 1) * (cb + 1) * (cb + 1) <= L) ++cb; while (cb * cb * cb > L) --cb;
ans = 0;
for (int i = 0; i < (int)primes1.size(); ++i) { int p = primes1[i]; if (p > cb) break;
dfs(1LL * p * p, i, 1, 2);
ll limit_q = int_square(L / p); for (int j = i + 1; j < (int)primes1.size(); ++j) { int q = primes1[j]; if (q > limit_q) break;
if (1LL * p * q * q <= L) dfs(1LL * p * q, j, 2, 1); } }
return ans; }
int main() { cout << B(N) << "\n"; return 0; }
|