联赛模拟测试5题解
T1: 平均数
分析:这个题昨天考试的时候一看就想到了之前做过两次的一个题型,但是似乎没有办法用两个指针一起扫的方法解决, 感觉平均数这个东西怪怪的没有单调性, 于是n^2暴力加信仰剪枝, 40pts get(信仰剪枝怎么没发挥作用!!!)
这道题还是有区别的,但是和之前学姐讲过的0/1分数规划有很多的相似之处,二分答案是要的,于是我们重点考虑怎么检验,满足条件的区间一定满足(sum[r] - sum[l - 1]) / (r - l + 1) >= x,于是sum[r] - sum[l - 1] >= x * ( r - l + 1); 变形之后有sum[r] - x * r >= sum[r - 1] - x * (l - 1);发现这个似乎是一个逆序对, 于是直接归并排序 / 树状数组就可以了, 但是本题树状数组常数巨大, 于是采用归并的方式进行实现。时间复杂度为O(N(logN) ^ 2), 学长开的3333ms可以稳过。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 const int N = 1e5 + 10; 5 double a[N], l, r; 6 typedef long long ll; 7 ll x[N], sum[N]; 8 ll n, m, res; 9 double b[N], c[N]; 10 void merge_sort(int l, int r) { 11 int mid = (l + r) >> 1; 12 if(l == r) return; 13 merge_sort(l, mid); 14 merge_sort(mid + 1, r); 15 for(int i = l; i <= r; ++i) { 16 if(i <= mid) b[i] = a[i]; 17 else c[i] = a[i]; 18 } 19 int _l = l, _r = mid + 1, head = l - 1; 20 while(_l <= mid && _r <= r) { 21 if(b[_l] < c[_r]) { 22 a[++head] = b[_l]; 23 _l++; 24 } 25 else { 26 a[++head] = c[_r]; 27 res += mid - _l + 1; 28 _r++; 29 } 30 } 31 while(_l <= mid) { 32 a[++head] = b[_l++]; 33 } 34 while(_r <= r) { 35 a[++head] = c[_r++]; 36 } 37 } 38 bool Judge(double pos) { 39 for(int i = 0; i <= n; ++i) 40 a[i] = sum[i] - pos * i; 41 res = 0; 42 merge_sort(0, n); 43 return res >= m; 44 } 45 int main() { 46 freopen("ave.in", "r", stdin); 47 freopen("ave.out", "w", stdout); 48 scanf("%d%d", &n, &m); 49 for(int i = 1; i <= n; ++i) { 50 scanf("%lld", &x[i]); 51 sum[i] = sum[i - 1] + x[i]; 52 if(x[i] > r) r = x[i]; 53 } 54 while(r - l > 1e-7) { 55 double mid = (l + r) / 2; 56 if(Judge(mid)) r = mid; 57 else l = mid; 58 } 59 printf("%.4lf\n", l); 60 return 0; 61 }
T2:涂色游戏
垃圾卡常出题人!!! 垃圾卡常出题人!!! 垃圾卡常出题人!!!
通过这一道题成功让我引起了对快速模和register的重视。。。
分析:对于p, q都小于等于5的点, 我们注意到可以利用状压来进行对于状态的记录, 于是定义dp[i][j]表示第i列的使用画笔的状态是j的方案数, 存在方程dp[i][j] += dp[i - 1][k] * calc(count(k), n), 转移的条件是count(j | k) >= q,其中的calc函数表示把count(k)个东西分为n段,没有空段且每个东西都用到的方案数,即第二斯特林数,初始状态都是1, 答案就是把dp[m][1~ed]加起来就可以了,什么?你说你会n^2求calc函数?我都写状压了还在意这些时间? 40pts get
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 const int Mod = 998244353; 5 typedef long long ll; 6 const int N = 2100; 7 int n, m, p, q; 8 namespace Case1 { 9 ll dp[1100][1 << 5]; 10 ll fac[N], ny[N], f[N]; 11 void getny(int x) { 12 fac[0] = 1; 13 for(int i = 1; i <= x; ++i) 14 fac[i] = fac[i - 1] * i % Mod; 15 ny[1] = 1; 16 for(int i = 2; i <= x; ++i) 17 ny[i] = (Mod - Mod / i) * ny[Mod % i] % Mod; 18 f[0] = 1; 19 for(int i = 1; i <= x; ++i) 20 f[i] = f[i - 1] * ny[i] % Mod; 21 } 22 ll comb(int x, int y) { 23 return fac[y] * f[x] % Mod * f[y - x] % Mod; 24 } 25 int count(int t) { 26 int res = 0; 27 while(t) { 28 if(t & 1) res++; 29 t >>= 1; 30 } 31 return res; 32 } 33 ll calc(int x, int y) { 34 ll res = 0; 35 if(!x) { 36 if(!y) return 1; 37 return 0; 38 } 39 if(!y) return 0; 40 for(int i = 1; i <= y; ++i) { 41 res = (res + calc(x - 1, y - i) * comb(i, y) % Mod) % Mod; 42 } 43 return res; 44 } 45 void Solve() { 46 getny(1100); 47 int ed = (1 << p) - 1; 48 for(int i = 1; i <= ed; ++i) { 49 dp[1][i] = 1; 50 } 51 for(int i = 2; i <= m; ++i) 52 for(int j = 1; j <= ed; ++j) 53 for(int k = 1; k <= ed; ++k){ 54 if(count(j | k) < q) continue; 55 dp[i][j] += dp[i - 1][k] * calc(count(k), n); 56 dp[i][j] %= Mod; 57 } 58 ll res = 0; 59 for(int i = 1; i <= ed; ++i) { 60 res += dp[m][i] * calc(count(i), n); 61 res %= Mod; 62 } 63 printf("%lld\n", res); 64 } 65 } 66 int main() { 67 freopen("color.in", "r", stdin); 68 freopen("color.out", "w", stdout); 69 scanf("%d%d%d%d", &n, &m, &p, &q); 70 Case1::Solve(); 71 return 0; 72 }
对于每行选出的数字, 发现貌似我们不记录状态也可以求解,若本列选了j个画笔,上一列选了k个画笔,我们计算答案的时候可以再记录一个s表示j和k里面重合的点的个数,于是本层对于答案的贡献就是comb(s, k) * comb(j - s, p - k) * d[j], 第一个comb表示从k个数字分出s个j的方案,第二个comb表示从剩下的p - k个数字里面选出j - s的数字和s个共同数字来拼出j的方案数,d[j]就是前面提到的第二斯特林数,省去了calc里面的第二个数字n,这里就只能n^2递推了,存在d[1] = 1, d[i] = qpow(i, n) - sigema{d[j] * comb(j, i)} (1 <= j < i), 于是我们的dp方程可以定义为第i列涂了j个颜色的方案数,转移:dp[i][j] += dp[i - 1][k] * comb(s, k) * comb(j - s, p - k) * d[j],发现每次dp(i, j)转移时dp(i - 1, k)乘的系数是相同的于是可以提前处理出来,70pts get
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 const int N = 1100 + 10; 5 const int Mod = 998244353; 6 typedef long long ll; 7 ll dp[N][N], d[N], mat[N][N]; 8 int n, m, q, p; 9 ll qpow(ll x, int cnt) { 10 ll res = 1; 11 while(cnt) { 12 if(cnt & 1) res = (res * x) % Mod; 13 cnt >>= 1; 14 x = (x * x) % Mod; 15 } 16 return res; 17 } 18 ll fac[N], ny[N], f[N]; 19 void getny(int x) { 20 fac[0] = 1; 21 for(int i = 1; i <= x; ++i) 22 fac[i] = fac[i - 1] * i % Mod; 23 ny[1] = 1; 24 for(int i = 2; i <= x; ++i) 25 ny[i] = (Mod - Mod / i) * ny[Mod % i] % Mod; 26 f[0] = 1; 27 for(int i = 1; i <= x; ++i) 28 f[i] = f[i - 1] * ny[i] % Mod; 29 } 30 ll comb(int x, int y) { 31 return fac[y] * f[x] % Mod * f[y - x] % Mod; 32 } 33 void getd(int x) { 34 d[1] = 1; 35 for(int i = 2; i <= x; ++i) { 36 d[i] = qpow(i, n); 37 for(int j = 1; j <= i - 1; ++j){ 38 d[i] = d[i] - 1ll * d[j] * comb(j, i) % Mod; 39 d[i] = (d[i] % Mod + Mod) % Mod; 40 } 41 } 42 } 43 signed main() { 44 freopen("color.in", "r", stdin); 45 freopen("color.out", "w", stdout); 46 scanf("%d%d%d%d", &n, &m, &p, &q); 47 getny(1000); 48 getd(1000); 49 for(int i = 1; i <= p; ++i) { 50 dp[1][i] = 1ll * comb(i, p) * d[i] % Mod; 51 } 52 for(int j = 1; j <= p; ++j) { 53 for(int k = 1; k <= p; ++k) { 54 int t = std::min(j, k); 55 for(int s = 0; s <= t; ++s) { 56 if(k + j - s > p || k + j - s < q) continue; 57 mat[j][k] = (mat[j][k] + comb(s, k) * comb(j - s, p - k) % Mod * d[j] % Mod) % Mod; 58 } 59 } 60 } 61 for(int i = 2; i <= m; ++i) { 62 for(int j = 1; j <= p; ++j) { 63 for(int k = 1; k <= p; ++k) { 64 dp[i][j] = (dp[i][j] + dp[i - 1][k] * mat[j][k] % Mod) % Mod; 65 } 66 } 67 } 68 ll res = 0; 69 for(int i = 1; i <= p; ++i) { 70 res = (res + dp[m][i]) % Mod; 71 } 72 printf("%lld\n", res); 73 return 0; 74 }
最后我们发现m贼大,可以联想到矩阵乘法,前面也处理出来了没一个dp(i, j)需要运算的系数, 放入矩阵里面就可以加速运算了,由于本题卡常卡的丧心病狂,你的满分算法很有可能会掉到70档,关于卡常:https://www.cnblogs.com/Midoria7/p/13717348.html,另外多用register和快速模, 尽可能少的开longlong进行计算也是个好方法。
不要问我为什么存在typedef int ll的操作, 卡常的时候我懒的把所有ll改为int了
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 const int N = 105 + 10; 5 const int Mod = 998244353; 6 typedef int ll; 7 #define reg register 8 void add(ll &x, int y) { 9 if(x + y >= Mod) x = x + y - Mod; 10 else x = x + y; 11 } 12 ll dp[N][N], d[N], mat[N][N]; 13 int n, m, q, p; 14 ll qpow(reg ll x, reg int cnt) { 15 reg ll res = 1; 16 while(cnt) { 17 if(cnt & 1) res = (1ll * res * x) % Mod; 18 cnt >>= 1; 19 x = (1ll * x * x) % Mod; 20 } 21 return res; 22 } 23 ll fac[N], ny[N], f[N]; 24 void getny(reg int x) { 25 fac[0] = 1; 26 for(reg int i(1); i <= x; ++i) 27 fac[i] = 1ll * fac[i - 1] * i % Mod; 28 ny[1] = 1; 29 for(reg int i(2); i <= x; ++i) 30 ny[i] = 1ll * (Mod - Mod / i) * ny[Mod % i] % Mod; 31 f[0] = 1; 32 for(reg int i(1); i <= x; ++i) 33 f[i] = 1ll * f[i - 1] * ny[i] % Mod; 34 } 35 ll comb(reg int x, reg int y) { 36 return 1ll * fac[y] * f[x] % Mod * f[y - x] % Mod; 37 } 38 void getd(reg int x) { 39 d[1] = 1; 40 for(reg int i(2); i <= x; ++i) { 41 d[i] = qpow(i, n); 42 for(reg int j(1); j <= i - 1; ++j){ 43 d[i] = d[i] - 1ll * d[j] * comb(j, i) % Mod; 44 if(d[i] < 0) d[i] = (d[i] % Mod + Mod) % Mod; 45 } 46 } 47 } 48 struct squ { 49 ll a[101][101]; 50 } res, now; 51 squ mul(squ x, squ y) { 52 squ ans; 53 memset(ans.a, 0, sizeof(ans.a)); 54 for(reg int i(1); i <= p; ++i) 55 for(reg int j(1); j <= p; ++j) { 56 for(reg int k(1); k <= p; ++k) { 57 add(ans.a[i][j], 1ll * y.a[i][k] * x.a[k][j] % Mod); 58 } 59 } 60 return ans; 61 } 62 int main() { 63 freopen("color.in", "r", stdin); 64 freopen("color.out", "w", stdout); 65 scanf("%d%d%d%d", &n, &m, &p, &q); 66 getny(1000); 67 getd(1000); 68 for(reg int i(1); i <= p; ++i) { 69 res.a[i][1] = 1ll * comb(i, p) * d[i] % Mod; 70 } 71 for(reg int j(1); j <= p; ++j) { 72 for(reg int k(1); k <= p; ++k) { 73 reg int t = std::min(j, k); 74 for(reg int s(0); s <= t; ++s) { 75 if(k + j - s > p || k + j - s < q) continue; 76 add(now.a[j][k], 1ll * comb(s, k) * comb(j - s, p - k) % Mod * d[j] % Mod); 77 } 78 } 79 } 80 m--; 81 while(m) { 82 if(m & 1) res = mul(res, now); 83 m >>= 1; 84 now = mul(now, now); 85 } 86 reg ll ans = 0; 87 for(reg int i(1); i <= p; ++i) { 88 add(ans, res.a[i][1]); 89 } 90 printf("%d\n", ans); 91 return 0; 92 }
T3:序列
考场上分块没调出来,只交了暴力得了20pts,但是好像G神加上register得了40pts??
分析:我们发现其实我们发现题目强制在线了修改,没有强制询问,于是我们可以考虑从询问下手,我们可以从线段树优化建图里面得到启发,把每一个询问区间拍到线段树里面,对于每一个线段树上的节点开一个vector,最终答案的统计可以转化为每个点从根节点出发,在到叶子的经过每一个节点的vector里面二分查找小于当前数字的询问区间的x的个数,直接累加答案就行了,对于修改直接把该位置上一次的贡献减去,把新的贡献加上就可以了。时间复杂度O(N(logN) ^ 2),经过卡常可以通过,卡常难度比T2小。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <vector> 5 #define rint register int 6 const int N = 1e6 + 10; 7 std::vector<int> ve[N]; 8 using std::lower_bound; 9 int read(){ 10 rint s=0,f=1; 11 char ch=getchar(); 12 while(ch < '0' || ch > '9'){ 13 if(ch=='-') f=-1; 14 ch=getchar(); 15 } 16 while(ch >= '0' && ch <= '9'){ 17 s=(s<<1)+(s<<3)+(ch^48); 18 ch=getchar(); 19 } 20 return s*f; 21 } 22 int a[N]; 23 #define debug puts("-debug-") 24 #define lson (t << 1) 25 #define rson (t << 1 | 1) 26 #define mid ((l + r) >> 1) 27 void change(rint t, rint l, rint r, rint cl, rint cr, rint num) { 28 if(cl <= l && r <= cr) { 29 ve[t].push_back(num); 30 return; 31 } 32 if(cr <= mid) change(lson, l, mid, cl, cr, num); 33 else if(cl > mid) change(rson, mid + 1, r, cl, cr, num); 34 else { 35 change(lson, l, mid, cl, cr, num); 36 change(rson, mid + 1, r, cl, cr, num); 37 } 38 } 39 int Binary(rint t, rint l, rint r, rint x) { //卡常。。。 40 while(l <= r) { 41 if(ve[t][mid] > x) r = mid - 1; 42 else l = mid + 1; 43 } 44 return l; 45 } 46 int query(rint t, rint l, rint r, rint pos, rint num) { 47 rint res = 0; 48 rint p = Binary(t, 0, ve[t].size() - 1, num); 49 res += p; 50 if(l == r) return res; 51 if(pos <= mid) res += query(lson, l, mid, pos, num); 52 else res += query(rson, mid + 1, r, pos, num); 53 return res; 54 } 55 struct Q { 56 int l, r, x; 57 bool operator < (const Q &a) const { 58 return a.x > x; 59 } 60 } b[N]; 61 signed main() { 62 freopen("seq.in", "r", stdin); 63 freopen("seq.out", "w", stdout); 64 rint n = read(), m = read(), q = read(); 65 for(rint i = 1; i <= n; ++i) { 66 a[i] = read(); 67 } 68 for(rint i = 1; i <= m; ++i) { 69 b[i].l = read(); 70 b[i].r = read(); 71 b[i].x = read(); 72 } 73 std::sort(b + 1, b + m + 1); 74 for(rint i = 1; i <= m; ++i) { 75 change(1, 1, n, b[i].l, b[i].r, b[i].x); 76 } 77 rint tot = n * 4 - 1; 78 for(rint i = 1; i <= tot; ++i) { 79 std::sort(ve[i].begin(), ve[i].end()); 80 } 81 rint res = 0; 82 for(rint i = 1; i <= n; ++i) { 83 res += query(1, 1, n, i, a[i]); 84 } 85 printf("%d\n", res); 86 for(rint i = 1; i <= q; ++i) { 87 rint p = read(), v = read(); 88 rint pre = res; 89 rint pos = pre ^ p; 90 res -= query(1, 1, n, pos, a[pos]); 91 a[pos] = pre ^ v; 92 res += query(1, 1, n, pos, a[pos]); 93 printf("%d\n", res); 94 } 95 return 0; 96 }
熟悉主席树的人可能已经发现了上面的过程可以用主席树进行实现,具体操作和vector的做法相似,但是时间复杂度降到了O(NlogN)不用卡常也可以通过。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 2e5 + 5; 4 inline int read(int s = 0, int f = 1, char ch = getchar()) { 5 while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); } 6 while(isdigit(ch)) { s = s*10 + ch - '0'; ch = getchar(); } 7 return s * f; 8 } 9 vector<pair<int, int> > qu[N]; 10 int n, m, q, a[N], edit[N], tot; 11 long long ans; 12 struct tree { 13 int ls, rs, val; 14 } tr[N * 28]; 15 int modify(int u, int v, int l, int r, int p, int w) { 16 u = ++tot; 17 if(l == r) { 18 tr[u].val = tr[v].val + w; 19 return u; 20 } 21 int mid = (l + r) / 2; 22 if(p <= mid) tr[u].rs = tr[v].rs, tr[u].ls = modify(tr[u].ls, tr[v].ls, l, mid, p, w); 23 else tr[u].ls = tr[v].ls, tr[u].rs = modify(tr[u].rs, tr[v].rs, mid + 1, r, p, w); 24 tr[u].val = tr[tr[u].ls].val + tr[tr[u].rs].val; 25 return u; 26 } 27 int query(int rt, int l, int r, int s, int t) { 28 if(rt == 0) return 0; 29 if(s <= l && r <= t) return tr[rt].val; 30 int mid = (l + r) / 2; 31 int ans = 0; 32 if(s <= mid) ans += query(tr[rt].ls, l, mid, s, t); 33 if(t > mid) ans += query(tr[rt].rs, mid + 1, r, s, t); 34 return ans; 35 } 36 int main() { 37 freopen("seq.in", "r", stdin); 38 freopen("seq.out", "w", stdout); 39 n = read(), m = read(), q = read(); 40 for(register int i = 1; i <= n; ++i) 41 a[i] = read(); 42 for(register int i = 1, l, r, x; i <= m; ++i) { 43 l = read(), r = read(), x = read(); 44 qu[l].push_back(make_pair(x, 1)); 45 qu[r + 1].push_back(make_pair(x, -1)); 46 } 47 for(register int i = 1; i <= n; ++i) { 48 edit[i] = edit[i-1]; 49 for(register int j = 0; j < qu[i].size(); ++j) { 50 edit[i] = modify(edit[i], edit[i], 0, n, qu[i][j].first, qu[i][j].second); 51 } 52 ans += query(edit[i], 0, n, 0, a[i]); 53 } 54 cout << ans << '\n'; 55 for(register int i = 1, p, v; i <= q; ++i) { 56 p = read(), v = read(); 57 p ^= ans, v ^= ans; 58 ans -= query(edit[p], 0, n, 0, a[p]); 59 ans += query(edit[p], 0, n, 0, v); 60 a[p] = v; 61 printf("%lld\n", ans); 62 } 63 return 0; 64 }