【模板复习+重构( 重 新 开 始 )记录+注解】+【错误记录】

目录(未完善)

1.快速幂||取余运算
2.线段树1(区间加)
3.gcd
4.树状数组_单点加+区间查询
5.树链剖分
6.单调队列
7.单调栈
8.Dijkstra(无堆优化+有堆优化)
9.Tarjan缩点
10.二分

快速幂_取余

快速幂_取余运算
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
long long mod;
inline long long pow_(long long a,long long b)
{
	long long ans=1,base=a;
	while (b)
	{
		if (b&1)//拆解b的当前计算位
		{
			ans*=base;//(base是a的2的当前计算位-1次幂次幂)
			ans%=mod;
		}
		base*=base;//(base^2)
		base%=mod;
		b>>=1;
	}
	return ans;
}

int main()
{
	long long n,m;
	scanf("%lld%lld%lld",&n,&m,&mod);
	printf("%lld^%lld mod %lld=%lld
",n,m,mod,pow_(n,m));
}

(tips:long tmd)


线段树_区间加

线段树_区间加
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233) 
long long ans[MAXN<<2],tag[MAXN<<2];
int n,m;
#define leftson cur<<1
#define rightson cur<<1|1
#define mid ((l+r)>>1)
#define len (r-l+1)
#define push_up ans[cur]=ans[leftson]+ans[rightson]
#define push_down down(leftson,l,mid,tag[cur]); down(rightson,mid+1,r,tag[cur]); tag[cur]=0//其实这里似乎应该加一个if (tag[cur]!=0)(?)
void build(int cur,int l,int r)
{
	if (l==r)
	{
		scanf("%lld",&ans[cur]);
		return;
	}
	build(leftson,l,mid);
	build(rightson,mid+1,r);
	push_up;
}
inline void down(int cur,int l,int r,long long k)
{
	ans[cur]+=(len*k);
	tag[cur]+=k;
	return;
}
void add(int cur,int l,int r,int cl,int cr,long long k)
{
	if (cl<=l&&r<=cr)
	{
		ans[cur]+=(k*len);
		tag[cur]+=k;
		return;
	}
	push_down;//节点标记下传
	if (cl<=mid) add(leftson,l,mid,cl,cr,k);//修改区间包含左子树则操作
	if (cr>mid) add(rightson,mid+1,r,cl,cr,k);//修改区间包含右子树则操作
	push_up;//上传
}
long long query(int cur,int l,int r,int ql,int qr)
{
	if (ql<=l&&r<=qr) return ans[cur];
	push_down;
	long long answ=0;
	if (ql<=mid) answ+=query(leftson,l,mid,ql,qr);
	if (qr>mid) answ+=query(rightson,mid+1,r,ql,qr);
	push_up;
	return answ;
}
int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	int opt,x,y;
	long long k;
	while(m--)
	{
		scanf("%d%d%d",&opt,&x,&y);
		if (opt==1)
		{
			scanf("%lld",&k);
			add(1,1,n,x,y,k);
		}
		else printf("%lld
",query(1,1,n,x,y));
	}
	return 0;
}
>(tips:傻逼才会把len mid k写乱位置,我是傻逼)//QAQ 我也会( >(tips:n*m的数组for 1 to n for 1 to n 是傻逼)

线段树2_区间乘_区间加

这份代码是我初一的时候写的。有时间再来重构一下吧
话说最近重构了好多板子啊
不过我以前确实码风有点丑

