NOIP多校联考13
问题 A: 【2022暑期集训8月20日】隔离
差点因为check传参传入了int而以为自己二分又写错了……

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; const int N = 1e6 + 2; const ll mod = 1e9 + 7; int n, m; ll l, r; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { ll a, b; bool operator < (const node &T) const { return a < T.a; } }p[maxn]; bool check(ll d) { ll pos = p[1].a + d; //printf("pos = %lld\n", pos); int num = 1, i = 1; while(pos <= p[m].b) { while(pos > p[i].b) { i++; } pos = max(pos, p[i].a); //printf("pos = %lld\n", pos); num++; pos += d; //printf("pos = %lld num = %d\n", pos, num); if(num >= n) return 1; } if(num < n) return 0; else return 1; } int main() { //freopen("1.in", "r", stdin); n = read(); m = read(); for(int i=1; i<=m; i++) { scanf("%lld%lld", &p[i].a, &p[i].b); } sort(p+1, p+1+m); r = (p[m].b + 1) / n; //printf("%lld %d\n", p[m].b, n); //printf("r = %lld\n", r); while(l < r) { ll mid = (l + r + 1) >> 1; //printf("mid = %lld\n", mid); if(check(mid)) { l = mid; //printf("mid = %lld\n", mid); } else r = mid - 1; } printf("%lld\n", l); return 0; }
问题 B: 【2022暑期集训8月20日】绽放的花火
赛时对着一个f[i-1]=p*(f[i]+1)+(1-p)*(f[i-2]+1)表示无奈(先把f[i-2]提到了前面,但是发现我只知道f[n]=0不是道f[n-1]是多少,转化失败),原来可以高斯消元……
不过高斯消元太麻烦了,有一种更简单的思路,就是从当前位置走到下一个位置,一维从1循环到n1-1,二维就从1循环到n1+n2-1,就是不管是几维空间,有效的移动是特定有限的,当然从0开始循环也可以,把结束位置也改小一点就好了,这个都一样。
把f[i] = p + (1-p)*(f[i-1]+f[i]+1)化简就好了。
and:我才知道有了p%mod,(1-p)%mod可以直接算——
caorong告诉我的时候我还不信,非要自己验证一下……

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 10; const int N = 1e6 + 2; const ll mod = 1e9 + 7; ll f[maxn], sum, ans, p, p1, p2; int t, k; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } ll qpow(ll a, ll b) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } int main() { //freopen("1.in", "r", stdin); t = read(); while(t--) { k = read(); sum = 0; ans = 0; for(int i=1; i<=k; i++) { int x = read(); sum = (sum + x) % mod; } p = read(); p1 = ((1-p)%mod+mod)%mod; p2 = qpow(p, mod-2); f[0] = 1; for(int i=1; i<sum-k; i++) { f[i] = (p1*f[i-1]%mod*p2%mod+p2)%mod; } for(int i=0; i<sum-k; i++) { ans = (ans + f[i]) % mod; } printf("%lld\n", ans); } exit(0); return 0; }
问题 C: 【2022暑期集训8月20日】美化数列
40分的话据说直接暴力就行,我还开了个树状数组……说起来,算时间的话可能还比不上暴力呢……
啊对,关于等差数列什么的,我本来想用树状数组二次差分,结果发现这两个差值在其他种类的修改时没法维护,所以每次1询问两次循环求差值?得了吧还不如直接暴力地单点修改呢……

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; const int N = 1e6 + 2; const ll mod = 19260817; int n, m, D; ll fib[maxn], c[maxn], ans; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } inline int lowbit(int x) { return x & -x; } inline void add(int x, int val) { while(x <= n) { c[x] = (c[x] + val) % mod; x += lowbit(x); } } inline ll query(int x) { ll sum = 0; while(x) { sum = (sum + c[x]) % mod; x -= lowbit(x); } return sum; } int main() { //freopen("1.in", "r", stdin); //freopen("ww.txt", "w", stdout); n = read(); m = read(); D = read(); for(int i=1; i<=n; i++) { int x = read(); add(i, x); } fib[1] = 1; fib[2] = 1; for(int i=3; i<=n; i++) { fib[i] = (fib[i-1] + fib[i-2]) % mod; } while(m--) { int tp = read(), l = read(), r = read(); if(tp == 1) { ll k = read(), d = read(); for(int i=l; i<=r; i++) { add(i, k); k = (k + d) % mod; } } else if(tp == 2) { ll k = read(); for(int i=l; i<=r; i++) { add(i, k); k = k * D % mod; } } else if(tp == 3) { for(int i=l; i<=r; i++) { add(i, fib[i-l+1]); } } else { ans = (query(r) - query(l-1) + mod) % mod; printf("%lld\n", ans); } } return 0; }
正解: 对于等差数列,区间求和可以变成[(首项+末项)/2],记录首项和长度就可以更新,小区间的首项可以由大区间的首项推出,对于等比数列,可以预处理首项为1的单项数值+前缀和,首项改变并不是全部改变,其实是每一项多乘了一个k,根据乘法结合律可以把它提出来,所以等比数列的首项可以合并。
最有趣的是fibo的处理,关于这两个系数,其实就是f[3] = f[2]+f[1],f[4] = f[3]+f[2] = f[2]+f[1]+f[2],所以f[2]的系数是2, f[1]的系数是1……所以说只要知道前两项,就可以得到后面的任意一项和任意前多少项的和(两个系数的前缀和+乘法分配率)。
这个结论可以拓展:不只是用f[1]和f[2]表示其它的,任意两个连续的斐波那契数都可以沿用预处理的系数,只要每一项都是前两项相加得到的就可以,当然系数的起始位置需要作出相应的变化。这也可以解释为什么修改时记录的前两个斐波那契数可以直接累加。
所以三种情况的处理原理很相似,三种情况的操作步骤也很相似:先由大区间的左端点的值得出小区间左端点的值,再由它得到区间修改的贡献和计入答案。


#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; const int N = 1e6 + 2; const ll mod = 19260817; #define int ll int n, m; ll D, f1[maxn], f2[maxn], sumf1[maxn], sumf2[maxn], g[maxn], sumg[maxn]; ll k1[maxn<<2], d1[maxn<<2], k2[maxn<<2], h1[maxn<<2], h2[maxn<<2]; struct node { bool lazy1, lazy2, lazy3; ll sum; }t[maxn<<2]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void Fibo() { f1[1] = 1; f2[1] = 0; f1[2] = 0; f2[2] = 1; for(int i=3; i<=n; i++) { f1[i] = (f1[i-1]+f1[i-2])%mod; f2[i] = (f2[i-1]+f2[i-2])%mod; } for(int i=1; i<=n; i++) { sumf1[i] = (sumf1[i-1]+f1[i])%mod; sumf2[i] = (sumf2[i-1]+f2[i])%mod; } g[1] = 1; for(int i=2; i<=n; i++) g[i] = g[i-1]*D%mod; for(int i=1; i<=n; i++) sumg[i] = (sumg[i-1]+g[i])%mod; } inline ll calc(ll k, ll len, ll d) { ll res = k + k + (len-1)*d; res = res*len/2%mod; return res; } inline void pushup(int x) { t[x].sum = (t[x<<1].sum+t[x<<1|1].sum)%mod; } inline void add1(int x, int l, int r, int L, int R, ll k, ll d) { k += (l-L)*d; t[x].sum += calc(k, r-l+1, d); t[x].sum %= mod; k1[x] += k; d1[x] += d; t[x].lazy1 = 1; } inline void add2(int x, int l, int r, int L, int R, ll k) { k = k*g[l-L+1]%mod; t[x].sum += sumg[r-l+1]*k%mod; t[x].sum %= mod; k2[x] += k; t[x].lazy2 = 1; } inline void add3(int x, int l, int r, int L, int R, ll a, ll b) { if(l == r) { t[x].sum += f1[l-L+1]*a%mod+f2[l-L+1]*b%mod; t[x].sum %= mod; return; } ll res1 = f1[l-L+1]*a%mod+f2[l-L+1]*b%mod; ll res2 = f1[l-L+2]*a%mod+f2[l-L+2]*b%mod; t[x].sum += res1*sumf1[r-l+1]%mod+res2*sumf2[r-l+1]%mod; t[x].sum %= mod; h1[x] += res1, h1[x] %= mod; h2[x] += res2, h2[x] %= mod; t[x].lazy3 = 1; } inline void pushdown1(int x, int l, int r) { int mid = (l + r) >> 1; add1(x<<1, l, mid, l, r, k1[x], d1[x]); add1(x<<1|1, mid+1, r, l, r, k1[x], d1[x]); k1[x] = d1[x] = 0; t[x].lazy1 = 0; } inline void pushdown2(int x, int l, int r) { int mid = (l + r) >> 1; add2(x<<1, l, mid, l, r, k2[x]); add2(x<<1|1, mid+1, r, l, r, k2[x]); k2[x] = 0; t[x].lazy2 = 0; } inline void pushdown3(int x, int l, int r) { int mid = (l + r) >> 1; add3(x<<1, l, mid, l, r, h1[x], h2[x]); add3(x<<1|1, mid+1, r, l, r, h1[x], h2[x]); h1[x] = h2[x] = 0; t[x].lazy3 = 0; } void build(int x, int l, int r) { if(l == r) { t[x].sum = read()%mod; return; } int mid = (l + r) >> 1; build(x<<1, l, mid); build(x<<1|1, mid+1, r); pushup(x); } void update1(int x, int l, int r, int L, int R, ll k, ll d) { if(L <= l && r <= R) { add1(x, l, r, L, R, k, d); return; } if(t[x].lazy1) pushdown1(x, l, r); if(t[x].lazy2) pushdown2(x, l, r); if(t[x].lazy3) pushdown3(x, l, r); int mid = (l + r) >> 1; if(L <= mid) update1(x<<1, l, mid, L, R, k, d); if(R > mid) update1(x<<1|1, mid+1, r, L, R, k, d); pushup(x); } void update2(int x, int l, int r, int L, int R, ll k) { if(L <= l && r <= R) { add2(x, l, r, L, R, k); return; } if(t[x].lazy1) pushdown1(x, l, r); if(t[x].lazy2) pushdown2(x, l, r); if(t[x].lazy3) pushdown3(x, l, r); int mid = (l + r) >> 1; if(L <= mid) update2(x<<1, l, mid, L, R, k); if(R > mid) update2(x<<1|1, mid+1, r, L, R, k); pushup(x); } void update3(int x, int l, int r, int L, int R) { if(L <= l && r <= R) { add3(x, l, r, L, R, 1, 1); return; } if(t[x].lazy1) pushdown1(x, l, r); if(t[x].lazy2) pushdown2(x, l, r); if(t[x].lazy3) pushdown3(x, l, r); int mid = (l + r) >> 1; if(L <= mid) update3(x<<1, l, mid, L, R); if(R > mid) update3(x<<1|1, mid+1, r, L, R); pushup(x); } ll query(int x, int l, int r, int L, int R) { if(L <= l && r <= R) return t[x].sum; if(t[x].lazy1) pushdown1(x, l, r); if(t[x].lazy2) pushdown2(x, l, r); if(t[x].lazy3) pushdown3(x, l, r); int mid = (l + r) >> 1; if(R <= mid) return query(x<<1, l, mid, L, R); if(L > mid) return query(x<<1|1, mid+1, r, L, R); return (query(x<<1, l, mid, L, R)+query(x<<1|1, mid+1, r, L, R))%mod; } inline void solve1() { int l = read(), r = read(); ll k = read(), d = read(); update1(1, 1, n, l, r, k, d); } inline void solve2() { int l = read(), r = read(); ll k = read(); update2(1, 1, n, l, r, k); } inline void solve3() { int l = read(), r = read(); update3(1, 1, n, l, r); } inline void solve4() { int l = read(), r = read(); ll ans = query(1, 1, n, l, r); ans = (ans%mod+mod)%mod; printf("%lld\n", ans); } signed main() { //freopen("1.in", "r", stdin); n = read(), m = read(), D = read(); Fibo(); build(1, 1, n); while(m--) { int tp = read(); if(tp == 1) solve1(); else if(tp == 2) solve2(); else if(tp == 3) solve3(); else solve4(); } return 0; }
问题 D: 【2022暑期集训8月20日】ZB的旋转树
本来打算根据树的中序遍历生成先序遍历结合起来凑成合法的树(旋转的顺序问题),然后枚举计算。。但是开始写发现不少细节都不会,而且那个数据范围好像也不打算给我这种想dfs生成排列的考生骗分的机会。
剩下好长时间不知道干嘛,于是就开始默写网络流的板子*n