NOIP模拟56

T1:

  原题,树形DP+贪心即可

代码如下:

NOIP模拟56
 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

代码如下:

NOIP模拟56
 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时需要容斥计算

因此这两种方式本质上是相同的,需要注意的只是在不同情况下选择不同方法,定义的计算方式要一一对应

 

上一篇:NOIP模拟58


下一篇:[CF505E]Mr. Kitayuta vs. Bamboos & Dantalian's