线段树2_区间乘_区间加
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#define ll long long
#define MAXN 200002<<1
#define leftson cur<<1
#define rightson (cur<<1|1)
#define mid ((l+r)>>1)
#define push_up ans[cur]=ans[leftson]+ans[rightson]
//#define push_down downmul(cur); downadd(cur,l,r)
#define push_down pushdown(cur);
using namespace std;
ll ans[MAXN],add[MAXN]={},mul[MAXN],lefts[MAXN],rights[MAXN];
ll n,m,mod;
ll a,b,c;
void build(ll cur,ll l,ll r)
{
	mul[cur]=1;
	add[cur]=0;
	lefts[cur]=l;
	rights[cur]=r;
    if (l==r)
    {
        scanf("%lld",&ans[cur]);
        return;
    }
    build(leftson,l,mid);
    build(rightson,mid+1,r);
    push_up;
}
/*
inline void downmul(ll cur)
{
    if (mul[cur]!=1)
    {
        ans[leftson]=(ans[leftson]*mul[cur])%mod;
        ans[rightson]=(ans[rightson]*mul[cur])%mod;
        mul[leftson]=(mul[leftson]*mul[cur])%mod;
        mul[rightson]=(mul[rightson]*mul[cur])%mod;
        add[leftson]=(add[leftson]*mul[cur])%mod;
        add[rightson]=(add[rightson]*mul[cur])%mod;
        mul[cur]=1;
    }
}
inline void downadd(ll cur,ll l,ll r)
{
    ll ln=(mid-l+1),rn=(r-mid);
    if (add[cur]!=0)
    {
        ans[leftson]=(ans[leftson]+add[cur]*ln)%mod;
        ans[rightson]=(ans[rightson]+add[cur]*rn)%mod;
        add[cur]=0;
    }
}
*///好像没用到
inline void pushdown(ll cur)
{
	ans[leftson]=(ans[leftson]*mul[cur]+add[cur]*(rights[leftson]-lefts[leftson]+1))%mod;//大概是想写 ans[cur]=ans[cur]*kmul+len*kadd
	ans[rightson]=(ans[rightson]*mul[cur]+add[cur]*(rights[rightson]-lefts[rightson]+1))%mod;
	mul[leftson]=(mul[leftson]*mul[cur])%mod;//  mul[cur]*=kmul;
	mul[rightson]=(mul[rightson]*mul[cur])%mod;
	add[leftson]=(add[leftson]*mul[cur]+add[cur])%mod;//  add[cur]=add[cur]*kmul+kadd;
	add[rightson]=(add[rightson]*mul[cur]+add[cur])%mod;
	mul[cur]=1;//清空掉父亲的标记
	add[cur]=0;
}
inline void mulchange(ll mu,ll mur,ll cur,ll l,ll r,ll delta)
{
    if (mu<=l&&r<=mur)
    {
        ans[cur]=ans[cur]*delta%mod;
        mul[cur]=mul[cur]*delta%mod;
        add[cur]=add[cur]*delta%mod;
        return;
    }
    push_down;
    if (mu<=mid) mulchange(mu,mur,leftson,l,mid,delta);
    if (mur>mid) mulchange(mu,mur,rightson,mid+1,r,delta);
    push_up;
}
inline void addchange(ll adl,ll adr,ll cur,ll l,ll r,ll delta)
{
    if (adl<=l&&r<=adr)
    {
        ans[cur]+=delta*(r-l+1);
        add[cur]+=delta;
        return;
    }
    push_down;
    if (adl<=mid) addchange(adl,adr,leftson,l,mid,delta);
    if (adr>mid) addchange(adl,adr,rightson,mid+1,r,delta);
    push_up;
}
ll query(ll quel,ll quer,ll cur,ll l,ll r)
{
    if (quel<=l&&r<=quer)
    {
        return ans[cur];
    }
    push_down;
    ll answer=0;
    if (quel<=mid) answer+=query(quel,quer,leftson,l,mid);
    if (quer>mid) answer+=query(quel,quer,rightson,mid+1,r);
    return answer;
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&mod);
    build(1,1,n);
    ll cs;
    for (ll i=1;i<=m;i++)
    {
        scanf("%lld",&cs);
        if (cs==1)
        {
            scanf("%lld%lld%lld",&a,&b,&c);
            mulchange(a,b,1,1,n,c);
            continue;
        }
        if (cs==2)
        {
            scanf("%lld%lld%lld",&a,&b,&c);
            addchange(a,b,1,1,n,c);
            continue;
        }
        scanf("%lld%lld",&a,&b);
        printf("%lld
",query(a,b,1,1,n)%mod);
    }
    return 0;
}

gcd

gcd
int gcd(int a,int b)
{
	if (b==0) return a;
	return gcd(b,a%b);
}

树状数组_单点加_区间查询

附一个lowbit的意义:非负整数n在二进制表示下,最低位的1及后边所有的0构成的数值。例如(lowbit((1001100)_2)=(100)_2)

树状数组_单点加_区间查询
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(5e5+233)
int n,m;
int c[MAXN];
#define lowbit(a) (a&-a)
inline void add(int x,int k)
{
	while (x<=n)
	{
		c[x]+=k;
		x+=lowbit(x);
	}
}
inline int query(int x)
{
	int sum=0;
	while (x>0)
	{
		sum+=c[x];
		x-=lowbit(x);
	}
	return sum;
}
inline int solve(int x,int y) { return query(y)-query(x-1); }//如果有进行模运算这里应该需要注意一下...

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1,k;i<=n;i++)
	{
		scanf("%d",&k);
		add(i,k);
	}
	int opt,x,y;
	while (m--)
	{
		scanf("%d%d%d",&opt,&x,&y);
		if (opt==1) add(x,y);
		else printf("%d
",solve(x,y));
	}
}

