目录(未完善)
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;
}
线段树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))使得下式值最大化
解法:二分答案。把柿子拆一下
那么就可以判断,若(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————————————————————————————————————————————————————————————————————————————————————