好久好久沒有碰oi然而9月19日有一個CSP的認證考試,強行被拉回來搞oi orz orz orz.....
目前是准備刷csp真題+codeforces+atcoder+刷模板題同時搞0.0
分塊算法
分成一塊一塊處理,當處理區間問題,左右小區塊暴力處理,中間被分到的大區塊整塊整塊處理。復雜度 O(n根號n)
luoguP3372 【模板】線段樹 1
分塊處理即可(沒過的假代碼理解思想就好)
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
const int maxn = 200005;
ll n,m,fk,ks;
ll sm[maxn],lz[maxn];
ll a[maxn];
ll bl[maxn];
void pt(ll x) {
printf("%lld\n",x);
}
int main(){
scanf("%lld%lld",&n,&m);
fk = pow(n,0.5);
for(ll i=1;i<=n;i++) {
bl[i] = bl[i-1];
if(i%fk==1) bl[i]++;
scanf("%lld",&a[i]);
sm[bl[i]]+=(ll)a[i];
}
ll op,x,y;ll k;
while(m--) {
scanf("%lld%lld%lld",&op,&x,&y);
if(op==1) {
scanf("%lld",&k);
if(bl[x]!=bl[y]) {
for(int i=x;i<=bl[x]*fk;i++) a[i]+=k,sm[bl[x]]+=k;
for(int i=y;i>=(bl[y]-1)*fk+1;i--) a[i]+=k,sm[bl[y]]+=k;
for(int i=bl[x]+1;i<=bl[y]-1;i++) lz[i]+=k,sm[i]+=(ll)k*fk;
} else {
for(int i=x;i<=y;i++) a[i] += k;
sm[bl[x]] += (ll)(y-x+1)*k;
}
} else {
if(bl[x]==bl[y]) {
ll su = (ll)lz[bl[x]]*(y-x+1);
for(int i=x;i<=y;i++) {
su += a[i];
}
pt(su);
} else {
ll su = 0;
for(int i=x;i<=bl[x]*fk;i++) {
su += a[i] + lz[bl[x]];
}
for(int i=y;i>=(bl[y]-1)*fk+1l;i--) {
su += a[i] + lz[bl[y]];
}
for(int i=bl[x]+1;i<=bl[y]-1;i++) {
su += sm[i];
}
pt(su);
}
}
}
return 0;
}
莫隊算法
也是分塊算法的其中一種,可以用於處理一類離線的區間查詢問題,適用范圍廣,復雜度O(n根號n)實際跑得飛快,
使用方法:分塊之后,將問題排序,以左端點所在塊為第一關鍵字,右端點為第二關鍵字。之后類似暴力的算法,暴力移動左右指針即可。
如果只有雙指針,那么分塊(根號n)
如果三指針(eg加上維度時間),分塊(n^2/3)
四指針 n^(3/4),以此類推。
luogu1972 HH的項鏈
數據加強后代碼過不了,,,反正只是記一個莫隊的板子
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 500005;
int n,m;
struct node{
int l,r,id;
}z[maxn];
int a[maxn],blo[maxn],ANS[maxn];
bool cmp(node x,node y) {
if(blo[x.l]==blo[y.l]) return x.r < y.r;
return x.l < y.l;
}
int tot[1000005];
int lzz=1,rzz,ans;
void ADD(int x) {
if(!(tot[a[x]]++)) ans++;
}
void DEL(int x) {
if(!(--tot[a[x]])) ans--;
}
int main(){
scanf("%d",&n);
int fk = pow(n,0.5);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
blo[i] = blo[i-1];
if(i%fk==1) blo[i]++;
}
scanf("%d",&m);
for(int i=1;i<=m;i++) {
z[i].id = i;
scanf("%d%d",&z[i].l,&z[i].r);
}
sort(z+1,z+1+m,cmp);
for(int i=1;i<=m;i++) {
while(rzzz[i].l) ADD(--lzz);
while(rzz>z[i].r) DEL(rzz--);
while(lzz
bzoj2120 (hydro oj)
三階莫隊,也稱帶修莫隊。
和上面hh的項鏈幾乎一模一樣,不同的一點點就是增加了time這個軸,修改時時間回退或者時間向前即可。
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 20005;
struct node {
int l,r,tm,id;
}z[maxn];
int cjp[maxn],cjcol[maxn],pacol[maxn],fk,blo[maxn];
bool cmp(node x,node y) {
if(blo[x.l]!=blo[y.l]) return x.l < y.l;
if(blo[x.r]!=blo[y.r]) return x.r < y.r;
return x.tm > y.tm;
}
int a[maxn],ANS[maxn],ans;
int tot[1000005];
int n,m,ntim,qrm,lzz=1,rzz;
char ss[3];
void add(int x) {
if(!(tot[a[x]])) ans++;
tot[a[x]]++;
}
void del(int x) {
tot[a[x]]--;
if(!tot[a[x]]) ans--;
}
void mol(int p,int x) {
if(lzz<=p&&p<=rzz) del(p);
a[p] = x;
if(lzz<=p&&p<=rzz) add(p);
}
int main(){
// freopen("2.in","r",stdin);
// freopen("dp.out","w",stdout);
scanf("%d%d",&n,&m);
int fk = pow(n,0.66);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
blo[i] = blo[i-1];
if(i%fk==1) blo[i]++;
}
for(int i=1;i<=m;i++) {
scanf("%s",ss+1);
if(ss[1]=='Q') {
++qrm;
scanf("%d%d",&z[qrm].l,&z[qrm].r); z[qrm].id=qrm;
z[qrm].tm = ntim;
} else {
++ntim; scanf("%d%d",&cjp[ntim],&cjcol[ntim]);
pacol[ntim] = a[cjp[ntim]];
a[cjp[ntim]] = cjcol[ntim];
}
}
sort(z+1,z+1+qrm,cmp);
for(int i=1;i<=qrm;i++) {
while(rzzz[i].l) add(--lzz);
while(rzz>z[i].r) del(rzz--);
while(lzzz[i].tm) mol(cjp[ntim],pacol[ntim]),ntim--;
while(ntim
平衡樹之treap
樹鏈剖分
樹剖每次找出重兒子,然后以此划一條鏈。本質是將一顆樹哈希后保存下來,具體實現上通常采用線段樹。值得記住的一個性質是一個點以其為根的子樹下的編號都是連續的。
luogu3384 樹剖模板題
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 2e5+5;
int la[maxn],nt[maxn],en[maxn],owo;
void adg(int x,int y) {
en[++owo] = y; nt[owo] = la[x]; la[x] = owo;
}
//treecut
int sz[maxn],dep[maxn],zerz[maxn],top[maxn],newid[maxn],oldid[maxn],fa[maxn],idcnt,endid[maxn];
int N,M,RT,MOD;
void ad(int &x,int y) { x+=y; if(x>=MOD)x-=MOD; }
int mu(int x,int y) { return 1ll*x*y%MOD; }
int ysa[maxn];//原始的val[i]
void dfs1(int x,int pre) {
fa[x] = pre; dep[x] = dep[pre]+1;
sz[x] = 1; int mz = 0;
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
if(y==pre) continue;
dfs1(y,x);
sz[x] += sz[y];
if(sz[y]>sz[mz]) mz = y;
}
zerz[x] = mz;
}
void dfs2(int x,int pre,int ace) {
newid[x] = ++idcnt; oldid[idcnt] = x;
top[x] = ace;
if(zerz[x]) dfs2(zerz[x],x,ace);
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
if(y==pre||y==zerz[x]) continue;
dfs2(y,x,y);
}
endid[x] = idcnt;
}
//segmenttree
int ls[maxn],rs[maxn],tot,laz[maxn],sum[maxn];
void putup(int p) {
sum[p] = 0; ad(sum[p],sum[ls[p]]); ad(sum[p],sum[rs[p]]);
}
void putdown(int p,int l,int mid,int r) {
if(!laz[p]) return;
ad(sum[ls[p]],mu(mid-l+1,laz[p]));
ad(sum[rs[p]],mu(r-mid,laz[p]));
ad(laz[ls[p]],laz[p]); ad(laz[rs[p]],laz[p]);
laz[p] = 0;
}
int maketree(int l,int r) {
int p = ++tot;
if(l==r) {
sum[p] = ysa[oldid[l]];
return p;
}
int mid = (l+r)>>1;
ls[p] = maketree(l,mid);
rs[p] = maketree(mid+1,r);
putup(p);
return p;
}
void adxds(int p,int l,int r,int x,int y,int z) {
if(x<=l&&r<=y) {
ad(laz[p],z); ad(sum[p],mu(r-l+1,z));
return;
}
int mid = (l+r)>>1;
putdown(p,l,mid,r);
if(y<=mid) adxds(ls[p],l,mid,x,y,z);
else if(x>mid) adxds(rs[p],mid+1,r,x,y,z);
else adxds(ls[p],l,mid,x,y,z),adxds(rs[p],mid+1,r,x,y,z);
putup(p);
}
int getansxds(int p,int l,int r,int x,int y) {
if(x<=l&&r<=y) return sum[p];
int mid = (l+r)>>1;
putdown(p,l,mid,r);
if(y<=mid) return getansxds(ls[p],l,mid,x,y);
else if(x>mid) return getansxds(rs[p],mid+1,r,x,y);
int res = 0;
ad(res,getansxds(ls[p],l,mid,x,y));
ad(res,getansxds(rs[p],mid+1,r,x,y));
return res;
}
//
void adsp(int x,int y,int z) {
while(top[x]!=top[y]) {
if(dep[top[x]]dep[y]) swap(x,y);
adxds(1,1,N,newid[x],newid[y],z);
}
int getanssp(int x,int y) {
int res = 0;
while(top[x]!=top[y]) {
if(dep[top[x]]dep[y]) swap(x,y);
ad(res,getansxds(1,1,N,newid[x],newid[y]));
return res;
}
int main(){
scanf("%d%d%d%d",&N,&M,&RT,&MOD);
for(int i=1;i<=N;i++) scanf("%d",&ysa[i]);
for(int i=1;i
最短路(dijkstra+spfa)
dijkstra
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 205;
int nt[maxn],en[maxn],la[maxn],owo,len[maxn];
void adg(int x,int y,int z) {
en[++owo] = y; nt[owo] = la[x]; la[x] = owo; len[owo] = z;
}
int n,m,ST,ED,dis[maxn];
struct node {
int to,di;
};
bool operator<(node aa,node bb) {
return aa.di > bb.di;
}
priority_queue q;
void dijkstra() {
for(int i=1;i<=n;i++) dis[i] = 0x3f3f3f3f;
dis[ST] = 0; q.push((node){ST,0});
while(q.size()) {
int x = q.top().to; int didi = q.top().di;
q.pop();
if(didi!=dis[x]) continue;
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
if(dis[y]>dis[x]+len[it]) {
dis[y] = dis[x]+len[it];
q.push((node){y,dis[y]});
}
}
}
printf("%d",dis[ED]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
adg(x,y,z);
}
scanf("%d%d",&ST,&ED);
dijkstra();
return 0;
}
spfa比較簡單就不練了orz
背包動態規划
稍微復習了一下基礎的背包:對於單個物品的背包空間就從大到小枚舉,無數物品從小到大枚舉,多重背包枚舉多層。(對於更高級的背包暫時備戰csp是放棄了orz)。。。
歐拉篩
原理還蠻簡單的,該篩法用於O(n)篩出一定上限內的所有質數。原理:每個合數只會被它最小的那個質數所篩出,故當去合數階段發現i%pri[j]==0即可break跳出,因為對於i*pri[j+1]應該是被pri[j]所篩。
luogu3383模板題
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int n,q;
int pri[8000005],tot;
bool ishs[100000005];
void init() {
ishs[0]=ishs[1]=0;
for(int i=2;i<=100000000;i++) {
if(!ishs[i]) pri[++tot] = i;
for(int j=1;j<=tot&&pri[j]*i<=100000000;j++) {
ishs[pri[j]*i] = 1;
if(i%pri[j]==0) break;
}
}
}
int main(){
scanf("%d%d",&n,&q);
init();
while(q--) {
int x; scanf("%d",&x);
printf("%d\n",pri[x]);
}
return 0;
}
GCD與擴展gcd
GCD:
由於gcd(a,b)==gcd(a-b,b) (b<a)由此可知 gcd==gcd(b,a%b),輾轉相除,當b^為0時,可得到gcd(a,b)===此時的a^。
那么可以得出(該為翡蜀定理哦)存在a*x + b*y == gcd(a,b),我們可以得到其中的若干組解。
以上截圖自zzt1208的博客。該算法可用於求解模的逆元以及求解的問題。對於a*x1+b*y1==c,可以用a*x2+b*y2==gcd(a,b),再x1==x2*(c/gcd)+(b/gcd)*k , y1==y2*(c/gcd) - (a/gcd)*k (k為整數)
luogu5656 做法完全在該博客中體現
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
ll a,b,c;
ll exgcd(ll a,ll b,ll &x,ll &y) {
if(!b) {
x = 1; y = 0; return a;
}
ll r = exgcd(b,a%b,x,y);
ll xx = x,yy = y;
x = yy; y = (xx-(a/b)*yy);
return r;
}
void solve() {
scanf("%lld%lld%lld",&a,&b,&c);
ll x1,y1;
ll d=exgcd(a,b,x1,y1);//a*x1+b*y1==gcd(a,b)
if(c%d) { puts("-1"); return; }
x1 = x1*c/d; y1 = y1*c/d;
ll dx = b/d , dy = a/d;
ll kxj = ceil(1.0*(-x1+1)/dx);
ll ksj = floor(1.0*(y1-1)/dy);
if(kxj>ksj) {
printf("%lld %lld\n",x1+kxj*dx,y1-ksj*dy);
} else {
printf("%lld %lld %lld %lld %lld\n",ksj-kxj+1,x1+kxj*dx,y1-ksj*dy,x1+ksj*dx,y1-kxj*dy);
}
}
int main(){
ll T;
scanf("%lld",&T);
while(T--) solve();
return 0;
}
網絡流
拓撲排序
將有向圖中的頂點排列成一個拓撲序列的過程,稱為拓撲排序。做法是通過將所有入度為0的點入棧,然后加入排序列,再將該點相連的點的入度-1,再重復過程即可得到一個完整的拓撲序。
eg:神經網絡luogu1038
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 10005;
int N,P;
int en[maxn],la[maxn],nt[maxn],vl[maxn],owo;
void adg(int x,int y,int z) {
en[++owo]=y; nt[owo]=la[x]; la[x]=owo; vl[owo]=z;
}
int U[maxn],C[maxn];
int W[maxn][maxn],rd[maxn],cd[maxn];
int st[maxn],top;
int main(){
scanf("%d%d",&N,&P);
for(int i=1;i<=N;i++) {
scanf("%d%d",&C[i],&U[i]);
if(!C[i])C[i] -= U[i];
}
for(int i=1;i<=P;i++) {
int x,y,w; scanf("%d%d%d",&x,&y,&w);
adg(x,y,w); rd[y]++; cd[x]++;
}
for(int i=1;i<=N;i++) {
if(!rd[i]) {
st[++top]=i;
}
}
while(top) {
int x = st[top--];
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
rd[y]--;
if(C[x]>0) {
C[y] += C[x]*vl[it];
}
if(!rd[y]) {
st[++top]=y;
}
}
}
bool flag = 0;
for(int i=1;i<=N;i++) {
if((!cd[i])&&C[i]>0) {
flag = 1;
printf("%d %d\n",i,C[i]);
}
}
if(!flag) puts("NULL");
return 0;
}
計算幾何
正二八經的計算幾何是不可能搞了。。刷了一道csp真題。。就這樣吧。。
CSP202009-4星際旅行
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define db double
using namespace std;
const int maxn = 2005;
int n,m,R;
db oo[105],a[105][maxn];
db DIS[maxn][maxn];
db getjl(int aa,int bb) {
db dis = 0;//aa-->bb distance
db odisa = 0 , odisb = 0;
db S , H , P; //面積 高 半周長
for(int i=1;i<=n;i++) {
dis += (a[i][aa]-a[i][bb])*(a[i][aa]-a[i][bb]);
odisa += (a[i][aa]-oo[i])*(a[i][aa]-oo[i]);
odisb += (a[i][bb]-oo[i])*(a[i][bb]-oo[i]);
}
dis = sqrt(dis); odisa = sqrt(odisa); odisb = sqrt(odisb);
P = (dis + odisa + odisb)/2.0;
S = sqrt(P*(P-dis)*(P-odisb)*(P-odisa));
H = 2.0 * S / dis;
if(H>=R) return dis;
if(odisa*odisa+dis*dis<=odisb*odisb) return dis;
if(odisb*odisb+dis*dis<=odisa*odisa) return dis;
db sita = acos( (odisa*odisa+odisb*odisb-dis*dis)/(2*odisb*odisa) );
db sit1 = acos( 1.0*R/odisa );
db sit2 = acos( 1.0*R/odisb );
db sisi = sita - sit1 - sit2;
db ans = 1.0*R*sisi + sqrt(odisa*odisa-R*R) + sqrt(odisb*odisb-R*R);
return ans;
}
int main(){
scanf("%d%d%d",&n,&m,&R);
for(int i=1;i<=n;i++) {
scanf("%lf",&oo[i]);
}
for(int i=1;i<=m;i++) {
for(int j=1;j<=n;j++) {
scanf("%lf",&a[j][i]);
}
}
for(int o=1;o<=m;o++) {
for(int i=o+1;i<=m;i++) {
DIS[o][i] = DIS[i][o] = getjl(o,i);
}
}
for(int i=1;i<=m;i++) {
db ans = 0;
for(int j=1;j<=m;j++) {
ans += DIS[i][j];
}
printf("%.12f\n",ans);
}
return 0;
}
字符串算法(馬拉車和ac自動機和kmp)
馬拉車算法 大致原理解釋較復雜,,用於尋找回文長度,做了一道馬拉車題,luogu4287雙倍回文,set做法
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e6+5;
int n;
char ss[maxn],c[maxn];
int r[maxn];
set >se1; // store mid + (r-1)/2
setsemid; //store mid
int main(){
scanf("%d",&n);
scanf("%s",ss+1);
for(int i=1;i<=n;i++) {
c[i*2] = ss[i];
c[i*2-1] = '#';
}
c[0] = '@'; c[n*2+1]='#'; c[n*2+2]='%';
int p = 0,mx = 0, ans = 0;
for(int i=2;i<=2*n;i++) {
if(i>1),i ) );
semid.insert(i);
while(se1.size()&&( (*se1.begin()).first < i ) ) {
int x = (*se1.begin()).second;
se1.erase(se1.begin());
semid.erase(x);
}
auto op = semid.lower_bound(i-r[i]+1);
if(op!=semid.end()) {
ans = max(ans,(i-*op)*2);
}
}
if(i+r[i]-1>mx) {
mx = i+r[i]-1; p = i;
}
}
printf("%d\n",ans);
return 0;
}
kmp算法
用於字符串匹配。大致思想即已經暴力匹配之后,不想完全回退,那么就跳fail跳回到后綴與前綴的最大匹配處繼續看匹不匹配,這樣來節省匹配時間。
luogu3375
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e6+5;
char s1[maxn],s2[maxn];
int n1,n2,fail[maxn];
int main(){
scanf("%s%s",s1+1,s2+1);
n1 = strlen(s1+1); n2 = strlen(s2+1);
int j = fail[1] = 0;
for(int i=2;i<=n2;i++) {
while(j>0&&s2[j+1]!=s2[i]) j=fail[j];
if(s2[j+1]==s2[i]) j++;
fail[i] = j;
}
j = 0;
for(int i=1;i<=n1;i++) {
while(j>0&&s2[j+1]!=s1[i]) j=fail[j];
if(s2[j+1]==s1[i]) j++;
if(j==n2) {
printf("%d\n",i-j+1);
j = fail[j];
}
}
for(int i=1;i<=n2;i++) printf("%d ",fail[i]);
return 0;
}
AC自動機
ac自動機比較像是在trie(字典樹)上的一個kmp,可以將多個子串加進去,然后在里面跑匹配。原理看的yyb的博客
luogu3808 純模板題
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e6+5;
struct trie {
int nt[26],end,fail;
}z[maxn];
int tot;
void ins(char *s) {
int len = strlen(s);
int now = 0;
for(int i=0;iq;
void buildac() {
int now = 0;
for(int i=0;i<26;i++) {
if(z[now].nt[i]) {
z[z[now].nt[i]].fail = 0; q.push(z[now].nt[i]);
}
}
while(q.size()) {
int x = q.front(); q.pop();
for(int i=0;i<26;i++) {
if(z[x].nt[i]) {
z[z[x].nt[i]].fail = z[z[x].fail].nt[i];
q.push(z[x].nt[i]);
} else z[x].nt[i]=z[z[x].fail].nt[i];
}
}
} // make ac auto machine
int ans;
int n;
char ss[maxn];
void query(char *s) {
int len = strlen(s);
int now = 0;
for(int i=0;i
luogu 3041 改到ac自動機上跑dp,f[i][j]表示時間到第i,跑到第j個時候的最大值。
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int inf = 0x3f3f3f3f;
int n,k;
int fail[305],f[1005][305];
int tot,nt[305][3],vl[305];
void ins(char *s) {
int len = strlen(s);
int now = 0;
for(int i=0;iq;
void buildac() {
for(int i=0;i<3;i++) {
if(nt[0][i]) q.push(nt[0][i]),fail[nt[0][i]]=0;
}
while(q.size()) {
int x = q.front(); q.pop();
for(int i=0;i<3;i++) {
if(nt[x][i]) fail[nt[x][i]]=nt[fail[x]][i],q.push(nt[x][i]);
else nt[x][i] = nt[fail[x]][i];
}
vl[x] += vl[fail[x]];
}
}
void dpdp() {
for(int i=0;i<=k;i++) {
for(int j=1;j<=tot;j++) {
f[i][j] = -inf;
}
}
f[0][0]=0;
for(int tim=1;tim<=k;tim++) {
for(int o=0;o<=tot;o++) {
if(f[tim-1][o]==-inf) continue;
for(int i=0;i<3;i++) {
f[tim][nt[o][i]] = max(f[tim][nt[o][i]],f[tim-1][o]+vl[nt[o][i]]);
}
}
}
int ans = -inf;
for(int o=0;o<=tot;o++) ans = max(ans,f[k][o]);
printf("%d",ans);
}
char ss[20];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) {
scanf("%s",ss);
ins(ss);
}
buildac();
dpdp();
return 0;
}
LCA 公共祖先
依稀記得求lca有兩種,,一種用倍增一種用樹剖..
倍增的話比較簡單,就是倍增father出來然后找出父親就可以了。。
樹剖也很簡單,往上跳就完事了。
點擊查看代碼(倍增)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
vectorve[500005];
int N,M,S;
int fa[500005][20],dis[500005],logg[500005];
void dfs(int x,int pre) {
dis[x] = dis[pre]+1;
fa[x][0] = pre;
for(int o=1;o<=logg[dis[x]];o++) fa[x][o] = fa[fa[x][o-1]][o-1];
for(int y:ve[x]) {
if(y==pre) continue;
dfs(y,x);
}
}
int LCA(int x,int y) {
if(dis[x]!=dis[y]) {
if(dis[x]=0;i--) {
if((cj>>i)&1) x = fa[x][i];
}
}
if(x==y) return x;
for(int i=logg[N];i>=0;i--) {
if(fa[x][i]!=fa[y][i]){
x = fa[x][i]; y = fa[y][i];
}
}
return fa[x][0];
}
int main(){
scanf("%d%d%d",&N,&M,&S);
for(int i=2;i<=N;i++) logg[i]=logg[i>>1]+1;
for(int i=1;i
圖的連通
有向圖G的最大強連通子圖稱為G的強連通分量。
最大強連通子圖的含義:該子圖是G的強連通子圖,將G的任何不在該子圖中的點加入到該子圖后,該子圖將不再連通。
求強連通分量:1.floyd傳遞閉包 2.kosaraju 3.tarjan,求出強聯通分量可以用於縮點。
kosaraju
方法:將原圖正向dfs一次,將后續遍歷的順序記錄下來,然后倒着按照反圖跑一次,反圖跑幾次dfs就是多少個SCC(強聯通分量)。該方法利用了原圖和反圖的強聯通性質是相同的,第一次的dfs本質上是進行了一次拓撲排序,反圖即拓撲序反了,然后再以此跑出強聯通分量。
Tarjan
感覺相較於kosaraju更容易理解且更簡單啊。。。常數又小又簡單,可能kosaraju比較好的是自動得到了一個關於scc的拓撲序吧。。但是跑兩遍也比不得tarjan+拓撲啊。。
tarjan的想法,對於一個有向跑dfs搜索樹,記錄dfn[x](搜索序),low[x](可以到達的最小搜索序),當dfn[x]==low[x]那么就找到了一個強連通分量。具體實現上可以用棧。可見代碼。由於整張圖不一定連通,一定要每個點都看是否已經搜索過了。
(luogu3387縮點)點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int maxn = 2e5+5;
using namespace std;
int la[maxn],nt[maxn],en[maxn],owo;
void adg(int x,int y) {
en[++owo]=y; nt[owo]=la[x]; la[x]=owo;
}
int n,m;
int vl[maxn],blo[maxn],iid;
int scc,sccvl[maxn],dfn[maxn],low[maxn];
vectorve[maxn];
bool inst[maxn]; int st[maxn],sttop,f[maxn],rd[maxn];
void tarjan(int x,int pre) {
dfn[x] = low[x] = ++iid;
inst[x] = 1; st[++sttop]=x;
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
if(!dfn[y]) {
tarjan(y,x);
low[x] = min(low[x],low[y]);
} else if(inst[y]) low[x]=min(low[x],dfn[y]);//這里low[y]和dfn[y]都可
}
if(low[x]==dfn[x]) {
++scc; int y=0;
while(y!=x){
y = st[sttop--];
inst[y]=0;
blo[y]=scc; sccvl[scc]+=vl[y];
};
}
}
queueq;
void topsort() {
for(int i=1;i<=scc;i++) {
if(!rd[i]) {
q.push(i);
}
}
while(q.size()) {
int x = q.front(); q.pop();
f[x] += sccvl[x];
for(auto y:ve[x]) {
f[y] = max(f[y],f[x]);
rd[y]--; if(!rd[y]) q.push(y);
}
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",&vl[i]);
}
for(int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
adg(x,y);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0); //記得所有點都要tarjan!
for(int i=1;i<=n;i++) {
int x = blo[i];
for(int it=la[i];it;it=nt[it]){
int y = blo[en[it]];
if(x!=y) ve[x].push_back(y),rd[y]++;
}
}
topsort();
int ans = 0;
for(int i=1;i<=scc;i++) ans = max(ans,f[i]);
printf("%d\n",ans);
}
tarjan還可以用來判斷一點是否是另一點祖先與求無向圖割點與割邊。
(luogu3388割點)點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 2e4+5;
vectorve[maxn];
int n,m;
int GD;
int scc,blo[maxn];
bool inst[maxn];
int ans[maxn],anst;
int dfn[maxn],low[maxn],idcnt;
void tarjan(int x,int pre) {
int snum=0;
dfn[x] = low[x] = ++idcnt;
bool flag = 0;
for(auto y:ve[x]) {
if(y==pre) continue;
if(!dfn[y]) {
snum++;
tarjan(y,x);
low[x] = min(low[x],low[y]);
if(pre&&low[y]>=dfn[x]) flag = 1;
}
low[x]=min(low[x],low[y]); //這里low[y]和dfn[y]都可
}
if((!pre)&&(snum>=2)) flag = 1;
if(flag) {
ans[++anst] = x;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
int x,y; scanf("%d%d",&x,&y);
ve[x].push_back(y);
ve[y].push_back(x);
}
for(int i=1;i<=n;i++) {
if(!dfn[i]) tarjan(i,0);
}
printf("%d\n",anst);
sort(ans+1,ans+1+anst);
for(int i=1;i<=anst;i++) printf("%d ",ans[i]);
return 0;
}
中國剩余定理
關於M1^(-1)怎么求,即Mi*Mi^(-1)-Wi*y==1用exgcd求出即可。
luogu1495 中國剩余定理模板點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y) {
if(!b) {
x = 1; y = 0; return a;
}
ll xx,yy;
ll r = exgcd(b,a%b,xx,yy);
x = yy; y = (xx-(a/b)*yy);
return r;
}
ll n;
ll P=1;
ll mod[15],b[15];
int main(){
scanf("%lld",&n);
for(ll i=1;i<=n;i++) {
scanf("%lld%lld",&mod[i],&b[i]);
P *= mod[i];
}
ll ans = 0;
for(ll i=1;i<=n;i++) {
ll M = P/mod[i];
ll x,y;
exgcd(M,mod[i],x,y);
x = (x%mod[i]+mod[i])%mod[i];
ans = (ans + M*x%P*b[i]%P)%P;
}
printf("%lld",ans);
return 0;
}
對於某個為質數的mod的逆元,可得到逆元為快速冪ksm(x,mod-2)。
證明???以后再說吧。
卡特蘭數
卡特蘭數的前幾位: 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862
斯特林數
第二類斯特林數S2[n][m]表示把n個元素划分成m個非空集合的方案數。
eg:何老板在NK食堂訂了m桌酒席,宴請信競隊的n名隊員。由你來安排隊員們就座。要求每桌至少坐1人。問總共有多少種安排方案?
S2[n][m] = S2[n-1][m-1] + S2[n-1][m]*m (即新開一個集合和加入已有集合)。
初始化S2[k][1] = 1
第一類斯特林數S1[n][m]表示把n個元素划分成m個非空循環排列集合的方案數。
eg:何老板在NK食堂訂了m桌酒席,宴請信競隊的n名隊員。酒桌為圓形。由你來安排隊員們就座。要求每桌至少坐1人,最多坐n人。問總共有多少種不同的安排方案?
S1[n][m] = S1[n-1][m-1] + (n-1)*S1[n-1][m] (即新開集合或者到某個元素(tongxue)左邊).
貝爾數
貝爾數B[n]表示把n個元素划分成若干個非空集合的方案數。
B[n]=S2[n][1]+S2[n][2]+S2[n][3]+…+S2[n][n]
最小生成樹
最小生成樹,對於一個n個點的無向圖,用最小的權值總和的邊集合將所有n個點相連。分為kruskal和prim都是基於貪心的算法。
關於kruskal,方法就是將所有邊排序,每次查看邊連接兩點是否同一個集合,若不是則可以聯這條邊。
關於PRIM算法從任意一個頂點開始,每次選擇一個與當前頂點集最近的一個頂點,並將兩頂點之間的邊加入到樹中。
差分約束
樹的直徑
樹的直徑為跑兩遍bfs,第一遍隨機一個點跑出的dep最遠點從該點出發再跑bfs得到的最遠另一點,兩點相連即可得到想要的直徑。
luogu4408 [NOI2003] 逃學的小孩 直徑的板子題吧,答案為直徑的長度加上max(min(A->i,B-->i))。
點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
using namespace std;
const int maxn = 4e5+5;
ll en[maxn],nt[maxn],la[maxn],len[maxn],owo;
void adg(ll x,ll y,ll z) { en[++owo]=y; nt[owo]=la[x]; la[x]=owo; len[owo]=z; }
ll n,m;
ll dep[maxn],rdep[maxn];
int qe[maxn],qf=1,qm;
bool vis[maxn];
ll bfs(ll kait) {
qf = 1; qm = 1; qe[1] = kait;
for(int i=1;i<=n;i++) {
vis[i] = 0;
}
dep[kait]=0;
while(qf<=qm) {
int x = qe[qf++]; vis[x]=1;
for(int it=la[x];it;it=nt[it]) {
int y = en[it];
if(vis[y]) continue;
dep[y] = dep[x] + len[it];
qe[++qm] = y;
}
}
int mx = 0;
for(int i=1;i<=n;i++) {
if(dep[i]>=dep[mx]) mx = i;
}
return mx;
}
ll A,B;
int main(){
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++) {
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
adg(x,y,z); adg(y,x,z);
}
A = bfs(1);
B = bfs(A);
for(int i=1;i<=n;i++) rdep[i] = dep[i];
ll ans = dep[B];
ll mm = 0;
bfs(B);
for(int i=1;i<=n;i++) {
mm = max(mm,ans+min(rdep[i],dep[i]) );
}
printf("%lld",mm);
return 0;
}
哈夫曼樹
啊,一看上一次提交荷馬史詩居然還是高一的時候。。一轉眼四年過去我還是這么菜。。
哈夫曼樹和合並果子很像,是指的帶權路徑長度WPL最短的多叉樹(最優多叉樹)。
具體方法如果是二叉樹每次找出最小的兩個權值的合並然后再放入堆中繼續去合並這樣可以想到最后可以達到最小。
而如果要K叉樹,可以發現如果還是一樣從開始就每次k個的合並,最后一次合並不足k個那么容易得到這樣不是最優的。所以我們應當添加空果子結點,當(n-1)%(k-1)不為0時補足。(因為每次合並要減少k-1個果子,最后剩下一個果子總少了n-1個果子)
而對於NOI2015荷馬史詩這道題,我們發現還要求盡量保證樹長度最小那么只要判堆排序時第一關鍵字權值第二關鍵字高度就可以了。
P2168 [NOI2015] 荷馬史詩點擊查看代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
using namespace std;
ll n,k;
struct node {
int cs;
ll wi;
}z[200005];
ll ans1,ans2;
bool operator<(node aa,node bb) {
if(aa.wi!=bb.wi) return aa.wi > bb.wi;
return aa.cs > bb.cs;
}
priority_queueq;
int main(){
// freopen("P2168_5.in","r",stdin);
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) {
scanf("%lld",&z[i].wi);
}
if((n-1)%(k-1)) {
int oo = (k-1)-(n-1)%(k-1);
n += oo;
}
for(int i=1;i<=n;i++) q.push((node){0,z[i].wi});
while(q.size()!=1) {
int ccs = 0; ll vl = 0;
node aa;
for(int i=1;i<=k;i++) {
vl += q.top().wi;
ccs = max(ccs,q.top().cs);
q.pop();
}
ans1 += vl; ccs++;
q.push((node){ccs,vl});
}
printf("%lld\n%d",ans1,q.top().cs);
return 0;
}