(tips:void return int的sb()

(tips:减模小心爆负)

(tips:线段树开四倍数组((


树链剖分

树链剖分
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233)
struct qwq
{
	int nex,to;
}e[MAXN<<1];
int h[MAXN],tot=0;
int n,m,r;
long long mod;
inline void add(int x,int y)
{
	e[++tot].nex=h[x];
	e[tot].to=y;
	h[x]=tot;
}
long long a[MAXN],a2[MAXN];
//seg_tree
long long ans[MAXN<<2],tag[MAXN<<2];
#define leftson cur<<1
#define rightson cur<<1|1
#define mid ((l+r)>>1)
#define len (r-l+1)
#define push_up ans[cur]=ans[leftson]+ans[rightson]
#define push_down down(leftson,l,mid,tag[cur]); down(rightson,mid+1,r,tag[cur]); tag[cur]=0;
//线段树部分
inline void down(int cur,int l,int r,long long k)
{
	ans[cur]+=((len*k)%mod);
	tag[cur]+=k;
	ans[cur]%=mod;
	tag[cur]%=mod;
}
void build(int cur,int l,int r)
{
	if (l==r)
	{
		ans[cur]=a2[l];//建树使用新dfs序编号
		return;
	}
	build(leftson,l,mid);
	build(rightson,mid+1,r);
	push_up;
}
void change(int cur,int l,int r,int cl,int cr,long long k)
{
	if (cl<=l&&r<=cr)
	{
		ans[cur]+=((len*k)%mod);
		tag[cur]+=k;
		ans[cur]%=mod;
		tag[cur]%=mod;
		return;//main开头的调试甚至是因为这里没写return()
	}
	push_down;
	if (cl<=mid) change(leftson,l,mid,cl,cr,k);
	if (cr>mid) change(rightson,mid+1,r,cl,cr,k);
	push_up;
}
long long query(int cur,int l,int r,int ql,int qr)
{
	if (ql<=l&&r<=qr) return ans[cur]%mod;
	push_down;
	long long answ=0;//不要和线段树数组重名!!!!!!!!!!!!!!!!!!!!!!!wtm错了好多次
	if (ql<=mid) answ+=query(leftson,l,mid,ql,qr)%mod;
	if (qr>mid) answ+=query(rightson,mid+1,r,ql,qr)%mod;
	push_up;
	return answ%mod;
}

//tree
int id[MAXN],son[MAXN],dep[MAXN],fa[MAXN],siz[MAXN],top[MAXN];//父亲,深度,子树大小,重儿子,重链顶,树剖dfs序编号
//init
void dfs_1(int x)
{
	siz[x]=1;//子树要先计算本身
	int maxn=-1;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (y==fa[x]) continue;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs_1(y);
		siz[x]+=siz[y];//愿世间没有____=____+1
		if (siz[y]>maxn)//子树最大的儿子为重儿子
		{
			son[x]=y;
			maxn=siz[y];
		}
	}
}
int cnt=0;
void dfs_2(int x,int tp)
{
	id[x]=++cnt;
	a2[cnt]=a[x];
	top[x]=tp;//重链顶
	if (!son[x]) return;//重链由重儿子延续
	dfs_2(son[x],tp);
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (y==fa[x]||y==son[x]) continue;
		dfs_2(y,y);//轻儿子作为新重链顶
	}
}

//add list
inline void add_list(int x,int y,long long k)
{
	while (top[x]!=top[y])//当x,y不在同一条链上,进行跳链
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);//更新为x相对y所在链顶端深度较低
		change(1,1,n,id[top[x]],id[x],k);//对x所在的链中被需操作链包含的部分操作,即当前x所在重链由x到重链顶 
		x=fa[top[x]];//x跳至当前重链顶
	}
	if (dep[x]>dep[y]) swap(x,y);
	change(1,1,n,id[x],id[y],k);//x与y在同一条链上,则编号连续,直接进行操作
	return;
}
//query list
inline long long query_list(int x,int y)
{
	long long answ=0;
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		answ+=query(1,1,n,id[top[x]],id[x]);
		answ%=mod;
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	answ+=query(1,1,n,id[x],id[y]);
	return answ%mod;
}
//add tree
inline void tree_add(int x,long long k)
{
	change(1,1,n,id[x],id[x]+siz[x]-1,k);//树链剖分所得的dfn本身是一种dfs序,子树由id[x]开始编号连续。
}
//query tree
inline long long tree_query(int x)
{
	return query(1,1,n,id[x],id[x]+siz[x]-1);
}
int main()
{
	scanf("%d%d%d%lld",&n,&m,&r,&mod);
	/*
	change(1,1,n,4,6,3);
	change(1,1,n,3,5,7);
	cout<<query(1,1,n,3,4)<<endl;//拿来调试的,丑(
	*/
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs_1(r);
	dfs_2(r,r);
	build(1,1,n);
	/*
	puts("uwu");
	for (int i=1;i<=n;i++) printf("%d ",id[i]);
	puts("w");*/
	int opt;
	int x,y;
	long long z;
	while (m--)
	{
		scanf("%d",&opt);
		if (opt==1)
		{
			scanf("%d%d%lld",&x,&y,&z);
			add_list(x,y,z);
		}
		else if (opt==2)
		{
			scanf("%d%d",&x,&y);
			printf("%lld
",query_list(x,y));
		}
		else if (opt==3)
		{
			scanf("%d%lld",&x,&z);
			tree_add(x,z);
		}
		else
		{
			scanf("%d",&x);
			printf("%lld
",tree_query(x));
		}
	}
	return 0;
}

单调队列

单调队列
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e6+233) 
#include <queue>
struct qwq
{
	int id,w;
};
int a[MAXN];
deque<qwq> q;
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++)//求长度为k窗口最小值,队列内元素单调递增
	{
		while (!q.empty()&&q.back().w>=a[i]) q.pop_back();//出现更小元素时,前面的小元素就过期(不可能再成为最小值)了,从队尾出队。
		q.push_back((qwq){i,a[i]});
		while (!q.empty()&&q.front().id<(i-k+1)) q.pop_front();//队列中元素出现时间单调递增,过期元素从队头出队
		if (i>=k) printf("%d ",q.front().w);
	}
	puts("");
	q.clear();
	for (int i=1;i<=n;i++)
	{
		while (!q.empty()&&q.back().w<=a[i]) q.pop_back();
		q.push_back((qwq){i,a[i]});
		while (!q.empty()&&q.front().id<(i-k+1)) q.pop_front();
		if (i>=k) printf("%d ",q.front().w);
	}
	return 0;
}

单调栈

