奇技淫巧
0.如果不是秒切的話,解決問題的一般思路:
1.退而求其次:如果數據小、沒有某個限制怎么辦?(可以通過暴力分得到提示)
2.找到矛盾問題的根源,就是算法升級的關鍵,如果有了這個限制,怎么處理?
3.集中處理這個矛盾
本篇博文記錄一些機智操作,簡潔方法,奇技淫巧。
1.快讀:
void read(int &x)
{
char ch;bool flag=false;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(x=num;isdigit(ch=getchar());x=x*10+num);
(flag)&&(x=-x);
}
注意:&x和&&前后兩個的順序
支持:整形讀入。包括負數
2.取模優化:
inline int mod(int x)
{
return x-p*(x/p);
}
3.線段樹define操作
struct node{
int l,r;
int add,ch;
int mx,hx;
int had,hch;//一段時間內最值
#define ad(x) t[x].add
#define ch(x) t[x].ch
#define mx(x) t[x].mx
#define hx(x) t[x].hx
#define ha(x) t[x].had
#define hc(x) t[x].hch
#define l(x) t[x].l
#define r(x) t[x].r
}t[4*N];
摘自:cpu監控
4.define int long long
適用于無腦調試
但是空間極其緊張,除非沒時間改,否則不要用這個。
5.num[++num[0]]=x;
用num[0]計數,有多個數組的時候,不會弄混cnt名稱
6.fix
當數組中可能遇見負數的時候,可以考慮采用修正值fix,將取值的區域平移。
如:NOIP2016 D1T2 天天愛跑步, 處理d[si]-2*d[lca]<0 , w[x]-d[x]<0時,取值范圍在:[-n,n]
直接再加上n,平移到[0,n]就可以直接處理了。
7.區間中位數
二分一個中位數的值ans,大于ans的賦為1,小于-1(等于再說)
找區間內有無連續子段和大于0,判斷ans+還是ans-
8.字符讀入避免空格
ztb法:char s[] scanf("%s",s+1) 利用字符串自動剔除前面空格,并且讀到下一個空格位置。
chen_zhe法:char c scanf("%s",&c) 敢情這也可以。。。。
9.Hungary匈牙利算法每次memset的小優化(默認左部點出發dfs)
①一般情況下,我們是每次找到一個右部點,vis[y]=1,之后每次memset一下vis
②但是,有的時候,右部點很多,memset一下,nleft*nright 就掛了。
而如果左部點很少,可以嘗試標記左部點的vis,
因為,我們之所以標記vis,是因為,不能在dfs(pre[y])時,從pre[y]里不斷返回pre[y]自己(考慮代碼模擬一下)。
如果標記左部點,那么,回到自己時,會因為已經vis,直接返回0
換句話說,①方案是不讓你找到y,而②方案,是回到自己再打斷。本質一樣。
③我們也可以用一個棧sta[],收集右部點vis的點,用彈棧并歸零處理vis。
為什么復雜度在②條件下沒問題??
其實,最壞和②復雜度是一樣的,一般情況下更優。
因為,我們每找到一個!vis[y],就要做出選擇,要么!pre[y] 返回true,要么dfs(pre[y])
返回就返回了,打斷搜索。如果dfs下去,那么必然要到了一個新的左部點。
所以,每vis一個,最多多訪問一個左部點。所以,sta[]中元素的數量和左部點數量是一致的。
所以,最壞情況下,和②是一樣的。
(2018.9.8 update:其實根本可以不用每次嘗試清空什么vis!!!
我們可以采用時間戳的東西,vis[i]變成int數組,表示最后一次訪問這個右部點的左部點的編號!
如果這一次的沒有訪問i,即vis[i]!=id 那么就令vis[i]=id,就不用每次清空了!
int vis[N],pre[N];
int id;
bool dfs(int x){
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(vis[y]!=id){
vis[y]=id;
if(!pre[y]||dfs(pre[y])){
pre[y]=x;
return true;
}
}
}
return false;
}
for(int i=1;i<=mx;i++){
id++;
if(dfs(i)) ans++;
}
)
10.多個點的lca
dfs序最小點和dfs序最大點取lca即可。
因為dfs總是先搜完一個子樹,又因為兩個點遍歷時間相差最遠。畫圖理解一下。
類似:JZ模擬賽 8.9B組T3
11.區間維護最小值,支持刪除,插入
1.multiset
2.差的最小值呢?兩個set,第二個set維護數值set相鄰兩個的差值。插入刪除用前驅后繼處理。
詳見:JZ模擬賽 8.9A組T3
12.雙哈希
有的時候,出題人要卡你。一個哈希值可能出現的差錯是:
本來不同的兩個串,通過取模運算,就可能相同了。
所以,我們通過兩個串,如果兩個有一個哈希值不同,就認為串不同了。
否則,認為串相同。
注意,不可能出現相同的串哈希值不同的情況,因為算法一樣,得到的哈希值一定相同。
13.曼哈頓距離轉切比雪夫距離
(x,y) -> (x-y,x+y)重新設置點的坐標。
(x1,y1),(x2,y2) -> (x1+y1,x1-y1), (x2+y2,x2-y2)
分類討論可以證明:|x1-x2|+|y1-y2|=max(|x1-x2|,|y1-y2|)
這樣,就可以把一個加法變成最值問題。
可以處理曼哈頓距離最小生成樹。
用兩個set一個維護沒有加入當前點集中的點,橫坐標,縱坐標。
用四個數維護當前點集最小x,最大x,最小y,最大y
每次從set找四個點和最小x,最大x,最小y,最大y找最小距離加進去。
因為,只要從x找一個max,y找一個max,再取max就可以。
14.變進制數存儲狀態,狀壓dp
變進制法不錯的講解:NKOJ1633 神仙開山【變進制數狀壓DP】
還有一個應用:JZ模擬賽 8.18 A組T3
就是,根據每個數的出現次數不同,每個數位置的進制都不同。
這樣,可以達到壓縮狀態數到最小的結果。
15.一點反演技巧
(i,j)=1 時,等價于:∑d d|i&&d|j u(d) =1
(i,j)!=1時,等價于:∑d d|i&&d|j u(d) =0
即通過判斷i,j所有公約數的u的和是否為1,判斷i,j是否互質。
證明:
i,j互質的時候,公約數只有1,u(1)=1
i,j不互質的時候,公約數就是i,j最大公約數的約數。
設gcd(i,j)=p1^q1*p2^q2*...pk^qk
那么,如果取兩個以上的pi,u就是0,不影響答案。
所以,就是取p1、。。。pk一個或不取的方案數要考慮
取零個:C(k,0)個1
取一個 :C(k,1)個-1
取兩個 :C(k,2)個1
。。。
所以,最后的答案是:C(k,0)-C(k,1)+C(k,2)-C(k,3)+...+C(k,k)
根據組合數的性質,不論k是奇數還是偶數,答案都是0
證畢。
16數學期望dp小結
總結:
數學期望以難以證明的性質,花樣繁出的特點聞名于OI界。
其難以下手的恐懼,令不少蒟蒻心有余而力不足。
還是抓住關鍵的線性遞推式,和期望定義 。 慢慢仔細分析。
對于期望狀態的設計:
1.多個終點一個起點,就f[i]表示,從i到終點的期望步數,f[s]即為答案
2.多個起點一個終點,就f[i]表示,從起點到i的期望步數,f[t]即為答案
3.與終點無關的樹形期望dp,通常往子樹對i,父親對i影響考慮,和一般的樹形dp類似。
17.曼哈頓距離和切比雪夫距離轉化
18.gets()讀入整行字符串,然后再 處理。
或者getchar()讀入空格換行,scanf("%s")讀入單詞等
19.擴展歐拉定理:
$b^cspace mod p=b^{cspace mod p+phi(p)}mod p (c>phi(p))$
p,b可以不互質
20.計算三角形面積。
坐標:(x1,y1),(x2,y2),(x3,y3)
把(x3,y3)移動到原點。得到新的(x1,y1),(x2,y2)
S=|x1y2-x2y1|/2
可以避免精度誤差
21.二分圖帶權最大匹配
如果左部點只連接兩個邊,可以認為是兩個右部點之間連接了一條邊。
形成了一個無向圖(可能不連通),相當于給邊定向。
22.無理數的高精度處理
(適用于($(a+b imessqrt{x})^k$):
1.保留“a+b*根號”的形式:構造有序數對:(a,b),即可定義乘法。
2.精度問題:令
無理項消除,N是整數。如果$(a-b imessqrt{x})^k)$小于1的話,k比較大,就逼近0,k比較小,double可以承受。
例題:圓——高精無理數的處理
23.快速質因數分解:
如果要多次詢問在1e7范圍內的質因數分解,可以利用線性篩預處理每個數的最小質因子,然后不斷除以mindiv,即可在低于logn時間下完成分解。
而且質因子已經自動排好序了。
例題:CF757E Bash Plays with Functions
24.十進制快速冪(專治高精快速冪)
和二進制快速冪原理一樣。
y要循環$log_{10} y$次。
每次x要左移一位,相當于一個快速冪$log_2 {10} $次。
所以,總的復雜度其實也是:$log_{10} y imes log_2 {10} = log_{2} y$
但是,當y是一個高精度的時候,如果用十進制儲存,每次/2是O(長度)的,會TLE。
然而十進制快速冪直接舍棄最后一位,可以O(1)進行移位。
(由于復雜度在低精度下是一樣的,所以除了高精快速冪,并沒有什么卵用)
25.爆棧的處理方法
26.HASH判斷循環節
對于O(1)判斷si是sj的循環節:
hash(sj)*p^leni+hash(si)=hash(si)*p^lenj+hash(sj)
證明方法類似kmp的循環節證明。
27.C(n,m)奇偶性
結論:C(n,m)為奇數,當且僅當n&m=m,即m是n的子集。
證明:
根據LUCAS定理,C(n,m)=1 mod 2 意味著,n,m二進制上的每一位,都有n是1,m是0,或者n是1,m是1.或者n是0,m是0.
如果n是0,m是1的話,那么C(0,1)=0,那么就是0了。
28.所有取值的前k最值問題
考慮每個可能的決策點是否有貢獻。
1.起初把每個點的最優解找出來,帶著點的id,放進堆里面。
2.從堆里取出最優解。
3.把和這個id有關的次優解找出來,放進堆里。
重復2、3
適用于一些有固定可能存在的決策點。并且能夠快速找到和這個點有關的第k優的值。
例題:BZOJ3689: 異或之
[NOI2010]超級鋼琴
29.轉化研究對象:
枚舉每個決策點,計算決策點包含的物品的貢獻——>枚舉物品,計算對能產生影響的決策點的貢獻。
后者,用于計數,就是分開考慮每個元素。對于最值決策,計算之后直接對決策點取最值。
30.-Wall -W
工具——編譯選項——編輯器——編輯時加入以下命令:-W -Wall
小錯可以警告
#pragma GCC ("-W1,--stack=128000000)手動開棧。
31.對于一些序列兩次取值的問題(兩次取值位置不相交)
可以正著dp,f[i],前i個位置選擇一個
再反著dp,g[i]i~n位置選擇一個。
然后枚舉分界點,即可。
ans=max(ans,f[i]+g[i])
32.雪球數:
一個數的所有非空前綴組成的數都是素數。
例如:313 :3,31,313都是素數。
然而,雪球數有最大的數并不是無限的:73939133
33.NOI Linux編譯
1.進入Linux虛擬機
2.桌面新建文件。打開。
3.復制代碼進去。
4.重命名為my.cpp(后綴為cpp)
5.Ctrl+Alt+T 進入終端
6.輸入cd Desktop進入桌面界面(忘了文件名,輸入ls Desktop 顯示桌面的所有文件)
7.輸入g++ my.cpp -o my.exe
(有需要的話,后面加上-std=c++11 -Wall -O2)
8.如果回車之后,沒有其他信息(沒有error),那么編譯成功。
9.接下來輸入./my.exe (注意之間沒有空格)表示進入這個exe
然后類似windows下的cmd,直接輸入數據即可。
(按動↑可以尋找上面進行過的命令)
34.半平面交atan2
排序時,求向量旋轉角的時候,用atan2(y,x)比較好
既可以保證精度,而且范圍是在(-Pi,Pi]的。可以表示所有的旋轉角。而且,如果atan2(t,0)的話(t>0),會返回Pi/2,
并不會因為tan(Pi/2)不存在而RE等。
35.int a=floor((double)b+0.5)
適用于:FFT等需要四舍五入以及處理-0.0的情況。
(注意,負數的時候,強轉int是向中取整,floor是向下取整,有所不同。看情況決定)
而且,FFT最后輸出的時候,
用
for(reg i=0;i<=m;++i){
b[i].x/=n;
int pp=b[i].x+0.5;
printf("%d ",pp);
}
代替
for(reg i=0;i<=m;++i){
b[i].x/=n;
printf("%.0lf ",fabs(b[i].x));
}
可以快一倍!
還有一個東西叫round(x)
(int)y=round((double)x)
四舍五入函數。
36.FFT三次變兩次:
就是把P(x)=F(x)+G(x)i
P(x)^2虛數部分/2即可。節省一次DFT
傅里葉變換(FFT)學習筆記
37.O(1)求LCA
用歐拉遍歷序遍歷樹,然后RMQ記錄深度。LCA就是之間深度最淺的,O(1)查詢
詳見:LCA 最近公共祖先
38.堆優化dij的一個小剪枝
1.不必每次把所有的出邊更新到的都加入到堆里
類似spfa,如果dis[y]>dis[x]+e[i].val那么dis[y]=dis[x]+e[i].val,q.push(po(y,dis[y]))
這樣常數會小一些。多次dij,優化就明顯了。
2.還可以用num記錄已經確定的點的個數,如果num=0直接return
適當減少了常數(但是如果要多次dij的話,別忘了清空堆)
39.關于平均值的二分
上面有中位數的二分,平均值也可以二分
每個數減去平均值,如果區間和大于等于0,那么可以
fzyzojP1635 -- 平均值
40.一個前綴問題
一個有向圖,S到T經過至少k條邊的最短路,這個要T自己和自己連個自環,表示保留下來
2.11考試
41.關心應該關心的
縮減冗余狀態:各種DP中用到
合并類似轉移及狀態:數組平移,維護分段函數,20190211 模擬訓練 A. 大貓咪
修改之間忽視并不會變的:虛樹、動態DP
42.異或下線性方程組的自由元個數:
先變成n*(n+1)的矩陣
然后高斯消元,如果某一個id找不到,那么一定是自由元了,計數器++
注意,每次找i和消除必須在全局位置,并且用一個vis標記表示是否還能動(因為有時候存在自由元X_i,但是第i行其實還能用)
最后削成的上三角矩陣,除了無解情況,剩下的一定有唯一解
(一般的線性方程組也是這樣求,只不過xor下的自由元用的比較多)
代碼見:bzoj4671: 異或圖——斯特林反演
43.一個通用技巧是:
一個通用技巧是:
找到兩個數組f,g
f范圍寬松好統計,g范圍嚴格難統計但是和答案有直接關系,
這樣,只要得到f和g的關系,就可以找到答案!
經常是可以得到f由g的表達式,然后斯特林反演或者二項式反演得到g的求法
也可以用多項式科技
數論函數的反演也可以這么做。
luoguP4841城市規劃
bzoj4671: 異或圖——斯特林反演
已經沒有什么好害怕的了——二項式反演+經典套路
44.枚舉LCP
一些對于字典序大小,或者二進制下有大小比較限制的按位操作的題
枚舉LCP可以有效體現比較大小的“實質”
一般外層枚舉LCP,內層用DP處理方案。
來自多校的一個題——數位DP+卡位
45.O(n^2)插值
[學習筆記]拉格朗日插值
46.一種生成函數還原序列的方法
有時候,兩個生成函數卷積起來,要展開以后求x^k的系數,但是k很大。
①如果一個生成函數的次方的話,就是組合意義小球放盒子了,O(1)有公式,LUCAS求組合數
②否則考慮能不能把其中一個的生成函數變成有限項,使得項數在O(n)以內
例如:自古槍兵幸運E
47.給DAG轉移重新定向
可以做到O(sqrt(m))的出邊個數。支持暴力枚舉Day5
48.樹剖維護子樹&&連通塊信息
PK LCT
基于動態DP的思想,記錄往輕兒子的貢獻。重兒子現場計算。Day5
便于打標記。便于寫。(畢竟是靜態樹)
49.取Ln
1.有標號圖計數,F表示所有情況,G表示連通情況。則:G=Ln(F)
反之,F=exp(G)
證明可以參考:
luoguP4841城市規劃
COGS 2353 2355 2356 2358 有標號的DAG計數
2.把乘法變成加法,
①多項式降次
付公主的背包
luoguP4705玩游戲
②把答案大的變小,仍然滿足單調性,所以取最小值方案還是不變的。
[BJOI2019]奧術神杖取Ln直接就變成0/1分數規劃了
3.碰見Ln(f)很麻煩?
求導就把f搞出來了。
4.求積分時候,處理分式分母。
待定系數法。1/(ax+b)->Ln(|ax+b|)直接加上絕對值,求導回去還是成立的
50.分母沒有逆元
前提是求一個分子分母都是連乘積的分式
可以考慮表示成:x*p^y的pair形式,最后上下把p的次數相減(類似擴展Lucas)
具體操作:(a,b)*(c,d)=(a*c,b+d)
然后檢查(a,b):如果a%mod==0,(a,b)->(a/mod,b+1),否則(a,b)->(a%mod,b)
顯然這樣取模,mod的次數不會減少。
51.DINIC從T開始BFS常數更小
顯然DINIC的瓶頸在于增廣。
所以一些必然不會到達T的點就不用訪問了。
所以從T開始搜分層圖。
雖然一些點S不能到達,但是浪費的只是BFS的常數,反之浪費的就是增廣時候的常數了。
52.
53.真·奇技淫巧
O(p)預處理,O(1)任意底數,任意質數的快速冪
$a^b=G^{log_G{a^b}}=G^{b imes log_Ga}$
然后$log_Ga$對于所有的$a in [0,p-1]$可以預處理
對于$G^{x}$可以直接預處理
54.樹上點集到點集的最大值
邊權為正數
一個常用的套路:一個樹上點集的直徑,可以直接合并兩個集合得到。四個端點C(4,2)計算即可。
貓的某個隨機連通塊直徑題、安師大的某個O(n^4)預處理二分+前綴和查詢、51nod1766
55.編號區間點集,信息可以合并?
一個常用套路:編號區間?線段樹!線段樹區間維護這個區間的信息即可
MaxMex51nod1766
56.不能立刻決定球的顏色?
每個球的顏色個數固定,不能放一個球就染色
可以先認為是白色,然后某一個時刻欽定k個白色成為某一個顏色。只要知道之前有多少個白色就可以了。
UOJ#449. 【集訓隊作業2018】喂鴿子
57.IDFT的trick
如果遇到矩陣乘法套多項式,最后就求一個多項式的系數的話,可以考慮用IDFT的trick降低常數
具體是:直接把轉移矩陣的x換成$w_N^i$,得到$Ans(w_N^i)$最后再插值。
一個乘法的常數,比卷積的常數小太多了。
UOJ#424. 【集訓隊作業2018】count
58.__builtin
59.樹上后綴排序
有的時候很有用。比EXSAM好寫好調。
60.Cnt(S,T)
表示起點在S,終點在T集合的邊的條數
不能預處理,O(n)也有點慢。
可以bitset!處理S集合的出邊包括哪些編號,T集合的入邊包括哪些編號,求&
61.bitset與字符串匹配?
[國家集訓隊]JZPSTR
字符集很小的時候,可以對每個字符集維護出現位置。
各種插入刪除可以手寫bitset進行。
xi=x%Mi+1Mi
2.
總結
- 上一篇: 数据结构-二叉排序树
- 下一篇: 对SVC和SVR的理解