就我2018年暑假这阵子练过的区域赛题目来看
const int N=1e3+10;
const int M=2*N;
int tot,head[N];
void init(){
tot=0;memset(head,-1,sizeof head);
}
struct Edge{
int to,next;
}edge[M];
void addedge(int u,int v){
edge[tot].to=v;edge[tot].next=head[u];
head[u]=tot++;
}
bool vis[N];
int ind[N];
int que[N];
void topo(int root){
int q=0,p=0;//队列指针
que[q++]=root;
while(p
使用DAG的拓扑序求最短路
实际上就是BF的松弛操作,不过由于DAG的性质,可以降低复杂度到m。并且可以处理负边权。
int dist[N];
Rep(i,1,n)dist[i]=INF;
dist[s]=0;
for(int i=0;idist[u]+edge[j].w)dist[v]=dist[u]+edge[j].w;
}
}
前两个kuangbin 模板有
首先我们来考虑一下DAG的最小树形图
对于每个点来说,连通这个点的最小花费,就是找一条最小的边到这个点。
思考一下DAG和拓扑排序,按照拓扑序来考虑每一个点,其都能通过选择任何一条前驱边使这个点连通。所以我们选择最小的那条边。
所以就使用拓扑序遍历所有遍,然后去更新连通每个点的贡献
如果不是DAG的话,除非能缩点,否则只能用下面的板子了。
//来自kuangbin的最小树形图模版:
//UVA - 11183
//最小树形图 求有向图的最小生成树
//复杂度 O(VE)
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1000+10;
const int MAXM = 40000+10;
struct Edge{
//分别为起点,终点,花费
int u,v,cost;
};
Edge edge[MAXM];
//pre[i]表示i节点的入边的起点,in[i]表示该边的权值
int pre[MAXN],id[MAXN],visit[MAXN],in[MAXN];
//root为根节点,n为节点数量,m为边数量
int zhuliu(int root,int n,int m,Edge edge[]){
//最小树形图的总权值
int res=0,u,v;
while(1){
//找每个节点的最小入边
//初始化所有入边边权无穷大
for(int i=0;i
针对有向图的一个支配集,当然也不算支配集,
从这个点集合出发,可以到达有向图的任意一个点。称作为一个点基本
我们只要从所有的最高强连通分量中各选一个点组成集合就能组成 **最小点基 **
取权值最小的就是最小权点基
缩点后重新建边就可以了
dij mlogn
spfa mk
floyd n^3
Floyd求最小环
Floyd基于松弛的动态规划
针对每一个k去松弛i-j的路径。
for(k,1,n)for(i,1,n)for(j,1,n)if(可松弛)松弛
最小环为负就是有负环
const int MAXN = 110;
const int INF = 0xffffff0;
int temp,Map[MAXN][MAXN],Dist[MAXN][MAXN],pre[MAXN][MAXN],ans[MAXN*3];
void Solve(int i,int j,int k)
{
temp = 0; //回溯,存储最小环
while(i != j)
{
ans[temp++] = j;
j = pre[i][j];
}
ans[temp++] = i;
ans[temp++] = k;
}
void Floyd(int N)
{
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
{
Dist[i][j] = Map[i][j];
pre[i][j] = i;
}
int MinCircle = INF; //最小环
for(int k = 1; k <= N; ++k)
{
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(i != j && Dist[i][j] != INF && Map[i][k] != INF && Map[k][j] != INF
&& Dist[i][j] + Map[i][k] + Map[k][j] < MinCircle)
{
MinCircle = min(MinCircle, Dist[i][j] + Map[i][k] + Map[k][j]);
Solve(i,j,k); //回溯存储最小环
}
}
}
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(Dist[i][k] != INF && Dist[k][j] != INF &&
Dist[i][k] + Dist[k][j] < Dist[i][j])
{
Dist[i][j] = Dist[i][k] + Dist[k][j];
pre[i][j] = pre[k][j]; //记录点i到点j的路径上,j前边的点
}
}
}
}
if(MinCircle == INF) //不存在环
{
printf("No solution.\n");
return;
}
//如果求出最小环为负的,原图必定存在负环
for(int i = 0;i < temp; ++i) //输出最小环
if(i != temp-1)
printf("%d ",ans[i]);
else
printf("%d\n",ans[i]);
}
使用spfa+A*可以处理负边权
#include
#include
#include
using namespace std;
const int maxn=100010;
int n,m,dis[maxn];
int tot,head1[maxn],head2[maxn];
bool flag[maxn];
struct edge{
int to,next,w;
}e[maxn*2],e2[maxn*2];
struct node{
int from,f,g;
bool operator < (node rhs)const{
return rhs.f==f?g>rhs.g:f>rhs.f;
}
};
void init(){
memset(head1,-1,sizeof head1);
memset(head2,-1,sizeof head2);
tot=0;
memset(flag,false,sizeof flag);
}
void add_edge(int u,int v,int w)
{
e[tot].to=v;
e[tot].w=w;
e[tot].next=head1[u];
head1[u]=tot;
e2[tot].to=u;//建反图
e2[tot].w=w;
e2[tot].next=head2[v];
head2[v]=tot;
tot++;
}
void spfa(int t)//反图预处理dis
{
for(int i=1;i<=n;i++)dis[i]=maxn;
dis[t]=0;
queue q;q.push(t);
flag[t]=1;
while(!q.empty())
{
int v=q.front();
q.pop();flag[v]=0;
for(int i=head2[v];~i;i=e2[i].next)
if(dis[e2[i].to]>dis[v]+e2[i].w)
{
dis[e2[i].to]=dis[v]+e2[i].w;
if(!flag[e2[i].to])
{
q.push(e2[i].to);
flag[e2[i].to]=1;
}
}
}
}
int a_star(int s,int t,int k)
{
if(s==t) return 0;
if(dis[s]==maxn) return -1;
priority_queue q;
int cnt=0;
node tmp,to;
tmp.from=s;
tmp.g=0;
tmp.f=tmp.g+dis[tmp.from];
q.push(tmp);
while(!q.empty())
{
tmp=q.top();
q.pop();
if(tmp.from==t) cnt++;
if(cnt==k) return tmp.g;
for(int i=head1[tmp.from];i;i=e[i].next)
{
to.from=e[i].to;
to.g=tmp.g+e[i].w;
to.f=to.g+dis[to.from];
q.push(to);
}
}
return -1;
}
int main()
{
int x,y,z,s,t,k;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
add_edge(x,y,z);
}
cin>>s>>t>>k;//输入起点,终点,k短路
spfa(t);
int ans=a_star(s,t,k);
cout<
支配集,就是支配所有的点。
点覆盖,就是覆盖所有的边。
独立集,就是集合内所有点互相独立。
首先是深度优先遍历,得到遍历序列。代码如下:
int p[maxn];
bool select[maxn];
int newpos[maxn];
int now;
int n,m;
void DFS(int x)
{
newpos[now++]=x;
int k;
for(k=head[x];k!=-1;k=edge[k].next)
{
if(!select[edge[k].to])
{
select[edge[k].to]=true;
p[edge[k].to]=x;
DFS(edge[k].to);
}
}
}
对于最小支配集,贪心函数如下:
int greedy()
{
bool s[maxn];
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=0;i--)
{
int t=newpos[i];
if(!s[t])
{
if(!set[p[t]])
{
set[p[t]]=true;
ans++;
}
s[t]=true;
s[p[t]]=true;
s[p[p[t]]]=true;
}
}
return ans;
}
对于最小点覆盖,贪心函数如下:
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=1;i--)
{
int t=newpos[i];
if(!s[t]&&s[p[t]])
{
set[p[t]]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
return ans;
}
对于最大独立集,贪心函数如下:
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=0;i--)
{
int t=newpos[i];
if(!s[t])
{
set[t]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
return ans;
}
最小支配集:
void DP(int u,int p)
{
dp[u][2]=0;
dp[u][0]=1;
bool s=false;
int sum=0,inc=INF;
int k;
for(k=head[u];k!=-1;k=edge[k].next)
{
int to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],min(dp[u][1],dp[u][2]));
if(dp[to][0]<=dp[to][1])
{
sum+=dp[to][0];
s=true;
}
else
{
sum+=dp[to][1];
inc=min(inc,dp[to][0]-dp[to][1]);
}
if(dp[to][1]!=INF&&dp[u][2]!=INF)dp[u][2]+=dp[to][1];
else dp[u][2]=INF;
}
if(inc==INF&&!s)dp[u][1]=INF;
else
{
dp[u][1]=sum;
if(!s)dp[u][1]+=inc;
}
}
最小点覆盖:
void DP(int u,int p)
{
dp[u][0]=1;
dp[u][1]=0;
int k,to;
for(k=head[u];k!=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],dp[to][1]);
dp[u][1]+=dp[to][0];
}
}
最大独立集:
void DP(int u,int p)
{
dp[u][0]=1;
dp[u][1]=0;
int k,to;
for(k=head[u];k!=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=dp[u][1];
dp[u][1]+=max(dp[to][0],dp[to][1]);
}
}
参考kuangbin模板
匈牙利算法
#include
#include
#include
#include
#include
#include
#include
#include
int head[MAXN];
int gap[MAXN],dep[MAXN],pre[MAXN],cur[MAXN];
void init()
{
tol = 0;
memset(head,-1,sizeof(head));
}
//加边,单向图三个参数,双向图四个参数
void addedge(int u,int v,int w,int rw=0)
{
edge[tol].to =v;edge[tol].cap = w;edge[tol].next = head[u];
edge[tol].flow= 0;head[u] = tol++;
edge[tol].to =u;edge[tol].cap = rw;edge[tol].next = head[v];
edge[tol].flow= 0;head[v]=tol++;
}
//输入参数:起点、终点、点的总数
//点的编号没有影响,只要输入点的总数
int sap(int start,int end,int N)
{
memset(gap,0,sizeof(gap));
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
int u = start;
pre[u] = -1;
gap[0] = N;
int ans = 0;
while(dep[start] < N)
{
if(u == end)
{
int Min = INF;
for(int i = pre[u];i != -1; i = pre[edge[i^1].to])
if(Min > edge[i].cap - edge[i].flow)
Min = edge[i].cap - edge[i].flow;
for(int i = pre[u];i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
}
u = start;
ans += Min;
continue;
}
bool flag = false;
int v;
for(int i = cur[u]; i != -1;i = edge[i].next)
{
v = edge[i].to;
if(edge[i].cap - edge[i].flow && dep[v]+1 == dep[u])
{
flag = true;
cur[u] = pre[v] = i;
break;
}
}
if(flag)
{
u = v;
continue;
}
int Min = N;
for(int i = head[u]; i != -1;i = edge[i].next)
if(edge[i].cap - edge[i].flow && dep[edge[i].to] < Min)
{
Min = dep[edge[i].to];
cur[u] = i;
}
gap[dep[u]]--;
if(!gap[dep[u]])return ans;
dep[u] = Min+1;
gap[dep[u]]++;
if(u != start) u = edge[pre[u]^1].to;
}
return ans;
}
上下界网络流
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 110;
namespace ISAP {
const int MAXV = MAXN;
const int MAXE = ( MAXV*MAXV/2 + MAXV*2 )*3;
struct Edge {
int u, v, c, f;
Edge(){}
Edge( int u, int v, int c, int f ):
u(u),v(v),c(c),f(f){}
}edge[MAXE<<1];
int n, m, s, t, ss, tt;
int head[MAXV], nxt[MAXE<<1], eid[MAXE<<1], eidx;
void init( int n2, int ss2, int tt2 ) { // 初始化,设置附加源和附加汇
n = n2; ss = ss2; tt = tt2;
m = eidx = 0;
memset( head, -1, sizeof(head) );
}
int adde( int u, int v, int c ) { // 添加一条只有上界的边
int rtn = m;
eid[eidx] = m; nxt[eidx] = head[u]; head[u] = eidx++;
edge[m++] = Edge(u,v,c,0);
eid[eidx] = m; nxt[eidx] = head[v]; head[v] = eidx++;
edge[m++] = Edge(v,u,0,0);
return rtn;
}
int adde2( int u, int v, int b, int c ) { // 添加一条有上下界的边,返回边的下标
int rtn = adde(u,v,c-b);
adde(ss,v,b);
adde(u,tt,b);
return rtn;
}
// 以下ISAP板子
int prev[MAXV], dist[MAXV], num[MAXV], cur[MAXV], res[MAXV];
queue bfsq;
void bfs() {
for( int i = 1; i <= n; ++i ) dist[i] = n;
dist[t] = 0; bfsq.push(t);
while( !bfsq.empty() ) {
int u = bfsq.front(); bfsq.pop();
for( int i = head[u]; ~i; i = nxt[i] ) {
Edge &e = edge[eid[i]];
if( dist[e.v] == n ) {
dist[e.v] = dist[u] + 1;
bfsq.push(e.v);
}
}
}
}
void augment() {
int u = t, flow = res[t];
while( u != s ) {
int i = prev[u];
edge[i].f += flow;
edge[i^1].f -= flow;
u = edge[i].u;
}
}
bool advance( int &u ) {
for( int i = cur[u]; ~i; i = nxt[i] ) {
Edge &e = edge[eid[i]];
if( e.c > e.f && dist[e.v] + 1 == dist[u] ) {
prev[e.v] = cur[u] = i;
res[e.v] = min( res[u], e.c - e.f );
u = e.v;
return true;
}
}
return false;
}
bool retreat( int &u ) {
if( --num[dist[u]] == 0 ) return false;
int newd = n;
for( int i = head[u]; ~i; i = nxt[i] ) {
Edge &e = edge[eid[i]];
if( e.c > e.f ) newd = min( newd, dist[e.v] + 1 );
}
++num[ dist[u] = newd ];
cur[u] = head[u];
if( u != s ) u = edge[prev[u]].u;
return true;
}
int solve( int s2, int t2 ) { // 以s2为源,t2为汇跑最大流
s = s2; t = t2;
bfs();
for( int i = 1; i <= n; ++i )
cur[i] = head[i], ++num[dist[i]];
int u = s, flow = 0;
res[s] = INF;
while( dist[s] < n ) {
if( u == t ) {
augment();
flow += res[t];
u = s;
}
if( !advance(u) )
if( !retreat(u) )
break;
}
return flow;
}
}
int n, s, t, ss, tt; // 点的个数,源,汇,附加源,附加汇
namespace Solve {
using ISAP::head;
using ISAP::nxt;
using ISAP::eid;
using ISAP::Edge;
using ISAP::edge;
bool first;
void dfs( int u ) { // dfs输出方案
// printf( "Debug: u = %d\n", u );
if( !first ) putchar(' ');
first = false;
printf( "%d", u );
for( int i = head[u]; ~i; i = nxt[i] ) {
Edge &e = edge[eid[i]];
if( e.v <= n && e.f > 0 ) { // 任选一条边走下去
// printf( "going eid = %d, from %d to %d, flow_left = %d\n", eid[i], e.u, e.v, e.f );
--e.f;
dfs(e.v);
return;
}
}
}
void addbound() { // 把每条边流量加上下界,恢复成原图的样子,方便输出方案
using ISAP::m;
for( int i = 0; i < m; ++i ) {
Edge &e = edge[eid[i]];
if( e.u <= n && e.v <= n && e.c > 0 )
++e.f;
}
}
}
namespace Debug { // 调试用QwQ
void print_flow() {
using ISAP::edge;
using ISAP::Edge;
using ISAP::eid;
using ISAP::m;
for( int i = 0; i < m; ++i ) {
Edge &e = edge[eid[i]];
if( e.u <= n && e.v <= n && e.c > 0 )
printf( "eid = %d, from %d to %d, flow = %d\n", eid[i], e.u, e.v, e.f );
}
}
void print_flow2() {
using ISAP::edge;
using ISAP::Edge;
using ISAP::eid;
using ISAP::m;
for( int i = 0; i < m; ++i ) {
Edge &e = edge[eid[i]];
if( e.f > 0 )
printf( "eid = %d, from %d to %d, flow = %d\n", eid[i], e.u, e.v, e.f );
}
}
}
int main() {
while( scanf( "%d", &n ) == 1 ) {
s = n+1, t = n+2, ss = n+3, tt = n+4;
ISAP::init(tt,ss,tt);
for( int i = 1; i <= n; ++i ) {
int mi; scanf( "%d", &mi );
while( mi-- ) {
int v; scanf( "%d", &v );
ISAP::adde2(i,v,1,INF);
}
ISAP::adde2(s,i,0,INF);
ISAP::adde2(i,t,0,INF);
}
int flow1 = ISAP::solve(ss,tt);
// printf( "flow1 = %d\n", flow1 );
// Debug::print_flow();
// Debug::print_flow2();
int tsedge = ISAP::adde2(t,s,0,INF); // 存储弧的信息,调试用QwQ
int ans = ISAP::solve(ss,tt);
// printf( "t_s flow = %d\n", ISAP::edge[tsedge].f );
// Debug::print_flow();
// Debug::print_flow2();
printf( "%d\n", ans );
Solve::addbound(); // 把每条图中的边流量加上下界,恢复成原图的样子,方便输出方案
while( ans ) {
using namespace Solve;
for( int i = head[s]; ~i; i = nxt[i] ) {
Edge &e = edge[eid[i]];
if( e.v <= n && e.f > 0 ) { // 任选一个点dfs,输出方案
first = true;
--e.f;
--ans;
dfs(e.v);
putchar('\n');
}
}
}
}
return 0;
}
原理就是一直增广 使用spfa(因为有负边权)找到的s->t的一条可行最短路,直到不存在最短路了。可以证明这样形成的最大流费用是最小的。
最大费用修改spfa或者直接建负边权即可。
#include
/*
支持重边,所以边数自己定,最大费用改成负权即可。
*/
using namespace std;
const int MAXN=1e4+10;
const int MAXM=2e5+10;
const int INF=0x3f3f3f3f;
struct Edge{
int to,next,cap,flow,cost;
}edge[MAXM];
int tol,N;
int head[MAXN],pre[MAXN],dis[MAXN];
bool vis[MAXN];
void init(int n){
N=n;
tol=0;
memset(head,-1,sizeof head);
}
void addedge(int u,int v,int cap,int cost){
Edge &e=edge[tol];
e.to=v;
e.cap=cap;
e.cost=cost;
e.flow=0;
e.next=head[u];
head[u]=tol++;
Edge &ee=edge[tol];
ee.to=u;
ee.cap=0;
ee.cost=-cost;
ee.flow=0;
ee.next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t){
//初始化
queue q;
for(int i=0;i<=N;i++)
dis[i]=INF,vis[i]=false,pre[i]=-1;
dis[s]=0;vis[s]=true;q.push(s);
//松弛,没有判断负环,判负环用cnt[N];
while(!q.empty()){
int u=q.front();q.pop();vis[u]=false;
for(int i=head[u];~i;i=edge[i].next){
Edge &e=edge[i];int v=e.to;
if(e.cap>e.flow && dis[v]>dis[u]+e.cost){
dis[v]=dis[u]+e.cost;
pre[v]=i;
if(!vis[v]){
vis[v]=true;
q.push(v);
}
}
}
}
if(pre[t]==-1)return false;
else return true;
}
void mcmf(int s,int t,int &cost,int &flow){
flow=0;cost=0;
//找一条费用最小的可行流。增广之。
while(spfa(s,t)){
int Min=INF;
for(int i=pre[t];~i;i=pre[edge[i^1].to])if(Min>edge[i].cap-edge[i].flow){
Min=edge[i].cap-edge[i].flow;
}
//这里i 是边(u,v)的下标,让edge[i^1].to=u
for(int i=pre[t];~i;i=pre[edge[i^1].to]){
//debug
//cout<<"pre="<