競技プログラム練習会
2020 Normal
第十二回
包徐定理とか
担当:zeke
自己紹介
自己紹介
- KMCid:zeke(本名:岡島和希)
- 工学部工業化学科2回
- AtCoder水(1569)
- 自堕落生活を送る、学部の勉強がつらすぎるんじゃ
- 青になりたい
- 数学書はもうちょっと人類に分かるように書いてほしい
- 強化学習とか勉強中、興味ある方はぜひ!
- Pythonとかも勉強してる
- いつになったら、サークル対面再開できるんですかね…新入生の皆さん困ったことがあったら上回生をどんどん頼ってください
- #zeke-memo

さて皆さん
伺いたいことが二点
一点目:講座やってみませんか?
二点目:マラソン競技の練習会
- 先週AtCoderでヒューリスティックコンが開催された
- 普通のコンテストとは異なり「最適解」「厳密解」を求めるのではなく、「できるだけよい解」を求める問題が出題
- 2~72時間で1~3問解くのが多い
- 全探索、ビームサーチ、山登り法、焼きなまし法などのアルゴリズムを学ぶ
- 「今後AtCoderで、これまでと異なるレーティングが実装される予定である」らしい??
- ある程度有志がいればやりたい気持ち
- やるとしたら、わいわい実装の手伝いをする方針
コンテスト
コンテスト
- A~D 包徐!!
- E bit全探索の練習
- F 二分探索(前回ABC-Cで出たやつ)の練習
本日は
包徐定理
ABC-172-Eで出たので自分の復習も兼ねてます
包徐定理とは?
|A_1 \cup A_2 \cup …A_n| \\
=\Sigma_{k=1}^n(-1)^{k-1}(k個のかつの総和)

まずは小さいもので
|A_1 \cup A_2|を考える
|A_1 \cup A_2 \cup A_3|を考える
どんな問題に使えるのか?
集合論で
\overline{A_1}\cap\overline{A_2}…\cap\overline{A_n}
が求めづらいとき!
ドモルガンの法則
\overline{A_1\cup A_2…\cup A_N}=\overline{A_1}\cap\overline{A_2}…\cap\overline{A_n}
余事象的な考え方
具体例
正の整数 N について、1 から N までの\\自然数のうちN と互いに素なものの\\個数を求めよ。\\
1 \leqq N \leqq 10^9
まずは愚直解
- 一つずつ素因数分解していく
O(NlogN)
こう考えてみよう
Nの素因数を P_1,P_2…P_m とする \\
それらをすべて素因数として\\含まないのが目的の数
これは条件をすべて満たす必要があるということ!
{A_1}\cap{A_2}…\cap{A_n}
これはとても考えにくい!!
ドモルガンの法則より
Nの素因数を P_1,P_2…P_m とする \\
それらのうちどれかを含むのは\\目的の数ではない
これは条件をどれか満たすということ!
\overline{A_1}\cup\overline{A_2}…\cup\overline{A_n}
包徐定理が使える!
N=30の時
さあ、実装だ!
- まずはNを素因数分解し、素因数をすべて列挙
- この素因数の数がM個だったととしたら、(2^{M-1})個の状態を全部試す
- ↑bit全探索が使える
- 覚えていますか??
- 計算量分かりますか?
さあ、実装だ!(C++)
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <functional>
#include <cmath>
#define ll long long
using namespace std;
int main(){
ll n;
cin>>n;
//素因数分解をしていきます
ll test=n;//こうやって分けておかないとやばいです(実経験)
vector<ll> primes;//Nの素因数を格納する配列
for(ll prime=2;prime<sqrt(n);prime++){//√aしておかないとO(N)になる、注意!
if(test%prime==0){
primes.push_back(prime);//素因数を加えていく
while(test%prime==0){
test/=prime;
}
}
}
if(test!=1)primes.push_back(test);//忘れないように。
//ちなみに講座前にこの問題を解いてるときこれを忘れてWA
ll m=primes.size();
ll res=0;
//bit全探索を書けますか?私は書けます。
//bitの考え方としてm=3ならbit=000~111(2)まで回すイメージ
//2個目のforでどのbitがたっているかをcheckをするイメージ
//とにかく書いてみましょう!!!!!!!!
for(ll bit=1;bit<(1<<m);bit++){
ll k=1;
for(ll i=0;i<m;i++){//m桁まで見る
if(bit&(1<<i)){//i番目のbitがたっているかどうか
k*=primes[i];
}
}
ll num=__builtin_popcountll(bit);//何個bitがたっているかわかる、便利!
if(num%2==1)res+=n/k;//包徐定理の肝
else res-=n/k;//どっちを足すか引くかに注意です!
}
cout<<n-res<<endl;//余事象を取っていたことを忘れずに!!
}参考
競プロ練習会 第十二回
By kmc_procon2020
競プロ練習会 第十二回
- 175