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

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

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

 

posted @ 2021-09-19 08:09  HZOI_LYM  阅读(40)  评论(0编辑  收藏  举报