6.9~6.15
容斥原理
集合 \(S\) 的子集 \(A_1\) 有性质 \(p_1\) ,\(A_2\) 有性质 \(P_2\) , \(\dots\) , \(A_n\) 有性质 \(P_n\) 。
那么,集合 \(S\) 中具有性质 \(P_1,P_2,\dots,P_n\) 的集合个数为
那么不具有性质 \(P_1,P_2,\dots,P_n\) 的集合个数呢?很明显就是
来个例子加深理解
问题:在100人中,60人喜欢咖啡,40人喜欢茶,20人两者都喜欢。求至少喜欢一种饮料的人数?一种饮料都不喜欢的人数?
解答:
设 \(A\) 为喜欢咖啡的集合,\(B\) 为喜欢茶的集合。
容斥原理给出:
因此,80人喜欢至少一种饮料。
因此,20人一种饮料都不喜欢
以下是一道例题(2024CCPC新疆E题):
题目
Bob现在具有一个长为 \(n\) 的序列 \(a_1,a_2,a_3,\cdots,a_n\)。
一共有 \(q\) 次询问,每次给定一个数 \(x\)。
询问在 \([1,x]\) 中有多少个 正整数 满足它不是任何 \(a_i\) 的倍数。
输出满足这样要求的正整数数量。
输入
第一行一个正整数 \(n\),表示数组的长度。
第二行 \(n\) 个正整数,第 \(i\) 个正整数表示 \(a_i\)。
第三行一个正整数 \(q\),表示共有\(q\)个询问。
接下来的 \(q\) 行中,第 \(i\) 行一个正整数 \(x\) 表示当次询问。
其中保证\(n \leq 10,q \leq 10^4, a_i, x \leq10^9\).
子集共有 \(2^n\) 个,用二进制法把每个子集给枚举出来,奇数个子集相加,偶数个子集减去,本题属于"不是、不具有"的情况,因此满足“不具有”的情况就是总集减去子集数
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
int n,q,a[15];
inline ull lcm(int x,int y){
return x/__gcd(x,y)*y;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
cin >> q;
while(q--){
ull x, ans = 0;
cin >> x;
for(int i = 1; i < (1 << n); i++){
ull sum = 0, glcm = 1;
for(int j=0;j<n;j++){
if((i>>j) & 1){
sum++;
glcm = lcm(glcm,a[j+1]);
}
}
if(sum & 1) ans += x/glcm;
else ans -= x/glcm;
}
cout << x - ans << '\n';
}
return 0;
}
CCPC新疆重现赛
B
题目描述
在一款即时通讯软件里,有一种功能叫做群组。
现在,群组中有\(n\)个成员。每个成员都是一个模仿者,会模仿指定对象的头像。允许模仿自己。
模仿的具体含义是:每经过一个时刻,若成员A模仿对象为成员B,那么成员A就会更换为成员B的头像。
现在给定所有成员的模仿对象。初始时,每个成员都有本质不同的原始头像。
你的任务是计算经过\(10^{100}\)个时刻之后,群组里还存在几种本质不同的头像。
输入描述
第一行一个整数\(n\)。表示群组里一共有几个成员。编号\(1 \sim n\)。
接下来一行\(n\)个整数\(a_i\),描述每个成员\(i\)的模仿对象为\(a_i\)。
其中,\(1 \le n \le 10^5, 1 \le a_i \le n\).
题目的 $ 10^{100} $ 意味着正无穷,也就是经过一定次数后,"本质"的种类数会固定下来,不再发生变动。
造了几个样例,并且在纸上画出图论关系图后,就有思路了。
思路是用拓扑排序找环,只要是环上(包括自环)的点,就不会发生退化;而所有的不在环上的点,都会发生退化。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n,a[maxn],s[maxn];
void init(){
for(int i=1;i<=n;i++) s[i] = i;
}
int finds(int x){
if(x!=s[x]) s[x] = finds(s[x]);
return s[x];
}
void unions(int x,int y){
x = finds(x);
y = finds(y);
if(x != y) s[x] = s[y];
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
init();
//for(int i=1;i<=n;i++) cout << s[i] << ' ';
//cout << '\n';
for(int i=1;i<=n;i++){
cin >> a[i];
unions(i,a[i]);
}
for(int i=1;i<=n;i++) cout << s[i] << ' ';
return 0;
}
C
题目
现在给定你一个字符串。小Y有一个幸运字符,他认为含有这个幸运字符的字符串是好的。
现在给你一个简单的问题。小Y会有\(q\)次询问,每次询问会有必须包含的位置要求,你的任务是在包含这个位置的前提下,选出一段最短的连续子串,使得这个子串中含有小Y所指定的幸运字符。
输入
第一行两个整数\(n, q (1 \le n, q \le 10^5)\),表示字符串长度为\(n\),一共有\(q\)次询问。另有一个字符\(ch\),表示连续子串中需要包含的幸运字符\(ch\)。该字符一定是一个小写英文字母。
第二行一个长度为\(n\)的字符串。字符串中的位置从1开始计数。
接下来\(q\)行,每行一个正整数\(pos\),保证\(1 \le pos \le n\),你的任务是对每次询问的\(pos\)给出最短的连续子串的长度,使得它含有小Y所指定的幸运字符。若不存在满足要求的选取方法输出-1。
先预处理一遍,将每个s[i]初始化为\(-1\),然后依次处理每个s[i]:向左找最近的幸运字符,并记录路径长度;向右找最近的幸运字符,并记录路径长度;取左右长度的最小值存进a[i]里;查询时直接查表即可
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n,q,a[maxn];
char ch;
string s;
void init(){
for(int i=1;i<=n;i++) a[i] = -1;
for(int i=1;i<=n;i++){
int l = i, r = i, cnt = 1;
bool flag = 0;
while(1){
if(l == 0) break;
if(s[l] == ch) {
flag = 1;
break;
}
l--,cnt++;
}
//cout << "cnt: " << cnt << '\n';
if(flag) a[i] = cnt;
cnt = 1;
flag = 0;
while(1){
if(r == n+1) break;
if(s[r] == ch){
flag = 1;
break;
}
cnt++;
r++;
}
if(flag && a[i]==-1) a[i] = cnt;
else if(flag) a[i] = min(a[i],cnt);
}
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> q >> ch;
cin >> s; s = ' ' + s;
init();
for(int i=1;i<=q;i++){
int t; cin >> t;
cout << a[t] << '\n';
}
//for(int i=1;i<=n;i++) cout << a[i] << ' ';
return 0;
}
D
题目
Alice现在通过随机数生成了一个长度为\(n\)的序列。
定义一个序列是好的,当且仅当:
- 对于这个序列,任意截取序列中前若干个数字,其和均非负。
例如:序列[1, 2, -3]是好的,序列[5, -3, -3, 9]是不好的。这是因为对于5 - 3 - 3 = -1 < 0。
现在Alice希望把这个序列划分成若干个连续的子序列,使得划分后的每个子序列都是好的。有些情况下不止一种划分方案,Alice希望你给出最多的序列划分方案。
输入
第一行一个正整数\(n\)表示序列的长度\(n\)。其中保证\(1 \le n \le 2 * 10^5\)。
接下来一行\(n\)个整数\(a_1, a_2, ..., a_n\),表示这个序列。其中保证\(-10^4 \le a_i \le 10^4\)。
主要是贪心,
很明显的是,负数不能在开头,因为这样会使得开头的一段直接为负;结合贪心,要让尽可能多的非负数独立成段。
所以思路就是,从后向前走一遍序列,如果遇到非负数,就独立成段,段数++;如果遇到负数,就出现了一个“含负数段”,从这个负数开始,一直像前走,直到sum为非负时停止。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int n,a[maxn],cnt = 0;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=n;i>=1;i--) cin >> a[i];
for(int i=1;i<=n;i++){
if(a[i] >= 0) cnt++;
else{
int sum = a[i];
while(sum < 0){
if(++i > n) break;
sum += a[i];
}
if(sum >= 0) cnt++;
}
}
cout << cnt << '\n';
return 0;
}
F
题目
Cindy最近沉迷某种六个字的游戏。
这个游戏里有一个场景是选择房间,每个房间会有不同的事件。最后一个房间会面对游戏Boss。
Cindy设计了一个比原版游戏简单一些的场景:
现在有一行\(n\)个房间。每个房间里有\(a_i\)份奖励。一些房间是普通房间,获取该房间的奖励对其他房间无任何影响。另一些房间是特殊房间,获取该房间内的奖励会导致编号满足\(i - a_i \le x \le i + a_i\)的\(x\)号房间内的奖励清零。当然,对于这两类房间,你都可以不获取奖励,此时对其他房间无任何影响。
你的任务是计算最多能在按顺序从\(1 \sim n\)走完这\(n\)个房间后可以获取的最多奖励数量。
输入
第一行输入一个整数 \(n(1 \leq n \leq 5 \times 10^5)\) 表示一共有几个房间。
第二行输入 \(n\) 个整数 \(a_i(1 \leq a_i \leq 10^9)\) 表示每个房间内的奖励数量。
第三行输入 \(n\) 个整数 \(b_i(0 \leq b_i \leq 1)\) 表示每个房间属性, 1 表示普通房间, 0 表示特殊房间。
六字游戏?不会是崩坏星穹铁道吧(bushi)
戴南米克破管明,简称DP
定义 \(dp[i]\) 为到第 \(i\) 个房间可以获得的最大奖励,从后往前dp
走到第 \(i\) 个房间时,先 \(dp[i] = dp[i+1]\),获取前一节点的最优状态;状态转移方程:
对于普通房间:
对于特殊房间:
之所以取 \(min\) ,是担心 \(i+a[i]+1\) 会越界
全程用ans记录最大值即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5e5+5;
int n,a[maxn],dp[maxn],ans = 0;
bool b[maxn];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) cin >> b[i];
for(int i=n;i>=1;i--){
dp[i] = dp[i+1];
if(b[i]) dp[i] = dp[i+1] + a[i];
else{
int edge = min(n+1,i+a[i]+1);
dp[i] = max(dp[i],dp[edge]+a[i]);
}
ans = max(ans,dp[i]);
}
cout << ans << '\n';
return 0;
}
J
题目
在平面上有若干个点,另有若干个圆。
给出所有点和所有圆,对于每个圆,问有多少点位于该圆内。
注:在圆形边缘上的点也算在内。
输入
第一行两个整数\(n, m (1 \le n, m \le 2 * 10^5)\),表示一共有\(n\)个圆,\(m\)个点。
接下来\(n\)行,每行三个正整数\(O_x, O_y, r (1 \le O_x, O_y, r \le 20)\),表示一个圆心在\((O_x, O_y)\)处,半径为\(r\)的圆。
接下来\(m\)行,每行两个正整数\(x, y (1 \le x, y \le 20)\)表示一个点。
第一思路就是暴力
考虑最坏的情况,点的坐标都在20以内,枚举每个点,有 \(20×20=400\) 个坐标点, \(2×10^5\) 个圆,那么就一共要枚举 \(400×2×10^5=8×10^7\) 次,接近 \(10^8\) 了,虽然不太保险,但是我们还是很信任牛客的数据和机器不会针对我们的ovo
提交后成功过了
#include <bits/stdc++.h>
using namespace std;
const int maxn =2e5+5;
class circle{
public:
int x,y,r;
}cir[maxn];
int n,m,room[25][25];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for(int i=1;i<=n;i++){
int x,y,r; cin >> x >> y >> r;
cir[i] = {x,y,r};
}
for(int i=1;i<=m;i++){
int x,y; cin >> x >> y;
room[x][y]++;
}
for(int i=1;i<=n;i++){
int cnt = 0;
for(int j=1;j<=20;j++){
for(int k=1;k<=20;k++){
if(room[j][k] && (cir[i].x-j)*(cir[i].x-j) + (cir[i].y-k)*(cir[i].y-k) <= cir[i].r*cir[i].r){
cnt += room[j][k];
}
}
}
cout << cnt << '\n';
}
return 0;
}
E
题目
Bob现在具有一个长为 \(n\) 的序列 \(a_1,a_2,a_3,\cdots,a_n\)。
一共有 \(q\) 次询问,每次给定一个数 \(x\)。
询问在 \([1,x]\) 中有多少个 正整数 满足它不是任何 \(a_i\) 的倍数。
输出满足这样要求的正整数数量。
输入
第一行一个正整数 \(n\),表示数组的长度。
第二行 \(n\) 个正整数,第 \(i\) 个正整数表示 \(a_i\)。
第三行一个正整数 \(q\),表示共有\(q\)个询问。
接下来的 \(q\) 行中,第 \(i\) 行一个正整数 \(x\) 表示当次询问。
其中保证\(n \leq 10,q \leq 10^4, a_i, x \leq10^9\).
容斥原理,赛时还不会,赛后学了就把这题开出来了
子集共有 \(2^n\) 个,用二进制法把每个子集给枚举出来,奇数个子集相加,偶数个子集减去,本题属于"不是、不具有"的情况,因此满足“不具有”的情况就是总集减去子集数
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
int n,q,a[15];
inline ull lcm(int x,int y){
return x/__gcd(x,y)*y;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
cin >> q;
while(q--){
ull x, ans = 0;
cin >> x;
for(int i = 1; i < (1 << n); i++){
ull sum = 0, glcm = 1;
for(int j=0;j<n;j++){
if((i>>j) & 1){
sum++;
glcm = lcm(glcm,a[j+1]);
}
}
if(sum & 1) ans += x/glcm;
else ans -= x/glcm;
}
cout << x - ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号