牛客练习赛42D(性质、数学)
就像题解所说的,写几个可以发现有分成四段的性质:第一段是从n开始往下贪,第二段是个数字,第三段……卧槽好吧真难描述。
然后发现这个数据量可达1e9,所以考虑“二分确定序列+数学计算”的方式解题。
首先二分出第一段的长度,这里我写得丑了,又将某些情况特判了一下;不难发现有了第一段的长度、N、K这三个量,序列已确定。
然后疯狂手推数学公式把这四段的值求出来,特殊情况的例子很好举,自己调一调打打补丁做一做膜法,大概就莽A了吧……不过要是在考场上本垃圾就必死无疑了。
1 #include <cstdio> 2 #include <cmath> 3 #include <iostream> 4 using namespace std; 5 6 typedef long long ll; 7 const ll mod = 1e9 + 7; 8 ll N, K, x, ans1, ans2, ans3, ans4; 9 10 ll add(ll a, ll b) { 11 return a + b >= mod ? a + b - mod : a + b; 12 } 13 14 ll sub(ll a, ll b) { 15 return a - b < 0 ? a - b + mod : a - b; 16 } 17 18 ll ksm(ll a, ll b) { 19 ll ret = 1; 20 for (; b; b >>= 1) { 21 if (b & 1) ret = ret * a % mod; 22 a = a * a % mod; 23 } 24 return ret; 25 } 26 27 inline ll cal(ll l, ll r) { 28 return (l + r) * (r - l + 1) / 2; 29 } 30 31 inline ll cal2(ll n) {//这是求x + 2*x^2 + ... + n*x^n的函数 32 ll a = n % mod * ksm(x, n + 1) % mod * (x - 1) % mod; 33 ll b = x * sub(ksm(x, n), 1) % mod; 34 return sub(a, b) * ksm((x - 1) * (x - 1) % mod, mod - 2) % mod; 35 } 36 37 int main() { 38 cin >> N >> K; 39 x = N + 1; 40 if (K == 0) { 41 cout << cal2(N) << endl; 42 return 0; 43 } 44 ll l = 1, r = N; 45 while (l < r) { 46 ll mid = (l + r) >> 1; 47 if (cal(N - mid, N - 1) <= K) l = mid + 1; 48 else r = mid; 49 } 50 while (cal(N - l, N - 1) >= K) l--;//至此l为第一段长度。如果出现54312这种,会将54作为第一段,3作为第二段,12为第三段,第四段为空 51 if (l) { 52 ans1 = add(sub(x * x % mod * (ksm(x, l) - 1 + mod) % mod * ksm(x - 1, mod - 2) % mod, l * x % mod), (N - l) * x % mod * (ksm(x, l) - 1 + mod) % mod) * ksm(x - 1, mod - 2) % mod; 53 K -= cal(N - l, N - 1); 54 } 55 if (K) { 56 K++, l++;//K就是第二段那个数字,l++只是为了运算简便 57 ans2 = K % mod * ksm(x, l) % mod; 58 ans3 = ksm(x, l) * cal2(K - 1) % mod; 59 if (N - l + 1 > K) {//如果有第四段 60 ll tmp = K % mod * sub(ksm(x, N + 1), ksm(x, K + l)) % mod * ksm(x - 1, mod - 2) % mod; 61 ans4 = add(tmp, ksm(x, K + l - 1) * cal2(N - K - l + 1) % mod); 62 } 63 } 64 cout << (ans1 + ans2 + ans3 + ans4) % mod; 65 return 0; 66 }