单调栈
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#include <stack>
int n;
#define MAXN (int)(3e6+233)
stack<int> st;
int a[MAXN],r[MAXN];
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++)
	{
		while (!st.empty()&&a[st.top()]<a[i])//求每个数右边第一个比其大的数的下标,栈内元素由底到顶单调递减。当入栈不满足单调性时持续出栈
		{
			r[st.top()]=i;//出栈过程即可标记
			st.pop();
		}
		st.push(i);
	}
	for (int i=1;i<=n;i++) printf("%d ",r[i]); puts("");
	return 0;
}

dijkstra

dijkstra
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 2510
#define MAXM 6200
int n,m,s,t;
#define inf (int)(1e9+300)
struct qwq
{
	int nex,to,w;
}e[MAXM<<1];
int h[MAXN],tot=0;
inline void add(int x,int y,int z)
{
	e[++tot].nex=h[x];
	e[tot].to=y;
	h[x]=tot;
	e[tot].w=z;
}

int vis[MAXN],dis[MAXN];

inline void dijkstra()
{
	int x=s;
	vis[x]=1;//设源点为已遍历
	dis[x]=0;//源点到本身最短路为0
	int summmm=n-1,minn=inf;
	while (summmm--)//执行n-1次
	{
		for (int i=h[x],y;i;i=e[i].nex)
		{
			y=e[i].to;
			if (dis[y]>dis[x]+e[i].w)
				dis[y]=dis[x]+e[i].w;
		}
		minn=inf;
		for (int i=1;i<=n;i++)
		{
			if (!vis[i]&&dis[i]<minn)//找出未遍历的未更新答案的节点
			{
				minn=dis[i];
				x=i;
			}
		}
		vis[x]=1;//将新的预遍历节点标记
	}
}

int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for (int i=1,u,v,w;i<=m;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	for (int i=1;i<=n;i++) dis[i]=inf;
	dijkstra();
	printf("%d
",dis[t]);
}

Dijkstra_priority_queue_optimized

Dijkstra_priority_queue_optimized
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
using namespace std;
int n,m,s;
#define MAXN (int)(1e5+233)
#define MAXM (int)(2e5+233) 
struct qwq
{
	int nex,to,w;
}e[MAXM];
int h[MAXN],tot=0;
inline void add(int x,int y,int z)
{
	e[++tot].to=y;
	e[tot].w=z;
	e[tot].nex=h[x];
	h[x]=tot;
}
bool vis[MAXN];
int dis[MAXN];
struct Node { int id,diss; };
bool operator < (const Node &a,const Node &b) { return a.diss>b.diss; }
priority_queue<Node> q;
inline void dijkstra()
{
	int x;
	q.push((Node){s,0});//先把起点塞进优先队列里
	dis[s]=0;
	while (!q.empty())
	{
		x=q.top().id; q.pop();
		if (vis[x]) continue;//访问过了就跳过。
		vis[x]=true;//check in!
		for (int i=h[x],y;i;i=e[i].nex)
		{
			y=e[i].to;
			if (dis[y]>dis[x]+e[i].w)
			{
				dis[y]=dis[x]+e[i].w;//更新
				if (!vis[y])
					q.push((Node){y,dis[y]});//未访问过就塞进去
			}
		}
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	for (int i=1,x,y,z;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
	}
	for (int i=1;i<=n;i++) dis[i]=(int)(2e9+7000);
	dijkstra();
	for (int i=1;i<=n;i++) printf("%d ",dis[i]);
	return 0;
}

(tips:stl的priority_queue默认是大根堆。写转义运算符的时候要把符号反过来)

(tips:以及....初始化dis。)


缩点

yysy,这里好像有个点双边双连通分量板子....

https://www.luogu.com.cn/team/21355#problem

无向图缩点
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e4+233)
#define MAXM (int)(1e5+233)
#include <stack>
int a[MAXN];
stack<int> st;
struct qwq
{
	int nex,to;
}e[MAXM],e2[MAXM];
int h[MAXN],h2[MAXN],tot=0,tot2=0;
inline void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].nex=h[x];
	h[x]=tot;
}
bool vis[MAXN];
int dfn[MAXN],lst[MAXN],cnt=0;
int color[MAXN],c_count=0;
int w[MAXN];
void tarjan(int x)
{
	vis[x]=true;//vis实际上表示该节点是否在栈中,此处标记x入栈 
	st.push(x);//x入栈 
	dfn[x]=lst[x]=++cnt;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (!dfn[y]) { tarjan(y); lst[x]=min(lst[x],lst[y]); }//y未遍历过,(x,y)为树枝边 
		else if (vis[y]) lst[x]=min(lst[x],dfn[y]);//y在栈内 
	}
	if (dfn[x]==lst[x])//x为x所在的连通分量的根 
	{
		color[x]=++c_count;//对x染色 
		vis[x]=false;//出栈标记 
		w[c_count]+=a[x];//统计分量点权值和 
		int y;
		while (st.top()!=x)
		{
			y=st.top();
			color[y]=c_count;//对连通分量中的点染色 
			vis[y]=false;//出栈标记 
			w[c_count]+=a[y];//统计分量点权值和 
			st.pop();
		}
		st.pop();
	}
}
int in[MAXN];
inline void add2(int x,int y)
{
	in[y]++;
	e2[++tot2].to=y;
	e2[tot2].nex=h2[x];
	h2[x]=tot2; 
}
#include <queue>
queue<int> q;
int f[MAXN];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),add(x,y);
	for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
	for (int x=1;x<=n;x++)
		for (int i=h[x];i;i=e[i].nex) 
			if (color[x]!=color[e[i].to]) add2(color[x],color[e[i].to]);//以连通分量为单位重新建图(记得写add2) 
	//从这里开始使用图e2[] 
	for (int i=1;i<=c_count;i++)//新图节点数是c_count 
	{
		if (!in[i]) q.push(i);
		f[i]=w[i];
	}
	int x; 
	while (!q.empty())//缩点后图形态为DAG。进行DAG上的最长路DP 
	{
		x=q.front();
		q.pop();
		for (int i=h2[x],y;i;i=e2[i].nex)
		{
			y=e2[i].to;
			in[y]--;
			f[y]=max(f[y],f[x]+w[y]);
			if (!in[y]) q.push(y);
		}
	}
	int ans=-1;
	for (int i=1;i<=n;i++) ans=max(ans,f[i]);
	printf("%d
",ans);
	return 0;
}

