首页 > 技术文章 > 联赛模拟测试5题解

li-jia-hao 2020-09-23 21:15 原文

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 }
View Code

 

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 }
View Code

  对于每行选出的数字, 发现貌似我们不记录状态也可以求解,若本列选了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 }
View Code

  最后我们发现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 }
View Code

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 }
View Code

  熟悉主席树的人可能已经发现了上面的过程可以用主席树进行实现,具体操作和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 }
View Code

 

推荐阅读