2022.8.11 颓废记录
Preface
容斥原理真好玩!
Content
[CF156C]Cipher
给定一个长为 \(len\),由小写字母组成的字符串 \(s\),有两种操作:
选定 \(p(1\le p\lt len)\),使得 \(s_p\) 变为前一个字母,\(s_{p+1}\) 变成后一个字母。
选定 \(p(1\le p \lt len)\),使得 \(s_p\) 变为后一个字母,\(s_{p+1}\) 变为前一个字母。
特别的,\(\texttt{a}\) 不能变为前一个字母,\(\texttt{z}\) 不能变为后一个字母。
如果 \(s_1\) 可以通过若干次操作变为 \(s_2\),则称 \(s_1\) 和 \(s_2\) 是“一致”的。
求有多少个字符串和 \(s\) “一致”。
\(1\le len \le 100,1\le t \le 10^4\)。
Solution-1
很有意思的一道题。
看完题就能发现,如果把 \(a\sim z\) 映射到 \(1\sim 26\),设这个映射为 \(f\),那么题目要求的其实就是满足 \(\sum\limits_{i=1}^{len} f(s'_i)=\sum\limits_{i=1}^{len}f(s_i)\) 的 \(s'\) 的数量。
设 \(S=\sum\limits_{i=1}^{len} f(s_i)\),那么这个问题又可以化为把 \(S\) 划分为 \(len\) 个 \([1,26]\) 内的整数的方案数。
显然可以 \(O((26\times len)^2)\) 预处理,然后 \(O(t\times len)\) 查询。
Code-1
注意,实际代码中我把 \(a\sim z\) 映射到了 \(0\sim 25\)。
// Problem: CF156C Cipher
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF156C
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int maxm = 3005;
const int mod = 1e9 + 7;
char s[maxn];
int f[maxn][maxm];
void work() {
scanf("%s",s + 1);
int len = strlen(s + 1),cnt = 0;
for(int i = 1;i <= len;++ i)cnt += s[i] - 'a';
printf("%d\n",(f[len][cnt] - 1 + mod) % mod);
return ;
}
int main() {
f[0][0] = 1;
for(int i = 1;i <= 100;++ i) {
for(int k = 0;k <= 2500;++ k) {
for(int j = 0;j <= 25;++ j) {
if(j > k)break ;
(f[i][k] += f[i - 1][k - j]) %= mod;
}
}
}
int T;
scanf("%d",&T);
while(T --)work();
return 0;
}
Solution-2
其实我们会发现,把上面那个问题写作:
\(\sum\limits_{i=1}^{len} x_i=S(1\le x_i\le 26)\) 的方案数,那么这题就可以化为一道经典的容斥问题。
具体可以看 容斥原理 - OI Wiki 这里面的介绍。
时间复杂度 \(O(t\times len)\)。
Code-2
// Problem: CF156C Cipher
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF156C
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define N 2600
typedef long long ll;
using namespace std;
const int maxn = 105;
const int maxm = 2605;
const ll mod = 1e9 + 7;
ll frac[maxm],inv[maxm];
ll power(ll x,ll y) {
ll ans = 1;
for(;y;y >>= 1) {
if(y & 1)(ans *= x) %= mod;
(x *= x) %= mod;
}
return ans;
}
ll C(int n,int m) {
if(n < m||m < 0)return 0ll;
return frac[n] * inv[m] % mod * inv[n - m] % mod;
}
char s[maxn];
void work() {
scanf("%s",s + 1);
int cnt = 0,n = strlen(s + 1);
for(int i = 1;i <= n;++ i)cnt += s[i] - 'a' + 1;
ll ans = 0;
for(int i = 0,f = 1;i <= n;++ i,f *= -1) {
ans = (ans + 1ll * f * C(n , i) * C(cnt - 26 * i - 1 , n - 1) % mod + mod) % mod;
}
printf("%lld\n",(ans - 1 + mod) % mod);
return ;
}
int main() {
frac[0] = 1ll;
for(int i = 1;i <= N;++ i)frac[i] = 1ll * i * frac[i - 1] % mod;
inv[N] = power(frac[N] , mod - 2ll);
for(int i = N - 1;~ i;-- i)inv[i] = 1ll * (i + 1) * inv[i + 1] % mod;
int T;
scanf("%d",&T);
while(T --)work();
return 0;
}
[HAOI2008] 硬币购物
\(4\) 种硬币,价值分别为 \(c_1,c_2,c_3,c_4\)。
\(n\) 次询问,每次第 \(i\) 种硬币有 \(d_i\) 个,求价值和为 \(s\) 的方案数。
\(1\le c_i,d_i,s \le 10^5,1\le n\le 10^4\)。
发现用单调队列优化多重背包照样会有 \(O(ns)\) 的时间复杂度,原地去世。
注意到硬币的种类数相当相当小,考虑容斥原理。
(好吧懒得写了,摆烂,想看题解的话还是点上面 OI-Wiki 的链接叭)
Code
// Problem: P1450 [HAOI2008] 硬币购物
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1450
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 5;
int c[4],d[4],s;
ll f[maxn];
void work() {
scanf("%d %d %d %d %d",&d[0],&d[1],&d[2],&d[3],&s);
ll ans = 0;
for(int k = 1;k < 16;++ k) {
int popcnt = 0,cur = s;
for(int i = 0;i < 4;++ i) {
if(k >> i & 1) {
++ popcnt;
cur -= (d[i] + 1) * c[i];
}
}
if(cur >= 0)ans += ((popcnt & 1) ? 1 : -1) * f[cur];
}
printf("%lld\n",f[s] - ans);
return ;
}
int main() {
s = 1e5;
int T;
scanf("%d %d %d %d %d",&c[0],&c[1],&c[2],&c[3],&T);
f[0] = 1;
for(int k = 0;k < 4;++ k) {
for(int i = 1;i <= s;++ i) {
if(i >= c[k])f[i] += f[i - c[k]];
}
}
while(T --)work();
return 0;
}
[CF786A]Berzerk
太长了QAQ,不写简要题意了。
这题和组合什么的没啥关系,显然不能用普通的博弈论的方法来套。
注意到一个结论:必胜态转移到的一定是必败态,必败态转移到的一定是必胜态。
设 \(f(i,0/1)\) 表示 Rick/Morty 在 \(i\) 号节点起手的胜/败。
显然,\(f(1,0/1)\) 为必败态,那么它能转移到的地方一定都是必胜态。
如果一个状态不管怎么转移都是对方的必胜态,那么它一定是必败态。
根据这个结论,记忆化搜索一下就行。时间复杂度 \(O(N^2)\)。
Code
// Problem: CF786A Berzerk
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF786A
// Memory Limit: 250 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 7e3 + 5;
int n,m,k;
int a[2][maxn];
int f[2][maxn],c[2][maxn];
bool vis[2][maxn];
int LastStep(int x) {
return x > 0 ? x : x + k;
}
void DP(int p,int x) {
if(vis[p][x])return ;
vis[p][x] = true;
if(!p) {
for(int i = 1;i <= m;++ i) {
int q = LastStep(x - a[1][i]);
if(vis[1][q]||q == 1)continue ;
if(f[p][x] == -1) {
f[1][q] = 1;
DP(1 , q);
}
else if(f[p][x] == 1) {
++ c[1][q];
if(c[1][q] == m) {
f[1][q] = -1;
DP(1 , q);
}
}
}
}
else {
for(int i = 1;i <= n;++ i) {
int q = LastStep(x - a[0][i]);
if(vis[0][q]||q == 1)continue ;
if(f[p][x] == -1) {
f[0][q] = 1;
DP(0 , q);
}
else if(f[p][x] == 1) {
++ c[0][q];
if(c[0][q] == n) {
f[0][q] = -1;
DP(0 , q);
}
}
}
}
return ;
}
int main() {
scanf("%d",&k);
scanf("%d",&n);
for(int i = 1;i <= n;++ i)scanf("%d",&a[0][i]);
scanf("%d",&m);
for(int i = 1;i <= m;++ i)scanf("%d",&a[1][i]);
f[0][1] = f[1][1] = -1;
DP(0 , 1);
DP(1 , 1);
for(int i = 2;i <= k;++ i) {
if(f[0][i] == 1)printf("Win ");
else if(!f[0][i])printf("Loop ");
else printf("Lose ");
}
puts("");
for(int i = 2;i <= k;++ i) {
if(f[1][i] == 1)printf("Win ");
else if(!f[1][i])printf("Loop ");
else printf("Lose ");
}
return 0;
}
[CF621E]Wet Shark and Blocks
\(b\) 个格子,每个格子里有相同的 \(n\) 个数 \(a_{1\sim n}\),请从每个格子的 \(n\) 个数中选出一个组成一个 \(b\) 位数,求使得这个数模 \(x\) 为 \(k\) 的方案数。对 \(10^9+7\) 取模。
\(2\le n\le 5\times 10^4,1\le b\le 10^9,0\le k\lt x,x\ge 2\)。
看错题导致白白耗了 1h QAQ,看懂题就秒切了。
首先一看 \(b,k,x\) 的范围就能猜到这是矩阵快速幂优化 DP。
先把朴素的 DP 列出来。
\(f(i,j)\) 可以转移到的状态为 \(f(i+1,10\times j+a_k)(k\in [1,n])\)。
初始状态:\(f(0,0)=1\)。
现在,让我们考虑矩阵优化。
设转移矩阵为 \(A\),答案矩阵 \(f\) 为行向量,初始 \(f_0=1,f_{1\sim x-1}=0\)。
把矩阵乘法的过程稍微画一画就能发现,\(A_{i,j}\) 要存的是余数 \(i\) 化为余数 \(j\) 的方案数。
显然 \(A\) 矩阵可以用 \(O(x\times n)\) 的时间复杂度预处理出来。
然后跑出 \(A^b\),答案就是 \((f\times A^b)\) 的第 \(k\) 列。
Code
// Problem: CF621E Wet Shark and Blocks
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF621E
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int maxm = 5e4 + 5;
const int maxn = 105;
int n,b,k,x;
int a[maxm];
struct matrix {
int g[maxn][maxn];
matrix() {
memset(g , 0 , sizeof(g));
}
void getinit() {
for(int i = 0;i < x;++ i) {
g[i][i] = 1;
}
return ;
}
matrix operator * (const matrix& p)const {
matrix c;
for(int q = 0;q < x;++ q) {
for(int i = 0;i < x;++ i) {
for(int j = 0;j < x;++ j) {
(c.g[i][j] += 1ll * g[i][q] * p.g[q][j] % mod) %= mod;
}
}
}
return c;
}
}f,A;
matrix power(matrix a,int y) {
matrix ans;
ans.getinit();
for(;y;y >>= 1) {
if(y & 1)ans = ans * a;
a = a * a;
}
return ans;
}
int main() {
scanf("%d %d %d %d",&n,&b,&k,&x);
for(int i = 1;i <= n;++ i)scanf("%d",&a[i]);
f.g[0][0] = 1;
for(int i = 1;i <= n;++ i) {
for(int j = 0;j < x;++ j) {
++ A.g[j][(j * 10 + a[i]) % x];
}
}
f = f * power(A , b);
printf("%d\n",f.g[0][k]);
return 0;
}
[CF235B]Let's Play Osu!
长为 \(n\) 的字符串,第 \(i\) 位为 \(\texttt{O}\) 的概率为 \(p_i\),为 \(\texttt{X}\) 的概率为 \(1-p_i\)。
定义这样一个字符串的得分为所有极长连续 \(\texttt{O}\) 子串的长度的平方和。
eg:\(\texttt{OOXOOOXXOO}\),得分为 \(2^2+3^2+2^2=17\)。
求得分的期望值。\(1\le n\le 10^5\)。
一直在想着暴力转移,就是没有想到直接把长度的期望值算出来,还是太蒻啦 qwq。
设 \(len_i\) 表示以 \(i\) 为结尾的 \(\texttt{O}\) 子串的期望长度,\(f_i\) 表示 \(1\sim i\) 的期望得分。
显然有 \(len_i=(len_{i-1}+1)\times p_i\)。
如果知道了 \(len_i\),那么原本复杂的 \(f_i\) 也就呼之欲出了。
状态转移方程:\(f_i=f_{i-1}\times (1-p_i)+(f_{i-1}+len_{i-1})\times p_i\)。
时间复杂度 \(O(N)\)。
Code
// Problem: CF235B Let's Play Osu!
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF235B
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n;
double p[maxn],len[maxn],f[maxn];
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;++ i)scanf("%lf",&p[i]);
f[0] = 0;
len[0] = 0;
for(int i = 1;i <= n;++ i) {
len[i] = (len[i - 1] + 1) * p[i];
f[i] = f[i - 1] * (1 - p[i]) + (f[i - 1] + 2.0 * len[i - 1] + 1) * p[i];
}
printf("%.8lf\n",f[n]);
return 0;
}

假期余额不足www
浙公网安备 33010602011771号