https://www.luogu.com.cn/record/59925909

无向图上求双连通分量,先求出桥并标记再dfs染色。

这个代码是模拟赛时的,桥的判定是lst[y]>dfn[x]

边编号从2开始,相同边的反向边由异或1互相转换。

无向图边双联通分量缩点

边双缩点
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(5e4+233)
#define MAXM (int)(5e4+233)
struct qwq
{
	int nex,to;
}e[MAXM<<1];
int h[MAXN],tot=1;
int n,m;
inline void add(int x,int y)
{
	e[++tot].nex=h[x];
	e[tot].to=y;
	h[x]=tot;
}
bool book[MAXM<<1];
int dfn[MAXN],lst[MAXN],cnt=0;
void tarjan(int x,int ie)
{
	dfn[x]=lst[x]=++cnt;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (!dfn[y])
		{
			tarjan(y,i);
			lst[x]=min(lst[x],lst[y]);
			if (lst[y]>dfn[x]) book[i]=book[i^1]=true;//,printf("bridge: %d %d
",x,y);
		}
		else if (i!=(ie^1)) lst[x]=min(lst[x],dfn[y]);//不等于从父亲来的那条边。这样写可以处理重边
	}
}
int color[MAXN],c_count=0;
void dfs(int x)
{
	color[x]=c_count;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (color[y]||book[i]) continue;
		dfs(y);
	}
}
int deg[MAXN];
inline int tc(int x)
{
	if (x%2==0) return x/2;
	return (int)(x*1.0/2.0)+1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i,0);
	for (int i=1;i<=n;i++)
		if (!color[i]) { c_count++; dfs(i); }
	for (int x=1;x<=n;x++)
		for (int i=h[x],y;i;i=e[i].nex)
		{
			y=e[i].to;
			if (color[x]!=color[y])
				deg[color[y]]++;
		}
	int sum=0;
	for (int i=1;i<=c_count;i++)
		if (deg[i]==1) sum++;
	printf("%d
",tc(sum));
	return 0;
}

割点

割点
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(2e4+233)
#define MAXM (int)(1e5+233)
struct qwq
{
	int nex,to;
}e[MAXM<<1];
int h[MAXN],tot=0;
inline void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].nex=h[x];
	h[x]=tot;
}
int dfn[MAXN],lst[MAXN];
int id=0;
bool cut[MAXN];
int rt;
void tarjan(int x)
{
	dfn[x]=lst[x]=++id;
	int ssum=0;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (!dfn[y])
		{
			tarjan(y);
			lst[x]=min(lst[x],lst[y]);
			if (lst[y]>=dfn[x]&&x!=rt) cut[x]=true;//割点判定
			ssum++;
		}
		else lst[x]=min(lst[x],dfn[y]);
	}
	if (x==rt&&ssum>1) cut[rt]=true;//当前搜索树的root,须有至少两个子节点y满足lst[y]>=dfn[x]才为割点
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	} 
	for (int i=1;i<=n;i++)
		if (!dfn[i]) { rt=i; tarjan(i);  }
	int sum=0;
	for (int i=1;i<=n;i++)
		if (cut[i]) sum++;
	printf("%d
",sum);
	for (int i=1;i<=n;i++)
		if (cut[i]) printf("%d ",i); puts("");
	return 0;
}

写了个假的v-DCC做法之后发现一个很大的误区

v-DCC的含义是极大不含割点的子图。一个割点可能被多个v-DCC包含

一张无向图是点双连通图,当且仅当满足下列两个条件之一

1.图的顶点数不超过2

2.图中任意两点都同时包含在至少一个简单环中。(属于两个不同点双连通分量的两个节点,不可能同时包含在至少一个简单环中)

