我是耐断更王。
至于我为啥又开始更了呢?因为我发现,不把解题思路记下来真的会忘。
因为今天要把所有的没写解题思路的题补上,所以就不分上午下午的了。
P10986 [蓝桥杯 2023 国 Python A] 2023↗
我们看到“恰好”两个字,就想到先推“至少”的情况,再用二项式反演的式子推到“恰好”。
设 表示至少有 个 2023 的方案数。我们钦定有 个 2023,再用插板法↗,在这些 2023 的缝里插入那些乱七八糟的数。因为插入的时候会再凑出一些 2023,所以是“至少”。那么,在 个 2023 中插入 个数的方案是
设 表示恰好有 个 2023 的方案数,那么有:
根据二项式反演公式,我们得到:
那么我们就求得了 ,就是答案。时间复杂度
BZOJ2839 集合计数↗
还是由“至少”转到“恰好”。
设 表示钦定有 个相同元素的情况,那么,剩下的 个元素能够组成 个集合,而,这 个集合又能够再组成 个集合。那么显然有
设 表示恰好有 个相同元素的情况,那么有:
反演一下得到:
我们就得到了 。时间复杂度 。
P1450 [HAOI2008] 硬币购物↗
我们先考虑没有限制的情况: 表示没有限制的情况下买价格为 的物品的方案数,容易发现这就是一个完全背包,可以在 的复杂度下预处理,其中 表示最大价格。然后接下来考虑限制的情况。发现正着考虑不超过限制的情况不太好做,正难则反。
我们考虑超过限制的情况。超过限制就是第 个硬币强制选 个,剩下的依然随便选,那么情况数就是 ,答案就要减去 。这是单个硬币超出限制的情况,如果有多个硬币,就得请出我们的容斥原理了。但是,容斥原理的公式要求任意两个集合的交集和并集大小相等,这很明显不相等啊,我们又发现:只有 种硬币,那我们直接枚举不就行了吗?无非就是带一个 的常数嘛。
核心代码:
int ans = dp[s];//dp是预处理好的完全背包for (int k = 1; k < (1 << 4); k++){ int tmp = s; for (int i = 0; i < 4; i++) if (k & (1 << i)) tmp -= (d[i] + 1) * c[i]; if (tmp >= 0) ans += (__builtin_popcount(k) & -1 : 1) * dp[tmp];//容斥}P5505 [JSOI2011] 分特产↗
还是一道很板的二项式反演。首先设 表示至少有 个同学没有拿到特产的方案数,那么有:
后面那坨表示将 个物品分成 个可空集的方案数,而前面的是因为我们并没有钦定哪几个人没有,所以要乘一个在 个人里面选 个的方案数。
接下来我们开始反演。设 表示正好有 个人没有,那么有反演公式:
而我们要让每一个人都拿到,答案就是 。这样,我们就做完了。时间复杂度
P6521 [CEOI2010 day2] pin↗
发现考虑不同的方案数有点难,正难则反。
依然先考虑“至少”。我们设 为至少有 位相同的方案数。这个可以用哈希求解。我们设哈希函数:
int hsh(char ch){return isdigit(ch) ? ch - '0' : ch - 'a' + 10;}int hsh(char ch1,char ch2){return hsh(ch1) * 36 + hsh(ch2);}int hsh(char ch1,char ch2,char ch3){return hsh(ch1) * 36 * 36 + hsh(ch2) * 36 + hsh(ch3);}分别用于求解一个字符,两个字符,三个字符的哈希值。很明显,这个哈希函数生成的哈希值不会超过 。那么我们记录 表示有多少个字符序列的哈希值是 ,然后要求解方案数呢,就相当于求解“在 个元素里随便取两个不同元素的方案数”,很明显是 。最后,我们求得了至少有 个元素相同的方案数,因为最多有 个元素相同,所以我们直接暴力容斥就行。
code:
for (int x = 1; x <= 4; x++)//一个字符相同的{ memset(tmp,0,sizeof tmp); for (int i = 1; i <= n; i++) tmp[hsh(s[i][x])] ++; for (int i = 0; i <= 6e4; i++) cnt[1] += (tmp[i] * (tmp[i] - 1)) >> 1;}for (int x = 1; x <= 4; x++)//两个字符相同{ for (int y = x + 1; y <= 4; y++) { memset(tmp,0,sizeof tmp); for (int i = 1; i <= n; i++) tmp[hsh(s[i][x],s[i][y])] ++; for (int i = 0; i <= 6e4; i++) cnt[2] += (tmp[i] * (tmp[i] - 1)) >> 1; }}for (int x = 1; x <= 4; x++)//三个字符相同{ for (int y = x + 1; y <= 4; y++) { for (int z = y + 1; z <= 4; z++) { memset(tmp,0,sizeof tmp); for (int i = 1; i <= n; i++) tmp[hsh(s[i][x],s[i][y],s[i][z])] ++; for (int i = 0; i <= 6e4; i++) cnt[3] += (tmp[i] * (tmp[i] - 1)) >> 1; } }}//以下是暴力容斥ans[3] = cnt[3];ans[2] = cnt[2] - 3 * ans[3];ans[1] = cnt[1] - ans[2] * 2 - ans[3] * 3;ans[0] = ((n * (n - 1)) >> 1) - ans[1] - ans[2] - ans[3];printf("%lld",ans[4 - d]);//由于求解的是相同的,所以不同的就是4 - d个P10597 BZOJ4665 小 w 的喜糖↗
依然是正难则反+二项式反演 演都不带演
还是从“至少”做起。设 表示至少有 个位置相同, 表示正好 个位置相同。那么有反演:
我们的答案就是
现在的问题就在于怎么求 。发现初始数组的顺序对于答案来说没有影响,那么我们就记录每种颜色的数量并去重,然后开始 dp。设 表示循环到第 个颜色,钦定有 个元素相同的方案数。那么有
很明显,,其中 表示颜色总数。然后我们就得出了答案,时间复杂度为 。
code:
sort(a + 1,a + n + 1);int m = unique(a + 1,a + n + 1) - a - 1,tmp = 0;dp[0][0] = 1;for (int i = 1; i <= m; i++){ tmp += cnt[a[i]]; for (int j = 0; j <= tmp; j++) for (int k = 0; k <= min(j,cnt[a[i]]); k++) dp[i][j] = (dp[i][j] + dp[i - 1][j - k] * c(tmp - j,cnt[a[i]] - k) % mod * c(cnt[a[i]],k) % mod) % mod;}int ans = 0;for (int i = 0; i <= n; i++) ans = ((ans + (i & 1 ? -1 : 1) * dp[m][i]) % mod + mod) % mod;P2567 [SCOI2010] 幸运数字↗
我们看完题先看数据范围:,直接枚举肯定不现实,我们看:欸☝,数位 dp。你要是这么想,那么恭喜你,跑偏了。
直接枚举每一个数不现实,那么我就枚举每个幸运号码呗,虽然也不现实,但是好歹比直接枚举每一个快吧。枚举每一个幸运号码,然后把是它倍数的都计算一下,可以证明 范围内的幸运号码不是很多,枚举的完。然后我们不出意外的发现,他 WA 了,究其原因是有些算重了。现在就该我们容斥出场了。发现第 个属性就是能不能被第 个幸运号码整除。设第 个号码为 ,那么 ,直接套公式容斥就好了。但是这样还是过不了。我们发现当有一个幸运号码是其他幸运号码的倍数时,这个幸运号码没有贡献。 用脚趾都想的出来
注意要开 __int128
#include<bits/extc++.h>#define int __int128#define lcm(x,y) (x == 0 ? y : x * y / __gcd(x,y))using namespace std;void read(int &x){ x = 0; short f = 1; char ch = getchar(); while (!isdigit(ch)){f = ch == '-' ? -1 : 1; ch = getchar();} while (isdigit(ch)){x = x * 10 + ch - '0'; ch = getchar();} x *= f;}void write(int x){ if (x > 9) write(x / 10); putchar(x % 10 + '0');}int a,b,ans;map<vector<int>::iterator,bool>vis;vector<int>pending,num;void dfs1(int x){ if (x > b) return; if (x) pending.push_back(x); dfs1(x * 10 + 6); dfs1(x * 10 + 8);}void dfs2(vector<int>::iterator idx,int cnt,int x){ if (x > b) return; if (idx == num.end()) { if (x == 0) return; ans += (cnt & 1 ? 1 : -1) * (floor(1.0 * b / x) - ceil(1.0 * a / x) + 1); return; } dfs2(idx + 1,cnt + 1,lcm(x,*idx)); dfs2(idx + 1,cnt,x);}signed main(){ read(a),read(b); dfs1(0); for (auto i = pending.begin(); i != pending.end(); i++) { if (!vis[i]) num.push_back(*i); for (auto j = i + 1; j != pending.end(); j++) if (*j % *i == 0) vis[j] = 1; } sort(num.begin(),num.end(),greater<int>()); dfs2(num.begin(),0,0); write(ans); return 0;}P8737 [蓝桥杯 2020 国 B] 质数行者↗
题解↗里说:
看到题目限制不能经过给定的两个点,自然想到容斥。
至于怎么自然而然想到容斥,我也不知道。多做点题就好了。
设 表示从 走到 的方案数。设 , 分别为不能走到的两个点。那么根据容斥原理,有:
现在我们想:如何计算 。我们看,麻烦之处在于只能走质数格,考虑没有这个限制如何做。类似二维的方法:,意义很好理解,这里就不说了。
现在再考虑烦人的质数限制。发现一步走多远不重要,重要的是走这么远要多少步。设 表示用 个质数走 格的方案数,那么这就是一个完全背包:
其中 表示质数集合。因为 最大只有 , 以内的质数也没有 个,所以预处理的时间是可以过的。
那依据此,我们得到了:
时间复杂度 。立即发生爆炸
我们看这三个 就不顺眼,想想能不能优化掉一个。
我们看能不能在枚举 的同时计算 呢?好像可以。设 ,那么有:
时间复杂度 ,就可以过了。
code:
#include<bits/extc++.h>#define int long long#define Inv(x) binpow(x,mod - 2)using namespace std;const int maxn = 3005;const int mod = 1e9 + 7;int n,m,w;int fac[maxn],inv[maxn];int f[maxn][maxn];bool ipr[maxn];vector<int>pr;struct node{ int x,y,z;}st,en,p1,p2;int binpow(int x,int y){ int ret = 1; while (y) { if (y & 1) ret = ret * x % mod; x = x * x % mod; y >>= 1; } return ret;}void init(){ for (int i = 2; i <= 3000; i++) { if (!ipr[i]) pr.push_back(i); for (auto j = pr.begin(); j != pr.end() && *j * i <= 3000; j++) { ipr[*j * i] = 1; if (i % *j == 0) break; } } int up = max(max(n,m),w); f[0][0] = 1; for (int i = 1; i <= up; i++) for (int j = 1; j <= i >> 1; j++) for (auto k = pr.begin(); k != pr.end() && *k <= i; k++) f[i][j] = (f[i][j] + f[i - *k][j - 1]) % mod; up *= 3; fac[0] = 1; for (int i = 1; i <= up; i++) fac[i] = fac[i - 1] * i % mod; inv[up] = Inv(fac[up]); for (int i = up - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;}int dp(node x,node y){ int dx = y.x - x.x; int dy = y.y - x.y; int dz = y.z - x.z; if (dx < 0 || dy < 0 || dz < 0) return 0; int ret = 0; for (int sum = 0; sum <= (dx >> 1) + (dy >> 1); sum++) { int sum1 = 0; for (int i = 0; i <= sum; i++) if (int j = sum - i; i <= (dx >> 1) && j <= (dy >> 1)) sum1 = (sum1 + f[dx][i] * inv[i] % mod * f[dy][j] % mod * inv[j] % mod) % mod; int sum2 = 0; for (int k = 0; k <= (dz >> 1); k++) sum2 = (sum2 + f[dz][k] * inv[k] % mod * fac[sum + k] % mod) % mod; ret = (ret + sum1 * sum2 % mod) % mod; } return ret;}signed main(){ scanf("%lld%lld%lld",&n,&m,&w); st = {1,1,1}; en = {n,m,w}; scanf("%lld%lld%lld",&p1.x,&p1.y,&p1.z); scanf("%lld%lld%lld",&p2.x,&p2.y,&p2.z); init(); if (p2.x <= p1.x && p2.y <= p1.y && p2.z <= p1.z) swap(p1, p2); int ans1 = dp(st,en); int ans2 = dp(st,p1) * dp(p1,en) % mod; int ans3 = dp(st,p2) * dp(p2,en) % mod; int ans4 = dp(st,p1) * dp(p1,p2) % mod * dp(p2,en) % mod; int ans = (((ans1 + ans4) % mod - (ans2 + ans3) % mod) % mod + mod) % mod; printf("%lld",ans); return 0;}