NOIP模拟56
T1:
原题,树形DP+贪心即可
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define P pair<I,I> 9 #define MP make_pair 10 #define fir first 11 #define sec second 12 const I N = 1e6 + 3; 13 I n,f[N],MA[N],dp[N]; 14 I tot,head[N],to[N << 1],nxt[N << 1]; 15 P a[N]; 16 inline I read() { 17 I x(0),y(1); C z(getchar()); 18 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 19 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 20 return x * y; 21 } 22 inline V found (I x,I y) { 23 to[++tot] = y, nxt[tot] = head[x], head[x] = tot; 24 to[++tot] = x, nxt[tot] = head[y], head[y] = tot; 25 } 26 inline V Max (I &a,I b) { a = a > b ? a : b; } 27 inline V Min (I &a,I b) { a = a < b ? a : b; } 28 inline I max (I a,I b) { return a > b ? a : b; } 29 inline I min (I a,I b) { return a < b ? a : b; } 30 inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; } 31 V Dfs (I x,I deep) { 32 I cnt(0); 33 for (I i(head[x]),y(to[i]); i ;i = nxt[i],y = to[i]) if (y != f[x]) 34 Dfs (y,deep + 1), Max (MA[x],MA[y] + 1); 35 for (I i(head[x]),y(to[i]); i ;i = nxt[i],y = to[i]) if (y != f[x]) 36 a[cnt++] = MP (MA[y],y); 37 if (!cnt) return (V)(dp[x] = deep); 38 sort (a,a + cnt); 39 for (I i(1);i < cnt; ++ i) 40 if (a[i - 1].fir + 1 < deep) 41 dp[a[i].sec] -= deep - a[i - 1].fir - 1; 42 else 43 break; 44 for (I i(0);i < cnt; ++ i) 45 dp[x] += dp[a[i].sec]; 46 } 47 signed main () { 48 n = read(); 49 for (I i(2);i <= n; ++ i) 50 f[i] = read(), found (f[i],i); 51 Dfs (1,0); 52 printf ("%d\n",dp[1]); 53 }
T3:
首先感性理解的话,对于每种情况出现概率相同是显然的
考虑证明:设出现的情况为a1...an
那么对于ai其需要的1的个数为(ai - 1),显然的可重集排列m! / ∏(ai-1)!
接下来考虑对于该情况出现的概率,考虑首先比较显然的是总情况数为n*(n+1)*...*(n+m-1)
那么考虑对于该情况,总的出现次数为∏(ai-1)!(将每个集合进行排列即可)
于是对于每种情况出现概率即为m! / ∏(ai-1)! * ∏(ai-1)! / n*(n+1)*...*(n+m-1)
接下来首先考虑如何计算前r大数字之和,考虑若枚举前r大数字必然超时,如何消去不同数字的特性
转化为相同的计算结构,比较套路的做法为考虑1的贡献,对于1会作出贡献当且仅当其属于最大的r个数
于是考虑转化问题为计算最大的r个数中有多少个1,考虑类似与素因子筛,我们可以构造出一种筛法
使得仅满足一定条件的1会被计算,于是设f(i,j)表示对于所有情况中至少有i个数大于j的情况数
那么枚举i的过程相当于枚举当前在计算第i大的数的贡献,枚举j的过程相当与枚举第i大的数的所有可能情况
当且仅当第i大的数大于j时会作出一次贡献,于是当j枚举结束时即为所有情况中第i大的数所能造成的贡献
接下来考虑如何计算f(i,j),观察函数形式,比较套路的设g(i,j)为恰好有i个数大于j
那么显然f函数可以转化为g函数后缀和的形式,考虑如何计算g函数,比较显然的容斥原理(奇加偶减)
g(i,j) = sigma(k)(-1)^(k-i) * C(n,k) * C(k,i) * C(n - 1 - m - j*k,n - 1)
首先枚举至少有k个数大于j,然后C(n,k)钦定k个数,C(k,i)为其对g(i,j)的贡献次数,C(n - 1 - m - j*k,n - 1)为可重集组合
因为要满足至少有k个数大于j,于是首先给每个数分配j,剩余的数随意分配即可
因为要保证合法,所以i*j<=m,j*k<=m
时间复杂度sigma(m/i)^2
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define P pair<I,I> 9 #define MP make_pair 10 #define fir first 11 #define sec second 12 const I N = 5e3 + 3; 13 const I mod = 1e9 + 7; 14 I n,m,Inv,ans,J[N << 1],Y[N << 1],g[N][N]; 15 inline I read() { 16 I x(0),y(1); C z(getchar()); 17 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 18 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 19 return x * y; 20 } 21 inline V Max (I &a,I b) { a = a > b ? a : b; } 22 inline V Min (I &a,I b) { a = a < b ? a : b; } 23 inline I max (I a,I b) { return a > b ? a : b; } 24 inline I min (I a,I b) { return a < b ? a : b; } 25 inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; } 26 inline I fp (I a,I b) { I ans(1); 27 for (; b ;b >>= 1, a = 1ll * a * a % mod) 28 if (b & 1) ans = 1ll * ans * a % mod; 29 return ans; 30 } 31 inline I _C (I n,I m) { 32 return 1ll * J[n] * Y[m] % mod * Y[n - m] % mod; 33 } 34 signed main () { 35 n = read(),m = read(); J[0] = Y[0] = 1; 36 for (I i(1);i <= n + m; ++ i) J[i] = 1ll * J[i - 1] * i % mod; 37 Y[n + m] = fp (J[n + m],mod - 2); 38 for (I i(n + m - 1); i; -- i) Y[i] = 1ll * Y[i + 1] * (i + 1) % mod; 39 Inv = fp (_C(n + m - 1,n - 1),mod - 2); 40 for (I i(1);i <= n; ++ i) 41 for (I j(1),tmp1(m / i);j <= tmp1; ++ j) 42 for (I k(i),tmp2(m / j);k <= tmp2; ++ k) 43 (g[i][j] += (k - i & 1 ? -1ll : 1ll) * _C(n,k) * _C(k,i) % mod * _C(n + m - j * k - 1,n - 1) % mod) %= mod; 44 for (I j(1);j <= m; ++ j) 45 for (I i(m / j);i >= 1; -- i) 46 g[i][j] = (g[i][j] + g[i + 1][j]) % mod; 47 for (I i(1);i <= n; ++ i) { 48 for (I j(m / i);j >= 1; -- j) 49 ans = (ans + g[i][j]) % mod; 50 printf ("%lld\n",(1ll * ans * Inv % mod + i + mod) % mod); 51 } 52 }
Update:补坑:关于反演系数的计算
二项式反演中集合计数的做法一直有所困惑,根据定义:
f为集合元素恰好为i的方案数,g为集合元素至少为i的方案数
那么显然f为g函数的后缀和,然而题解做法给出的确是组合系数
当时给出的理解为因为g函数的计算过程中存在问题那么这种反演系数恰好能修正这种问题
接下来给出看起来更合理的解释:
考虑事实上反演的原理与正确性都是相同的,原因在于函数计算方式的不同,对于本题而言
f函数直接定义为g函数的后缀,显然具有正确性,那么这要求g函数的计算必须完全准确
考虑集合计数,显然可以发现g函数的计算存在重复,然而并不影响结果,原因就在于计算方式
其f函数定义g函数的贡献时是考虑每组数的贡献,这种定义方式本身就存在问题,换言之,
这种定义方式与计算方式一一对应,因此,观察两道题的特点就在于此
集合计数是g函数利用公式直接简洁计算,而总结结果f时利用容斥计算
而本题总结结果时直接利用g后缀和计算f,而计算g时需要容斥计算
因此这两种方式本质上是相同的,需要注意的只是在不同情况下选择不同方法,定义的计算方式要一一对应
T3:
比较具有思维量的一道题。
直接逐一从注意点(学习点)分析:
学习点1:首先观察数据范围与时间限制可以估计,正解复杂度约为O(nlogn)级别,考虑如何入手
比较直观的思路为考虑每一个点(l,r区间),计算其对于贡献,累计到对应的a,b区间中,
然而分析时间复杂度发现,很容易被卡成O(n^2logn),原因在于若考虑l,r区间想对于a,b区间的贡献
当存在大量a,b区间重合时,需要多次计算同一l,r区间的贡献,而冗余计算就在于此
考虑优化算法,上一种思路的问题在于尽管将问题转化为O(n)级别,然而计算量却非简单的O(1)
这种问题通常在于仍然对于问题独立考虑,解决方法显然就是考虑如何利用共性计算
这种区间问题很套路的一种方法就是考虑一段区间贡献总量,利用数据结构维护区间,计算问题区间的答案
学习点2:推式子很关键
考虑在区间a,b中比较显然的是选择方案共有len * (len + 1) / 2种,枚举方案显然O(n^2)滚粗,非常套路的是考虑区间中每一个点(l,r)的贡献
显然一个点会被选择(i - l + 1) * (r - i + 1)次,乘法原理,将选择左端点的方案数与选择右端点的方案数相乘即可,然而式子并不完全正确
考虑问题问区间总长度,当存在相同区间时,设上一次区间出现的位置为pos,那么对应的计算式为(i - pos) * (r - i + 1),
既然是推式子,显然不能就此结束,考虑将式子进一步化简得到:ri - i^2 + i - rl + li - l + r + i + 1 与 ri - i^2 + i - rpos + posi - pos
(很多问题中都需要维护多项式系数,而将多项式拆开也有可能有新的发现(本题),因此在推式子时不妨多一步将式子展开)
学习点3:ODT维护区间覆盖类操作,后缀BIT维护多项式系数
那么基本思路比较显然,从左到右扫描区间每个点,计算其对整个区间的贡献,统计答案时在区间上进行区间求和即可
考虑具体实现(由于对于ODT理解并不深刻,于是直接讲正解):
考虑由于在推式子过程中发现需要每个l,r区间上一次出现的位置,于是考虑在扫描过程中记录l,r区间出现的次数,并时刻更新
这里采用ODT,利用set进行维护,每次区间覆盖操作将区间端点所在区间从set中取出并截断,将原来l,r之间的区间删除后再见新的区间插入
学习点1.1:由于可能区间只有一部分与之前重复,那么ODT的区间分裂操作刚好可以适应这种需求,对于每一段pos不同的区间都有一段独立的小区间
这里以pos进行划分可以将区间贡献划分为若干小区间的贡献单独考虑每一段
考虑每个点的共性,发现上述式子中每一个点的贡献之与点的位置有关,于是可以在ODT删除操作时,通过位置计算其造成的多项式系数,利用树状数组维护
最后对于每个a,b,用l,r,l * r,分别乘以区间内系数在加上常数即可(维护多项式系数是常见思路)
细节:
首先将a,b区间按右端点排序是使得每个区间消去右端点的限制,指针单调右移
在树状数组区间修改过程中只修改上一次出现位置到本次出现位置,而不修改a到b是由于要保证区间中有该点才会作出贡献
若直接修改a,b,可能下个区间并不包含该点然而与当前区间有交集,那么就会造成多余贡献
在树状数组区间修改的过程中,由于要保证对于不同区间有不同计算方式(考虑上一次出现位置),
本题的操作是首先直接在树状数组上更新系数,通过观察式子发现两种计算方式有一定相似,那么可以在树状数组中
上一次出现的位置上通过调配系数,使得在同时包含两个区间时,本区间的计算方式有所改变,具体见代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I long long 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define P pair<I,I> 9 #define MP make_pair 10 #define fir first 11 #define sec second 12 #define lowbit(x) (x & -x) 13 #define It(stl,typ) stl <typ> :: iterator 14 const I N = 2e5 + 3; 15 const I mod = 1e9 + 7; 16 I n,m,Tinv,It,l[N],r[N],ans[N]; 17 struct L {I idx,l,r;} q[N]; 18 inline I read() { 19 I x(0),y(1); C z(getchar()); 20 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 21 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 22 return x * y; 23 } 24 inline V Max (I &a,I b) { a = a > b ? a : b; } 25 inline V Min (I &a,I b) { a = a < b ? a : b; } 26 inline I max (I a,I b) { return a > b ? a : b; } 27 inline I min (I a,I b) { return a < b ? a : b; } 28 inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; } 29 inline B com (const L &a,const L &b) { return a.r < b.r; } 30 inline I fp (I a,I b) { I ans(1); 31 for (; b ;b >>= 1, a = a * a % mod) 32 if (b & 1) ans = ans * a % mod; 33 return ans; 34 } 35 struct BIT { 36 I c[N]; 37 inline V Modify (I x,I y) { 38 for (; x ;x -= lowbit(x)) c[x] = (c[x] + y) % mod; 39 } 40 inline I Query (I x) { I ans(0); 41 for (;x <= n;x += lowbit(x)) ans = (ans + c[x]) % mod; 42 return ans; 43 } 44 }cont,coel,coer,coep; 45 struct ODT { 46 struct S { 47 I l,r; mutable I val; S () {} 48 S (I a,I b,I c) { l = a, r = b, val = c; } 49 B operator < (const S &b) const { return l < b.l; } 50 }; 51 set <S> s {S(0,INT_MAX,0)}; 52 inline It (set,S) split (I pos) { 53 It (set,S) it = s.lower_bound (S(pos,0,0)); 54 if (it != s.end () && it -> l == pos) return it; 55 -- it; 56 I l (it -> l), r (it -> r), val (it -> val); 57 s.erase (it); s.insert (S(l,pos - 1,val)); 58 return s.insert (S(pos,r,val)).fir; 59 } 60 inline V Modify (I l,I r,I val) { 61 It (set,S) itr (split (r + 1)), itl (split (l)); 62 while (itl != itr) { 63 I len (itl -> r - itl -> l + 1), last (itl -> val); 64 coel.Modify (val,len * (val - 1) % mod); 65 coer.Modify (val,len * (val + 1) % mod); 66 coep.Modify (val,len * -1 % mod); 67 cont.Modify (val,len * (1 - val * val % mod) % mod); 68 coel.Modify (last,len * (1 - val) % mod); 69 coer.Modify (last,len * (-1 - last) % mod); 70 coep.Modify (last,len % mod); 71 cont.Modify (last,len * (val - 1) % mod * (last + 1) % mod); 72 s.erase (itl ++ ); 73 } 74 s.insert (S(l,r,val)); 75 } 76 }ODT; 77 signed main () { 78 n = read(), m = read (); Tinv = fp (2,mod - 2); 79 for (I i(1);i <= n; ++ i) l[i] = read(), r[i] = read() - 1; 80 for (I i(1);i <= m; ++ i) q[i].idx = i, q[i].l = read(), q[i].r = read(); 81 sort (q + 1,q + m + 1,com); 82 for (I i(1);i <= m; ++ i) { 83 I len (q[i].r - q[i].l + 1), Inv (fp(len * (len + 1) % mod * Tinv % mod,mod - 2)); 84 while (It < q[i].r) It ++ , ODT.Modify (l[It],r[It],It); 85 (ans[q[i].idx] += coel.Query (q[i].l) * q[i].l % mod) %= mod; 86 (ans[q[i].idx] += coer.Query (q[i].l) * q[i].r % mod) %= mod; 87 (ans[q[i].idx] += coep.Query (q[i].l) * q[i].l % mod * q[i].r % mod) %= mod; 88 (ans[q[i].idx] += cont.Query (q[i].l)) %= mod; 89 ans[q[i].idx] = ans[q[i].idx] * Inv % mod; 90 } 91 for (I i(1);i <= m; ++ i) printf ("%lld\n",(ans[i] + mod) % mod); 92 }