OI康复计划

好久好久没有碰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的blog

以上截图自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

令h(1)=1,Catalan数的第n项为h(n),满足递归式:
h(n) = h(1)*h(n-1) + h(2)*h(n-2) + ... + h(n-1)h(1),n>=2
也可以表示为:
h(n) = C(2n,n)/(n+1)
h(n) = h(n-1)*(4*n-2)/(n+1)
h(n) = C(2n,n) - C(2n,n-1) ,n=1,2,3,...
神奇之处:很多看上去毫不相干的组合计数问题的最终表达式都是卡特兰数的形式。
eg:括号匹配,进出栈,n个结点构成二叉树,凸多边形的划分。

斯特林数

第二类斯特林数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;
}
posted @ 2021-09-14 23:43  Newuser233  阅读(163)  评论(2编辑  收藏  举报