NOIP模拟51
T1:
首先比较套路的是可以枚举右端点考虑其影响
线性序列求方案数首先想到的就是线性DP
考虑分析题目性质,每次施法将一段首尾相同的区间颜色全部转化为相同的颜色,
那么很显然的是当前位置进行的施法不受前后的影响,考虑状态划分
首先若当前位置选择不施法,那么直接继承上一位置的方案数即可(在上一位置的每种方案后增添一位)
考虑在当前位置施法的影响,那么对于之前所有位置中只有与当前位置颜色相同的位置为合法的施法位置
考虑由于当前位置施法影响不受前后干涉,那么假设当前位置为i,之前位置中与当前位置颜色相同的位置为j
考虑将i~j转化为同一种颜色所造成的方案数为dp[j - 1](即钦定i~j为颜色k,剩余位置随便选)
于是初步的DP方程为dp[i] = dp[i - 1] + sigma(dp[j - 1])(c[j] == c[i])
然而发现样例过不了,考虑DP的问题,发现该DP的原理在于:
钦定某段位置的颜色,在剩余位置中随意选取颜色,其正确性在于由于DP从左往右进行,
那么对于当前位置施法形成为区间在之前的DP过程中一定不存在,也就是当前位置施法一定合法
然而考虑特殊情况进行hack,即当存在连续相同颜色的一段时,上述DP会造成重复计算,
问题在于对于连续相同的一段颜色,施法的造成的相同颜色段本质上相同
因此对DP方程修正得,对于相同的一段颜色取最右端DP值后break即可
小问题:测试样例时一定要开freopen测试,对于大样例手动输入可能不准
注意考虑特殊情况即可
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 const I N = 1e6 + 3; 6 const I mod = 1e9 + 7; 7 I n,a,b,c,d; 8 vector <I> val[N]; 9 inline I read() { 10 I x(0),y(1); C z(getchar()); 11 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 12 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 13 return x * y; 14 } 15 signed main () { 16 n = read(); b = 1; 17 for (I i(1);i <= n; ++ i) { c = read(), a = b; 18 if (c == d) 19 val[c].clear (); 20 else 21 for (auto y : val[c]) a = (a + y) % mod; 22 val[c].push_back (b); d = c, b = a; 23 } 24 printf ("%d\n",a); 25 }
T2:
首先对于题目给出的奖励分计算机制求期望显然可以线性转化
考虑最终奖励分的期望实际上就是每一种奖励分段的期望
考虑设当前平局次数为k,那么奖励分段即为k + 1
首先直接思路为一个比较显然的排列组合:
C(n,i) * (p / p + 2)^i * C(n - i,(n - i >> 1) + 1) * (1 / p + 2)^((n - i >> 1) + 1) * (2 / p + 2)^(n - i - (n - i >> 1) - 1) * (k + 1)
式子的意义为首先在n局中钦定i局为平局,接着在n-i局中钦定(n - i >> 1) + 1局胜利(产生贡献所需的最小胜利次数),接着计算剩余位置胜负皆可的概率,最终乘以贡献
然而是错的,存在重复状态,考虑错误点:
对于钦定i局平局后剩余的n-i局中当我们钦定胜局后采取的计算方式为让剩余局随意胜负
然而考虑设胜为1,负为0,那么显然存在相同的状态
原因在于对于同一组选择项中钦定与随机的状态存在重复
然而本题由于采用组合数的钦定方式,又无法排除重复情况,考虑修正
那么考虑将随机转化为枚举胜局,得到公式:
C(n,i) * (p / p + 2)^i * sigma(k)C(n - i,k)(1 / p + 2)^k * (1 / p + 2)^(n - i - k)
直接枚举显然TLE,然而仔细观察发现后面可以合并得到(1 / p + 2)^(n - i)与k无关,直接提取得到:
C(n,i) * (p / p + 2)^i * (1 / p + 2)^(n - i) * sigma(k)
于是问题转化为计算sigma(k,(n - i >> 1) + 1,n - i)
突破点在于k的枚举从(n - i >> 1) + 1开始,即n - i的一半
考虑C(n,0) + C(n,1) + ... + C(n,n) = 2^n
那么2^(n - 1) = C(n,n) + C(n,n - 1) + ... + C(n,n >> 1)
那么直接计算即可,注意判断n - i + 1的奇偶性即可
学习点:组合数枚举注意重复,扩大考虑,组合数的意义,考虑组合问题需要注意元素的集合
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 const I N = 3e6 + 3; 6 const I mod = 998244353; 7 I n,p,a,t,f,ans,J[N],Y[N]; 8 inline I read() { 9 I x(0),y(1); C z(getchar()); 10 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 11 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 12 return x * y; 13 } 14 inline I fp (I a,I b) { I ans(1); 15 for (; b ;b >>= 1,a = 1ll * a * a % mod) 16 if (b & 1) ans = 1ll * ans * a % mod; 17 return ans; 18 } 19 inline I _C (I n,I m) { 20 return 1ll * J[n] * Y[m] % mod * Y[n - m] % mod; 21 } 22 signed main () { 23 n = read(), p = read(); 24 a = fp (p + 2,mod - 2), t = fp (2,mod - 2), f = fp (a,n); 25 J[0] = Y[0] = 1; 26 for (I i(1);i <= n; ++ i) J[i] = 1ll * J[i - 1] * i % mod; 27 Y[n] = fp (J[n],mod - 2); 28 for (I i(n - 1); i; -- i) Y[i] = 1ll * Y[i + 1] * (i + 1) % mod; 29 for (I i(0),s1(fp(2,n)),s2(1);i < n; ++ i,s1 = 1ll * s1 * t % mod,s2 = 1ll * s2 * p % mod) 30 (ans += 1ll * _C(n,i) * f % mod * s2 % mod * (s1 - (n - i + 1 & 1) * _C(n - i,n - i >> 1)) % mod * t % mod * (i + 1) % mod) %= mod; 31 printf ("%d\n",ans); 32 }
T3:
考虑题解一维情况下单调栈+线段树的做法:
考虑类似T1从右向左枚举,考虑右端点贡献,根据定义
对于所有左端点若:MA[l,r] - MI[l,r] == r - l,那么[l,r]作出r - l + 1的贡献
考虑r为变量,然而l相对为定值,很套路的提取l得:
MA[l,r] - MI[l,r] + l == r,那么考虑设等式左侧为f[l]
显然f[l] >= r,那么维护r左侧所有f[l]取最小值判断是否可行即可
考虑如何计算r右移对MA,MI的影响,
仍然一种套路做法,考虑每个点作为MI,MA的区间,可以单调栈O(n)求出
那么在r右移过程中在线段树上区间修改即可