Loading

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;
}
posted @ 2022-08-11 23:05  Skylakes  阅读(15)  评论(0)    收藏  举报