书中其实还有个引理()若某个v-DCC中存在奇环,则这个v-DCC中的所有点都被至少一个奇环包含。很好证,不写了(

求点双过程写代码注释里了(

点双连通分量
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(5e4+233)
#define MAXM (int)(1e5+233)
int n,m;
struct qwq
{
	int nex,to;
}e[MAXM<<1];
int h[MAXN],tot=0;
inline void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].nex=h[x];
	h[x]=tot;
}
#include <stack>
#include <vector>
stack<int> st;
int dfn[MAXN],lst[MAXN],cnt=0;
bool Cut[MAXN];
int rt;
vector<int> v[MAXN];
int vcnt=0;
void Tarjan(int x)
{
	dfn[x]=lst[x]=++cnt;
	st.push(x);//1.当一个节点第一次被访问,将该节点入栈。
	int ssum=0;
	if (x==rt&&(!h[x])) { v[++vcnt].push_back(x); return; }
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (!dfn[y])
		{
			Tarjan(y);
			lst[x]=min(lst[x],lst[y]);
			if (lst[y]>=dfn[x])//2.当割点判定成立(x为割点时)
			{
				if (x!=rt)
					Cut[x]=true;//这里还顺便求了割点来着....似乎是不用求的(?
				else
					ssum++;
				//从栈顶不断弹出节点直至y被弹出
				int z;
				vcnt++;
				do
				{
					z=st.top();
					st.pop();
					v[vcnt].push_back(z);
				} while (z!=y);
				v[vcnt].push_back(x);//刚刚弹出的所有节点与x一起构成了一个v-DDC。
			}
		}
		else lst[x]=min(lst[x],dfn[y]);
	}
	if (x==rt&&ssum>=2) Cut[x]=true;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	for (int i=1;i<=n;i++)
		if (!dfn[i])
		{
			rt=i;
			Tarjan(i);
		}
//	for (int i=1;i<=n;i++) printf("%d ",Cut[i]); puts("w");
	for (int i=1,len;i<=vcnt;i++)
	{
		len=v[i].size();
		for (int j=0;j<len;j++)
			printf("%d ",v[i][j]);
		printf("
");
	}
	return 0;
}

然后就是点双缩点了

方法是先给每个割点新标一个号,再将每个v-DDC与它包含的所有割点连边。

代码暂时没有()


二分

1.整数区间上二分
(I)查找最小的答案
Code
inline void binary_search(int l,int r)
{
	#define mid ((l+r)>>1)
	while (l<r)
		if (check(mid))
			r=mid;
		else l=mid+1;
	return l;
}
(II)查找最大的答案
Code
inline void binary_search(int l,int r)
{
	#define mid ((l+r+1)>>1)
	while (l<r)
		if (check(mid))
			l=mid;
		else r=mid-1;
	return l;
}
(III)实数域二分
Code
while (l+eps<r)
{
	double mid=(l+r)/2;
	if (check(mid)) r=mid; else l=mid;
}

其中eps为精度。若要保留k位小数,则取(eps=10^{-(k+2)})

乘法逆元

差点忘了。

(adiv b mod p = a*b^{p-2} mod p)

嗯....似乎还要复习一下基本的dp什么的。相当生疏了啊

二分图判定

.....怎么会有这个东西。。。。。。。。。。。。。。。

一张无向图是二分图,当且仅当图中不存在长度为奇数的环。

黑白染色即可。一个节点的相邻节点应与其染色相反,若染色过程产生冲突,则存在奇环。

所以反过来二分图判定也可以用来判奇环()

(tips:注意题目是否给出树根)

(tips:longlong!!!!!!!!longlong!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)

(tips:出现一个非0数到了另一个地方变成0了,可能是因为int a=(long long)了)


SPFA求负环

SPFA
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(2e4+233)
#define MAXM (int)(6e4+233)
struct qwq
{
	int nex,to,w;
}e[MAXM];
int h[MAXN],tot=0;
inline void add(int x,int y,int z)
{
	e[++tot].to=y;
	e[tot].nex=h[x];
	e[tot].w=z;
	h[x]=tot;
}
#define inf (long long)(2e11+233)
long long dis[MAXN];
int cnt[MAXN];
bool vis[MAXN];
int n,m;
inline void INIT()
{
	for (int i=1;i<=n;i++) dis[i]=inf,vis[i]=0,cnt[i]=0;
}
#include <queue>
queue<int> q;
int s=1;
bool spfa()
{
	scanf("%d%d",&n,&m);
	//scanf
	INIT();
	//INIT
	for (int i=1,x,y,z;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		if (z>=0) add(y,x,z);
	}
	dis[s]=0; vis[s]=true;//入队标记
	q.push(s);
	int x;
	while (!q.empty())
	{

		x=q.front();
		q.pop();
		vis[x]=false;//出队标记
		// printf("x:%d hx:%d
",x,h[x]);
		for (int i=h[x],y;i;i=e[i].nex)
		{
			y=e[i].to;
			if (dis[y]>dis[x]+e[i].w)
			{
				dis[y]=dis[x]+e[i].w;
				if (!vis[y])
				{
					cnt[y]=cnt[x]+1;
					if (cnt[y]>=n) return true;//
					q.push(y);
					vis[y]=true;//入队标记
				}
			}
		}
	}
	return false;//.......nmd
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		if (spfa()) puts("YES");
		else puts("NO");
		//edge clear
		for (int i=1;i<=n;i++) h[i]=0; tot=0;
	}
	return 0;
}

(tips:多测要清空....queue?)

(tips:喂!!!!怎么能(int)(1e16))

(tips:爆负了就从头到尾检查取模啊!!!!!!!!)

(tips:认真检查删调试!!)

(tips:别tm忘打大括号啊!!!!!!!!!)

(tips:l o n g l o n g !!!!!!!!!!!!!!!!!!!!!!下次能开就开算了()但是按照自己的做事方式似乎很难允许自己这么干)


带权并查集

在并查集的基础上对节点到父亲的边加了边权...... 路径压缩的时候就直接计算节点到根的边权和。

并查集还是带权并查集也好,都维护具有传递性的关系

放个例题:[ARC090B] People on a Line

给定(m)组信息,第(i)组信息形如((l_i,r_i,d_i)),含义为(x_{r_i}-x_{l_i}=d_i)。 判断是否所有信息都不冲突

