模板和學習筆記
-----------------------------------
點分治
一般步驟:
1.找到樹的重心(避免當樹退化成鏈時複雜度升高)
2.從重心出發分治統計路徑
分治過程:
統計當前節點子樹中的符合條件的路徑數加到ans中
標記當前點避免重複
減去當前節點子樹節點中的符合條件的路徑數(這樣剩下的就是經過當前節點的路徑數,不會重複了)
在當前節點的子樹中分別找到重心繼續分治解決
bzoj1468
[點分治例題]bzoj2599#include<cstdio> #include<algorithm> #include<vector> #define INF 2e9 #define N 40005 using namespace std; struct edge{int to,v;}; vector<edge>e[N]; int n,sum,root,k; int son[N],f[N],vis[N],deep[N],c[N],ans; void getroot(int x,int fa)//找重心 { son[x]=1,f[x]=0; for(int i=0;i<e[x].size();++i) { int y=e[x][i].to; if(y==fa||vis[y]) continue; getroot(y,x); son[x]+=son[y]; f[x]=max(f[x],son[y]); } f[x]=max(f[x],sum-son[x]);//若以當前點為根那還要考慮以當前點的父節點為根的子樹 if(f[x]<f[root]) root=x; } void getdis(int x,int fa)//計算深度 { ++c[0];c[c[0]]=deep[x]; for(int i=0;i<e[x].size();++i) { edge t=e[x][i]; if(t.to==fa||vis[t.to]) continue; deep[t.to]=deep[x]+t.v; getdis(t.to,x); } } int calc(int x,int v)//統計答案 { deep[x]=v;c[0]=0; getdis(x,0); sort(c+1,c+c[0]+1); int l=1,r=c[0]; LL res=0; while(l<r) if(c[l]+c[r]<=k) res+=r-l,++l; else --r; return res; } void solve(int x) { ans+=calc(x,0);vis[x]=1;//加上x節點及其子樹中所有的符合條件的路徑數 for(int i=0;i<e[x].size();++i) { edge t=e[x][i]; if(vis[t.to]) continue; ans-=calc(t.to,t.v);//顯然上面會加入一些非簡單路徑,就減去不經過x節點的那些會被統計到答案中的路徑數 sum=son[t.to]; root=0; getroot(t.to,0); solve(root); } } int main() { scanf("%d",&n); for(int i=1,x,y,v;i<n;++i) { scanf("%d %d %d",&x,&y,&v); e[x].push_back((edge){y,v}); e[y].push_back((edge){x,v}); } scanf("%d",&k); root=ans=0,sum=n,f[0]=INF; getroot(1,0); solve(root); printf("%d\n",ans); return 0; }
-----------------------------------
網路流(dinic)模板
#include<cstdio> #include<algorithm> #include<cstring> #include<vector> #include<queue> #define N 100005 #define INF 2e9 using namespace std; struct edge{int to,v;}; vector<edge>e; vector<int>G[N]; int n,m,ss,tt,d[N],cur[N<<1]; inline char nc(){ static char buf[100000],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } inline int read(){ int a=0;char f=1,c=nc(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();} while(c>='0'&&c<='9'){a=a*10+c-'0';c=nc();} return a*f; } inline void add(int x,int y,int v){ e.push_back((edge){y,v}); G[x].push_back(e.size()-1); e.push_back((edge){x,0}); G[y].push_back(e.size()-1); } bool bfs(){ queue<int>q; memset(d,-1,sizeof(d)); q.push(ss),d[ss]=0; while(!q.empty()){ int u=q.front();q.pop(); if(u==tt) return 1; for(int i=0;i<G[u].size();++i){ edge v=e[G[u][i]]; if(d[v.to]==-1&&v.v) d[v.to]=d[u]+1,q.push(v.to); } } return 0; } int dfs(int x,int a){ if(x==tt||!a) return a; int flow=0,f; for(int &i=cur[x];i<G[x].size();++i){ int t=G[x][i]; if(d[e[t].to]==d[x]+1&&(f=dfs(e[t].to,min(a,e[t].v)))>0){ e[t].v-=f; e[t^1].v+=f; flow+=f; a-=f; if(!a) break; } } if(!flow) d[x]=-1; return flow; } inline int dinic(){ int res=0; while(bfs()){ memset(cur,0,sizeof(cur)); res+=dfs(ss,INF); } return res; } int main(){ n=read(),m=read(),ss=read(),tt=read(); for(int i=1;i<=m;++i){ int x=read(),y=read(),v=read(); add(x,y,v); } printf("%d\n",dinic()); return 0; }
-----------------------------------
上下界網路流
神犇的總結 //看不懂我寫的還是參考這一位的免得浪費時間,畢竟我語文水平有限而且本意也只是自己隨便寫點以後自己能看懂就夠了
可以分為這麼幾類:
1.無源匯有上下界可行流
使所有邊都先擁有等於下界的流量得到初始流,用一個a陣列表示每一個點在初始流中的流入與流出的相對情況(a[i]>0則這個節點初始的 流入量>流出量)
建立超級源匯ss,tt
要給a[i]>0的點的流量找個來處(ss向i連容量a[i]的邊),給a[i]<0的流量找個去處(i向tt連容量為-a[i]的邊(ss,tt是為了和s,t區分,前者為超級源匯,後者為實際的源匯)
構建出了新的網路後跑ss到tt的最大流,一定要最大流等於新網路中ss可以流出的總流量才能滿足流量守恆
每條邊在可行流中的流量=容量下界+附加流中它的流量(即跑完dinic之後所加反向邊的權值)
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define INF 2e9
#define N 205
#define M 10201
using namespace std;
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
struct edge{int to,v,next,num;}e[M*3];
int n,m,cnt=1,s,t,sum=0;
int head[N],a[N],d[N],low[M],cur[N],ans[M];
void add(int x,int y,int v,int num)
{
e[++cnt].next=head[x],e[cnt].to=y,e[cnt].v=v,e[cnt].num=num;
head[x]=cnt;
e[++cnt].next=head[y],e[cnt].to=x,e[cnt].v=0,e[cnt].num=num;
head[y]=cnt;
}
int bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(s);d[s]=0;
while(!q.empty())
{
int x=q.front();q.pop();
if(x==t) return 1;
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if(d[y]==-1&&e[i].v)
{
q.push(y);
d[y]=d[x]+1;
}
}
}
return 0;
}
int dfs(int x,int a)
{
if(x==t||!a) return a;
int flow=0,f;
for(int &i=cur[x];i;i=e[i].next)
if(d[e[i].to]==d[x]+1&&(f=dfs(e[i].to,min(a,e[i].v)))>0)
{
e[i].v-=f;
e[i^1].v+=f;
flow+=f;
a-=f;
if(!a) break;
}
if(!flow) d[x]=-1;
return flow;
}
int dinic()
{
int res=0;
while(bfs())
{
for(int i=s;i<=t;++i)
cur[i]=head[i];
res+=dfs(s,INF);
}
return res;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;++i)
{
int x,y,h;
scanf("%d %d %d %d",&x,&y,&low[i],&h);
add(x,y,h-low[i],i);
a[x]-=low[i],a[y]+=low[i];
}
s=0,t=n+1;
for(int i=1;i<=n;++i)
if(a[i]>0)
sum+=a[i],add(s,i,a[i],0);
else
add(i,t,-a[i],0);
if(dinic()==sum)
{
printf("YES\n");
for(int i=1;i<=n;++i)
for(int j=head[i];j;j=e[j].next)
if(j%2&&e[j].num)
ans[e[j].num]=low[e[j].num]+e[j].v;
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
}
else printf("NO\n");
return 0;
}
2.有源匯有上下界可行流
1的建圖之後再從t向s連一條下界為0上界為正無窮的邊,可行流即為t到s反向邊中的流量
3.有源匯有上下界最大流
求出可行流,顯然可行流不一定是最大流
在剩下的網路中再跑s-t的最大流
最終的最大流流量=可行流流量(即t到s的無窮邊上跑出的流量)+新增廣出的s-t流量
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define INF 2e9
#define N 205
#define M 10000
using namespace std;
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
struct edge{int next,to,v;}e[M*3];
int n,m,sum,s,t,cnt,ss,tt;
int a[N],head[N],cur[N],d[N];
void add(int x,int y,int v)
{
e[cnt].next=head[x],e[cnt].to=y,e[cnt].v=v;
head[x]=cnt++;
e[cnt].next=head[y],e[cnt].to=x,e[cnt].v=0;
head[y]=cnt++;
}
int bfs()
{
memset(d,-1,sizeof(d));
queue<int>q;
q.push(ss),d[ss]=0;
while(!q.empty())
{
int x=q.front();q.pop();
if(x==tt) return 1;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(d[y]==-1&&e[i].v)
{
q.push(y);
d[y]=d[x]+1;
}
}
}
return 0;
}
int dfs(int x,int a)
{
if(x==tt||!a) return a;
int flow=0,f;
for(int &i=cur[x];i!=-1;i=e[i].next)
if(d[e[i].to]==d[x]+1&&(f=dfs(e[i].to,min(a,e[i].v)))>0)
{
e[i].v-=f;
e[i^1].v+=f;
flow+=f;
a-=f;
if(!a) break;
}
if(!flow) d[x]=-1;
return flow;
}
int dinic()
{
int res=0;
while(bfs())
{
for(int i=0;i<=n+1;++i) cur[i]=head[i];
res+=dfs(ss,INF);
}
return res;
}
int main()
{
n=read(),m=read(),s=read(),t=read();
memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),l=read(),r=read();
add(x,y,r-l),
a[x]-=l,a[y]+=l;
}
ss=0,tt=n+1;
for(int i=1;i<=n;++i)
if(a[i]>0)
sum+=a[i],add(ss,i,a[i]);
else if(a[i]<0) add(i,tt,-a[i]);
add(t,s,INF);
sum-=dinic();
if(sum) printf("please go home to sleep\n");
else
{
sum=e[head[t]^1].v;
head[s]=e[head[s]].next,head[t]=e[head[t]].next;
ss=s,tt=t;
printf("%d\n",sum+dinic());
}
return 0;
}
4.有源匯有上下界最小流
與有源匯有上下界最大流類似
求出可行流後在殘餘網路中跑t到s的最大流
最終的最大流流量=可行流流量(即t到s的無窮邊上跑出的流量)-新增廣出的t-s的流量
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<queue>
#define INF 2e9
#define N 50005
#define M 125005
using namespace std;
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
struct edge{int to,next,v;}e[M*3];
int n,m,cnt,sum,s,t,ss,tt;
int a[N],d[N],head[N],cur[N];
void add(int x,int y,int v)
{
e[cnt].next=head[x],e[cnt].to=y,e[cnt].v=v;
head[x]=cnt++;
e[cnt].next=head[y],e[cnt].to=x,e[cnt].v=0;
head[y]=cnt++;
}
int bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(ss),d[ss]=0;
while(!q.empty())
{
int x=q.front();q.pop();
if(x==tt)
{
memcpy(cur,head,sizeof(cur));
return 1;
}
for(int i=head[x];i!=-1;i=e[i].next)
if(d[e[i].to]==-1&&e[i].v)
{
d[e[i].to]=d[x]+1;
q.push(e[i].to);
}
}
return 0;
}
int dfs(int x,int a)
{
if(x==tt||!a) return a;
int flow=0,f;
for(int &i=cur[x];i!=-1;i=e[i].next)
if(d[e[i].to]==d[x]+1&&(f=dfs(e[i].to,min(a,e[i].v)))>0)
{
e[i].v-=f;
e[i^1].v+=f;
flow+=f;
a-=f;
if(!a) break;
}
if(!flow) d[x]=-1;
return flow;
}
int dinic()
{
int res=0;
while(bfs())
{
// for(int i=0;i<=n+1;++i) cur[i]=head[i];
res+=dfs(ss,INF);
}
return res;
}
int main()
{
n=read(),m=read(),s=read(),t=read();
memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),l=read(),r=read();
a[x]-=l,a[y]+=l;
add(x,y,r-l);
}
ss=0,tt=n+1;
for(int i=1;i<=n;++i)
if(a[i]>0)
sum+=a[i],add(ss,i,a[i]);
else if(a[i]<0)
add(i,tt,-a[i]);
add(t,s,INF);
sum-=dinic();
if(sum) printf("please go home to sleep\n");
else
{
sum=e[head[t]^1].v;
head[s]=e[head[s]].next,head[t]=e[head[t]].next;
ss=t,tt=s;
printf("%d\n",sum-dinic());
}
return 0;
}
-----------------------------------
最小費用最大流
有源匯的最大流一定是一定的,即便流的分配方案不同。
將弧的費用看做長度做最短路,不斷找路徑長度小的增廣路,當無法增廣時,即是最大流且此時費用最小
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#define N 405
#define M 15005
using namespace std;
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
struct edge{int next,to,v,c;}e[M*2];
int n,m,s,t,cnt;
int head[N],dis[N],from[N],pre[M*2],v[N];
void add(int x,int y,int v,int c)
{
e[cnt].next=head[x],e[cnt].to=y,e[cnt].v=v,e[cnt].c=c;
head[x]=cnt++;
e[cnt].next=head[y],e[cnt].to=x,e[cnt].v=0,e[cnt].c=-c;
head[y]=cnt++;
}
int spfa()
{
memset(from,-1,sizeof(from));
memset(dis,127,sizeof(dis));
queue<int>q;
q.push(s),dis[s]=0;
while(!q.empty())
{
int x=q.front();q.pop();
v[x]=0;
for(int i=head[x];i!=-1;i=e[i].next)
{
if(e[i].v&&dis[e[i].to]>dis[x]+e[i].c)
{
dis[e[i].to]=dis[x]+e[i].c;
from[e[i].to]=x,pre[e[i].to]=i;
if(!v[e[i].to])
v[e[i].to]=1,q.push(e[i].to);
}
}
}
return from[t]!=-1;
}
int main()
{
n=read(),m=read();
s=1,t=n;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),v=read(),c=read();
add(x,y,v,c);
}
int flow=0,cost=0;
while(spfa())
{
int f=2e9;
for(int i=t;i!=s;i=from[i])
f=min(f,e[pre[i]].v);
flow+=f,cost+=f*dis[t];
for(int i=t;i!=s;i=from[i])
e[pre[i]].v-=f,e[pre[i]^1].v+=f;
}
printf("%d %d\n",flow,cost);
return 0;
}
-----------------------------------
AC自動機
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define N 1000010
using namespace std;
struct node{
int fail,sum,lk[26];//有些地方賦值為0最好還是寫一下,因為有些時候初始化指向根不為0
}AC[N]; //的話容易疏漏
int n,size;
char s[N];
inline void insert(char *s){ //就是tire樹的插入
int len=strlen(s+1),nw=0;
for(int i=1,t;i<=len;++i){
t=s[i]-'a';
if(!AC[nw].lk[t]) AC[nw].lk[t]=++size;
nw=AC[nw].lk[t];
}
++AC[nw].sum;
return;
}
void build(){
queue<int>q;
for(int i=0;i<26;++i)
if(AC[0].lk[i]) AC[AC[0].lk[i]].fail=0,q.push(AC[0].lk[i]);//第一層的fail指標都指向根,順便進行第一次bfs的擴充套件
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<26;++i)
if(AC[x].lk[i]!=0){//存在這個子節點,直接把fail指標指向父節點fail指向的節點的這個子節點
AC[AC[x].lk[i]].fail=AC[AC[x].fail].lk[i];
q.push(AC[x].lk[i]);
}
else AC[x].lk[i]=AC[AC[x].fail].lk[i];//否則直接接過去,因為是bfs擴充套件,總會接到有這個子節點的那一個節點
}
return;
}
int ask(char *s){
int len=strlen(s+1);
int nw=0,ans=0;
for(int i=1;i<=len;++i){
nw=AC[nw].lk[s[i]-'a'];
int nxt=nw;
while(nxt&&AC[nxt].sum!=-1){ //按fail指標查詢
ans+=AC[nxt].sum;
AC[nxt].sum=-1;
nxt=AC[nxt].fail;
}
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%s",s+1),insert(s);
AC[0].fail=0;
build();
scanf("%s",s+1);
printf("%d\n",ask(s));
return 0;
}