OI記錄(持續更新
P2568 GCD
題意:
給定正整數 \(n\),求 \(1\le x,y\le n\) 且 \(\gcd(x,y)\) 為素數的數對 \((x,y)\) 有多少對(\(n\leq10^7\))
題解:
注意,可以不用莫比烏斯反演,單純的尤拉函式便可以解決,首先列出式子:
接著轉化式子,gcd裡的\(i,j\)同除\(p\),再換元一下:
然後我們可以觀察到\(gcd(i,j)=1\)代表的是互質,如何轉化到可以計算呢,我們假設\(j\)列舉到\(i\),這樣就可以用尤拉函式,然後討論一下改成這個就只是有序變為無序,然後會有2倍的差距,然後考慮\(i=j\)的時候會被統計兩次但是因為\(gcd(i,j)=1\) 只有\(i=j=1\)時有貢獻,在\(\sum_{p\in prime}\)這層迴圈下減個1,於是為:
然後發現這個\(\sum_{j=1}^{i}(gcd(i,j)=1)\)就是尤拉函式\(\varphi(x)\):
具體在程式中剛開始用尤拉篩預處理出來要用到的尤拉函式值和所有的質數,接著預處理出來一個字首和。
最後列舉所有的質數,求\(2sumphi[n/p]-1\)。
因為這題始數學題,AC code較為簡單:
#include<bits/stdc++.h>
#define int long long
#define N 10000009
using namespace std;
int n;
int phi[N],sumphi[N];
bool pri[N];
int prime[N],cnt;
int answ=0;
void euler()
{
phi[1]=1;
for(int i=2;i<=10000002;i++)
{
if(!pri[i]) prime[++cnt]=i,phi[i]=i-1;
for(int j=1;j<=cnt;j++)
{
if(i*prime[j]>10000002) break;
if(i%prime[j]==0){
pri[i*prime[j]]=1;
phi[i*prime[j]]=phi[i]*prime[j];break;
}
pri[i*prime[j]]=1;
phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
for(int i=1;i<=10000002;i++)
sumphi[i]=sumphi[i-1]+phi[i];
}
signed main()
{
scanf("%lld",&n);
euler();
//cout<<cnt;
for(int i=1;i<=cnt;i++)
{
if(prime[i]>n) break;
//cout<<sumphi[n/prime[i]]<<endl;
answ+=(2*sumphi[n/prime[i]]-1);
}
printf("%lld",answ);
}
P1640 [SCOI2010] 連續攻擊遊戲
題意:
給定\(n\)個物品,每個物品有兩個屬性值,每種物品只能選取其中一個屬性,並且每個物品只能用一次。現在要從屬性\(1\)開始,選取物品,問最多能選取多少個。
題解:
二分圖忘了,這道題是二分圖板子,是用來鍛鍊二分圖的,二分圖匹配的匈牙利演算法有一個很棒的性質:前面已經被匹配過的點不會失配。然後我們進行套路的建圖,每個物品連兩條邊到他的屬性值上,然後以屬性值作為右部點,依次跑二分圖匹配,直到失配便終止迴圈。
值得一提的是,這道題有些不適合網路流解法,二分答案跑網路流會超時,但是可以玄學先增廣序號在前的點,這樣也能得到正確答案。
因為用的是匈牙利演算法,所以程式碼簡單:
#include<bits/stdc++.h>
#define N 1145141
using namespace std;
int match[N],n;
vector<int>tu[N];
bool vis[N];
stack<int>V;
bool dfs(int now)
{
//cout<<now<<endl;
for(auto too:tu[now])
{
if(!vis[too])
{
vis[too]=1;
V.push(too);
if(!match[too]||dfs(match[too]))
{
match[too]=now;
return true;
}
}
}
return false;
}
int read()
{
int ans=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
{
ans=10*ans+c-'0';
c=getchar();
}return ans;
/*int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;*/
}
int main(){
n=read();
for(int i=1;i<=n;i++)
{
int x=read(),y=read();
tu[10000+i].push_back(x);
tu[10000+i].push_back(y);
tu[y].push_back(10000+i);
tu[x].push_back(10000+i);
}
for(int i=1;i<=10000;i++)
{
while(!V.empty()){
vis[V.top()]=0;
V.pop();
}
if(!dfs(i)){
printf("%d",i-1);
return 0;
}
}
printf("10000");
}
容器 set
和 multiset
set
和 multiset
代表有序集合和可重有序集合,包含在 set
庫中。
關於迭代器:
迭代器就是指向一個元素的指標,例如用 int
型別定義的 set
集合裡指向開頭元素的迭代器一般表示為:
set<int>::iterator it = s.begin();
it + 1
指向下一個元素,it - 1
指向上一個元素(如果這個集合中存在上一個或下一個元素的話)。
s.begin()
是指向s
開頭元素的迭代器。s.end()
是指向s
末尾元素的下一個記憶體位置的迭代器,所以如果要查末尾元素,一般是引用s.end() - 1
。
注意這裡 +1
和 -1
的時間複雜度都是 O(log n)
。
有何作用?除了遍歷集合元素外,set
和 multiset
都是有序集合(預設從小到大,可以用定義優先佇列的方法定義這個東西),所以這就相當於是一個平衡樹了,是不是很棒?平衡樹可以幹很多事呢。
如何把迭代器換為可以直接輸出的該元素的值?加個星號,即 *it
。
可以用減法檢視當前是第幾個。
關於操作:
定義如 int
型別用 set<int> s
或 multiset<int> s
。
具有以下基礎操作:
s.clear()
s.empty()
s.size()
s.insert(x)
代表插入元素x
,如果是重複的元素,set
不會插入,multiset
會插入。s.find(x)
表示查詢集合裡等於該元素的迭代器(返回第一個),不存在返回s.end()
。- 具有二分函式
s.lower_bound(x)
和s.upper_bound(x)
,代表查詢大於等於x
和大於x
的元素裡最小的一個,返回迭代器。 s.erase(x)
代表刪除所有等於x
的元素或者刪除x
迭代器所指的元素。特別注意的是,如果只刪去一個等於x
的元素,可以執行:
if((it=s.find(x))!=s.end()) s.erase(it);//代表先找指向x的迭代器,如果x存在,就刪去
s.count()
返回集合中等於x
的元素個數。
上面函式的複雜度除了 s.count()
和 s.erase()
是 O(k + log n)
,其餘都是 O(log n)
。
當然,平衡樹別寫set
,時間和正確性都容易炸(時間必炸)。
組合數預處理
先預處理\(C_{i}^{0}=1\)然後根據\(C_{i}^{j}=C_{i-1}^{j-1}+C_{i}^{j-1}\)遞推出所有組合數:
for(int i=0;i<=3000;i++) c[i][0]=0;
for(int i=1;i<=3000;i++)
for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i][j-1])%MOD;
10.23正睿模擬賽T3記錄
題意:
給定一張 \(n\)個點,\(m\)條邊的有向無環圖。
進行 \(q\)次詢問,每次詢問給定 \(s,t,l,r\)。問若只保留編號在$ [l,r]$ 之間的邊,\(s\) 能否到達 \(t\)。
題解:
妙妙題。
先給給詢問搞個編號,離線下來做。
首先我們要用\(bitset\)進行輔助記錄,首先記錄一個\(g[m]\),單個\(g[i]\)記錄的是編號為\(i\)的邊在所有詢問裡的存在情況(\(bitset\)存的)。
具體的計算我們搞一個掃描線,對於每一個詢問,在邊編號的\(tag\)上記東西,在\(l\)位置記上這個詢問編號,\(r+1\)位置也記錄這個詢問編號,然後就定義一個\(bitset\) \(Q\),掃一遍這個\(tag\),每回先給\(Q\)在所有目前位置上所有詢問編號在對應的位置取反,然後把這個\(Q\)賦值給對應的\(g[i]\)。
然後就是\(f[n]\)了,對於每一個結點,單個\(f[i]\)記錄的是每一個詢問的\(s_i\)在只能有當前詢問存在的邊時到這個點的連通性(同樣用\(bitset\)存)。
首先初始化給每個詢問的\(s_i\)的\(f[s_i]\)對應的關於本身\(s_i\)的存在性命名為1(因為自己肯定和自己連通嘛)。
然後考慮是DAG,我們可以轉移,有邊\((u,v)\),編號\(i\),可以使得\(f[u]\)貢獻給\(f[v]\)一個\(f[u] \and f[v]\)(貢獻的形式是或),在拓撲排序的過程中轉移,因為在所有通向自己的邊處理完後,這個點也處理完了。
但是這樣就是詢問比較多,空間容易炸,但是因為詢問和詢問間不打擾,所有我們可以\(B\)個詢問\(B\)個詢問的處理,這樣每個\(bitset\)的長度也就只有\(B\),空間也就不會炸,就處理完了。
最後就對於每個詢問輸出答案即可。
總結:
- 用\(bitset\)最佳化時間和運算
- 考慮在拓撲排序上進行\(dp\)轉移
- 用掃描線思想處理問題
- 空間炸瞭如果裡面各部分互不相干可以分塊計算
- 多個陣列聯合計算
\(over\)
拉格朗日插值
還沒記。
小紀錄
關於樹上面統計答案時,到了以\(u\)為根的子樹上,假如這個統計答案的方式需要是從\(u\)出發的兩個不同路徑的貢獻和的最大值,我們不僅可以把最大和次大的貢獻給和一塊,還可以不斷更新單個從u出發的最大貢獻過程中更新這個倆路徑合併在一起的最大值,不如現在單個路徑最大貢獻計算到了\(v\)之前的(不包括\(v\)),我們計算經過\(v\)的單個路徑和\(u\)目前單個路徑最大貢獻的和貢獻即可,然後繼續更新單個路徑最大貢獻。
線段樹分治記錄
更明白的應該說是線上段樹上離線查詢,適用於那種不支援刪除但是支援撤銷的東西可以用線段樹來輔助,具體來說比如一個東西,你要加進去一個東西,過一段時間再刪除這個東西,也就是說不是撤銷,不妨為這個時間序列開一個線段樹,每回區間操作都記錄這個東西存在的區間,這樣線上段樹上就可以保證這個東西建立和刪除可以在一塊兒進行。
然後有些題目不會給你明示,就需要各種觀察和轉化,比如求對於每個元素我們就刪除它求剩下的元素的答案,就可以把這些元素當成時間序列排一塊,對於不刪這個元素的兩個(或一個)區間進行標記操作,就可以轉化為多次撤銷和增加然後求答案。
當然一些題目中那些不要侷限在題目中的對哪個東西詢問來盲目地進行線段樹分治,可能不是對這玩意進行線段樹分治,要勤進行轉化。