这题原题面花里胡哨的,其实跟另一个带权并查集板子是一样的()那题是给出一系列区间和判断是否有冲突,实质上也是给出一组(a_{r_i}-a_{l_i}=d_i)

对于通过添加(root_r)(root_l)关系的话,我画了个图()虽然感觉有点冗余

这里用(fr)表示(root_r)(fl)表示(root_l)

原先的情况

现在要建立一个r到l的关系d

怎么通过dis'[fr]来建立?

使得dis[l]+d=dis[r]+dis'[fr],也即dis'[fr]=dis[l]+d-dis[r]

没了(

带权并查集
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN (int)(1e5+233)
using namespace std;
int n,m;
int dis[MAXN],fa[MAXN];
inline void INIT() { for (int i=1;i<=n;i++) fa[i]=i; }
int found(int x)
{
	if (x==fa[x]) return x;
	int rt=found(fa[x]);//先找root
//	printf("x: %d   fa: %d
",x,fa[x]);
	dis[x]+=dis[fa[x]];//再更新在当前root下的前缀和。本来在想这么写为什么正确,然后发现并查集每次合并并且运行found后森林一直是一堆菊花图的形态
	return fa[x]=rt;//路径压缩
}

int main()
{
	scanf("%d%d",&n,&m);
	INIT();
	for (int i=1,l,r,d,fl,fr;i<=m;i++)
	{
		scanf("%d%d%d",&l,&r,&d);
		fl=found(l); fr=found(r);
//		for (int j=1;j<=n;j++) printf("%d %d
",fa[j],dis[j]); puts("fA!");
		if (fl!=fr)
		{
			dis[fr]=d+dis[l]-dis[r];
			fa[fr]=fl;
		}
		else if (dis[r]-dis[l]!=d)//fl==fr,l与r在同个并查集中,也即l与r已经存在了联系。dis[i]表示r在[root[i]~i]区间的前缀和,即dis[r]-dis[l]可以表示l,r已经存在的这组联系值(换到区间和和那题更好表述为l+1到r的区间和)。
		{
			puts("No");
			return 0;
		}
	}
	puts("Yes");
	return 0;
}

https://www.luogu.com.cn/blog/wey-yzyl/zui-tai-zi-duan-hu-ji-ji-bian-shi-di-qi-shi

chy分享给我们的最大子段和变式及解法整理()

(tips:线段树维护RMQ什么的...push_up记得写手写long long max/min函数啊)

(tips:别手瓢就tm(int)(1e18)啊!!!就算写#define mod (long long)(1e9+7)也还是别int...)


李超树

把 [HEOI2013] Segment 当板子写了

本来在想先写哪道()Segment 是加线段,好像那道 JSOI 的是加直线,并且直线斜率大于0..

最后两个都写了。yysy,题解的码风各异....花了好多时间大致统一出了一个比较好看的写法。

[HEOI2013] Segment
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN (int)(1e5+233)
#define MAXXK (39989)
#define MAXK (int)(4e4+233)
#define MAXY (int)(1e9+233)
const double EPS=1e-8;
using namespace std;
int OPT;

struct Seg
{
	int l,r;
	int id;
	double k,b;
};
int countt=0;

#define leftson cur<<1
#define rightson cur<<1|1
#define mid ((l+r)>>1)
Seg ans[MAXK<<2];
int rn=(int)(4e4);

inline double cal(Seg i,int x) { return i.k*x+i.b; }

Seg query(int cur,int l,int r,int qs)
{
	if (l==r) return ans[cur];
	Seg answ;
	if (qs<=mid)
		answ=query(leftson,l,mid,qs);
	else
		answ=query(rightson,mid+1,r,qs);
	return cal(answ,qs)-cal(ans[cur],qs)>EPS?answ:ans[cur]; 
}
void change(int cur,int l,int r,Seg k)
{
	if (k.l<=l&&r<=k.r)//线段完全覆盖才更新 
	{
		if (!ans[cur].id) { ans[cur]=k; return; }
		if (cal(k,mid)-cal(ans[cur],mid)>EPS) swap(ans[cur],k);//优势判断 
		if (cal(k,l)-cal(ans[cur],l)>EPS) change(leftson,l,mid,k);//优势线段右侧完全优先,用劣势更新左子树 
		if (cal(k,r)-cal(ans[cur],r)>EPS) change(rightson,mid+1,r,k);//优势线段左侧完全优先,用劣势更新右子树 
		return;
	}
	if (k.l<=mid) change(leftson,l,mid,k);
	if (k.r>mid) change(rightson,mid+1,r,k);
}
int main()
{
	scanf("%d",&OPT);
	int lastans=0,opt;
	int K,xx0,yy0,xx1,yy1;
	while (OPT--)
	{
		scanf("%d",&opt);
		if (!opt)
		{
			scanf("%d",&K);
			K=(K+lastans-1)%39989+1;
			printf("%d
",lastans=query(1,1,rn,K).id);
		}
		else
		{
			scanf("%d%d%d%d",&xx0,&yy0,&xx1,&yy1);
			xx0=(xx0+lastans-1)%39989+1;
			yy0=(yy0+lastans-1)%(int)(1e9)+1;
			xx1=(xx1+lastans-1)%39989+1;
			yy1=(yy1+lastans-1)%(int)(1e9)+1;
			if (xx0>xx1) swap(xx0,xx1),swap(yy0,yy1);
			countt++;
			Seg k=(Seg){xx0,xx1,countt,0,0};
			if (xx0==xx1) k.b=max(yy0,yy1);//垂直于x轴的线段 
			else k.k=((double)(yy1-yy0))/((double)(xx1-xx0)),k.b=(double)yy0-k.k*xx0;
			change(1,1,rn,k);
		}
	}
	return 0;
}
[JSOI2008] Blue Mary 开公司
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN (int)(1e5+233)
#define MAXT (int)(5e4+233)
const double EPS=1e-8;
const int rn=(int)(5e4);
using namespace std;
int tot=0;

int n;
struct Lin
{
	double k,b;
}a,ans[MAXT<<2];
inline double cal(Lin i,int x) { return i.k*x+i.b; }
#define leftson cur<<1
#define rightson cur<<1|1
#define mid ((l+r)>>1)
void change(int cur,int l,int r,Lin k)
{
	if (l==r)
	{
		if (cal(ans[cur],l)<cal(k,l)) ans[cur]=k;
		return;
	}
	if (!ans[cur].k) { ans[cur]=k; return; }
	if (cal(k,mid)-cal(ans[cur],mid)>EPS) swap(ans[cur],k);
	if (cal(k,l)-cal(ans[cur],l)>EPS) change(leftson,l,mid,k);
	if (cal(k,r)-cal(ans[cur],r)>EPS) change(rightson,mid+1,r,k);
}
double query(int cur,int l,int r,int qs)
{
	if (l==r) return cal(ans[cur],l);
	double answ=cal(ans[cur],qs);
	if (qs<=mid)
		answ=max(answ,query(leftson,l,mid,qs));
	else
		answ=max(answ,query(rightson,mid+1,r,qs));
	return answ; 
}
int main()
{
	scanf("%d",&n);
	string opt;
	int K;
	for (int i=1;i<=n;i++)
	{
		cin>>opt;
		if (opt[0]=='Q')
		{
			scanf("%d",&K);
			if (tot==0) printf("0
");
			else printf("%d
",(int)(query(1,1,rn,K)/100));
//			else printf("%lf
",(query(1,1,rn,K)/*100*/));
		}
		else
		{
			tot++;
			scanf("%lf%lf",&a.b,&a.k); a.b-=a.k;
			change(1,1,rn,a);
		}
	}
	return 0;
}

(tips:别for j to k 该opt j 还 operate i 了()

01分数规划

模型:给定一系列整数(a_1,a_2,...,a_n)(b_1,b_2,...,b_n),求解一组(x_i)(space (1 leq i leq n , x_i = 0 space or space 1))使得下式值最大化

[frac{Sigma_{i=1}^{n}a_i*x_i}{Sigma_{i=1}^{n}b_i*x_i} ]

解法:二分答案。把柿子拆一下

[frac{sum_{i=1}^{n}a_i*x_i}{sum_{i=1}^{n}b_i*x_i}geq mid ]

[sum_{i=1}^{n}a_i imes x_i-mid imes (sum_{i=1}^{n}b_i imes x_i) geq 0 ]

[sum_{i=1}^{n}(a_i-mid imes b_i) imes x_i geq 0 ]

那么就可以判断,若(a_i-mid*b_i < 0),就令(x_i=0),否则令(x_i=1)

(tips:const double EPS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1)

(tips:沃日,写到个SPFA判负环结果边权柿子是浮点数,int dis[]一发100 -> 30)

set/multiset

暂时没写总结啥的,先放个别人博客

除此还差bitset,list,平板电视之类的没学(

放个自己 AtCoder Beginner Contest 170 E - Smart Infants 的代码,在里面写使用注释算了(

AtCoder Beginner Contest 170 E - Smart Infants
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
#define MAXN (int)(2e5+233)
#define MAXQ (int)(2e5+233)
using namespace std;
int n,q;
int S[MAXN],a[MAXN];
multiset<int> st[MAXN],setans;

int main()
{
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i],&S[i]);
		st[S[i]].insert(a[i]);
	}
	for (int i=1;i<=(int)(2e5);i++)
		if (!st[i].empty()) setans.insert(*(--st[i].end()));//--st[i].end(),返回set st[i]末尾位置迭代器。set.end()是末尾+1。加星号是取了值(
	int c,d;
	while (q--)
	{
		scanf("%d%d",&c,&d);
		setans.erase(setans.find( *(--st[S[c]].end()) ));//find(x),找到元素x所在位置的迭代器。在multiset中,multiset.erase(x)会导致将所有值为x的元素删除,而erase(it)就删除迭代器it所在的元素。
		if (!st[d].empty()) setans.erase(setans.find(*(--st[d].end())));
		st[S[c]].erase(st[S[c]].find(a[c]));
		st[d].insert(a[c]);//set.insert(x),插入一个元素x。
		if (!st[S[c]].empty()) setans.insert(*(--st[S[c]].end()));
		setans.insert(*(--st[d].end()));
		printf("%d
",*setans.begin());
		S[c]=d;
	}
	return 0;
}

草,一个很奇怪的点()记得上考场前把校章摘下来QAQ————————————————————————————————————————————————————————————————————————————————————

By ❤千柒/Kan_kiz
原文地址:https://www.cnblogs.com/Kan-kiz/p/15272515.html