很明显的一道最大流匹配题目
唯一要注意的点是题目要求是一头牛只能搭配一个饮料和事物
所以要拆点
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=505;
const int maxm=200000;
int N,F,D,cnt,S,T;
int head[maxm],num[maxn],maxflow;
struct node{
int to,next,w;
}edg[maxm];
void add(int u,int v,int w){
++cnt;
edg[cnt].to=v;
edg[cnt].w=w;
edg[cnt].next=head[u];
head[u]=cnt;
}
queue<int>Q;
bool bfs(){
while(!Q.empty()){
Q.pop();
}
for(int i=S;i<=T+N;i++)num[i]=-1;
num[S]=1;
Q.push(S);
while(!Q.empty()){
int now=Q.front();Q.pop();
for(int i=head[now];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&num[to]==-1){
num[to]=num[now]+1;
Q.push(to);
}
}
}
return (num[T]!=-1);
}
int dfs(int u,int f){
if(u==T){
return f;
}
int flow;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&num[to]==num[u]+1&&(flow=dfs(to,min(f,w)))){
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
void dinic(){
int minn;
while(bfs()){
while(minn=dfs(S,INF)){
maxflow+=minn;
}
}
}
int main(){
scanf("%d%d%d",&N,&F,&D);
cnt++;
S=1,T=1+N+F+D+1;
for(int i=1;i<=F;i++)add(S,i+1,1),add(i+1,S,0);
for(int i=1;i<=D;i++)add(i+1+F+N,T,1),add(T,i+1+F+N,0);
for(int i=1;i<=N;i++)add(1+F+i,1+F+N+D+1+i,1),add(1+F+N+D+i+1,1+F+i,0);
for(int i=1;i<=N;i++){
int ff,dd;
scanf("%d%d",&ff,&dd);
for(int t,j=1;j<=ff;j++){
scanf("%d",&t);
add(t+1,1+F+i,1);
add(1+F+i,t+1,0);
}
for(int t,j=1;j<=dd;j++){
scanf("%d",&t);
add(1+F+N+D+1+i,1+F+N+t,1);
add(1+F+N+t,1+F+N+D+1+i,0);
}
}
dinic();
printf("%d\n",maxflow);
return 0;
}
其实网络流的题目还是很明显的
你读完题目就知道这个是网络流的题目
建边
超源点S连接每个试题,因为每个试题只能选一次,所以边权为1
每个试题连接多个不同的类型,因为每个试题只能代表一种类型,所以边权为1
每个类型连接超汇点T,因为每个类型有不同的要求数量,所以边权具体题目给出
输出
这个题关键就是输出
记得dinic算法是建立了反向边的,所以只要该类型通向试题的反向边权大于0,就可以输出
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=2e3+5;
const int maxm=1e6+5;
int K,N,M,S,T,ans,cnt;
int head[maxm],num[maxn],vis[maxn];
queue<int>Q;
struct node{
int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
++cnt;
edg[cnt].to=v;
edg[cnt].w=w;
edg[cnt].next=head[u];
head[u]=cnt;
}
bool bfs(){
while(!Q.empty()){
Q.pop();
}
for(int i=S;i<=T;i++)vis[i]=-1;
vis[S]=1;
Q.push(S);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&vis[to]==-1){
vis[to]=vis[u]+1;
Q.push(to);
}
}
}
return (vis[T]!=-1);
}
int dfs(int u,int f){
if(u==T)return f;
int flow=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&vis[to]==vis[u]+1&&(flow=dfs(to,min(w,f)))){
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
void dinic(){
while(bfs()){
int ff;
while(ff=dfs(S,INF)){
ans+=ff;
}
}
}
void print(int u){
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w>0&&to<=S+N)
printf(" %d",to-S);
}
}
int main(){
scanf("%d%d",&K,&N);
++cnt;S=1;T=1+N+K+1;
for(int i=1;i<=K;i++){
scanf("%d",&num[i]),M+=num[i];
add(S+N+i,T,num[i]);
add(T,S+N+i,0);
}
for(int i=1;i<=N;i++)add(S,S+i,1),add(S+i,S,0);
for(int i=1;i<=N;i++){
int p;scanf("%d",&p);
while(p--){
int t;scanf("%d",&t);
add(S+i,S+N+t,1);
add(S+N+t,S+i,0);
}
}
dinic();
if(ans<M){
printf("No Solution!\n");
return 0;
}
else {
for(int i=1;i<=K;i++){
printf("%d:",i);
print(i+S+N);
printf("\n");
}
}
return 0;
}
分析
很好能get到这个题是最小割的题目
关键的点就是在于朋友之间建边要双向建边,一开始我一直没想明白,做题的时候也很难想到
那就说明对最小割的理解还不够深刻
一般我们建立S->T的单向边是因为只有S流向T,而这个题不一样
尽管A和B两人的意见不一样,那可以B妥协A,A可以妥协B,就是说
S可以流向T,T同样可以流向S
点击查看代码
#include<bits/stdc++.h>
#define il inline
using namespace std;
const int N=100005,inf=23333333;
int n,m,s,t=520,h[N],cnt=1,dis[N],ans;
struct edge{
int to,net,v;
}e[N*4];
il void add(int u,int v,int w)
{
e[++cnt].to=v,e[cnt].net=h[u],e[cnt].v=w,h[u]=cnt;
e[++cnt].to=u,e[cnt].net=h[v],e[cnt].v=0,h[v]=cnt;
}
queue<int>q;
il bool bfs()
{
memset(dis,-1,sizeof(dis));
q.push(s),dis[s]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].net)
if(dis[e[i].to]==-1&&e[i].v>0)dis[e[i].to]=dis[u]+1,q.push(e[i].to);
}
return dis[t]!=-1;
}
il int dfs(int u,int op)
{
if(u==t)return op;
int used=0;
for(int i=h[u];i;i=e[i].net)
{
int v=e[i].to;
if(dis[v]==dis[u]+1&&e[i].v>0)
{
used=dfs(v,min(op,e[i].v));
if(!used)continue;
e[i].v-=used,e[i^1].v+=used;
return used;
}
}
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x==1)add(s,i,1);
else add(i,t,1);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y,1),add(y,x,1);
}
while(bfs())ans+=dfs(s,inf);
cout<<ans;
return 0;
}
这是最小割的经典题目类型
最大权闭合图
建图:
S与每个实验相连,边权为资金费
T与每个仪器相连,边权为消耗费用(相当于已经取绝对值了)
每个实验与相应的仪器相连,边权设为无穷大(因为割边一定不能割实验与仪器的边)
最后就是跑一遍dinic最大流
最大流=最小割
最大权闭合子图的权值和 = max{被选择的点权和} = 正点权和−min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和−最小割
最后一个要解决的问题就是要输出这个最大权闭合图
考虑最后一次bfs,能够到达的点就是在该图内
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define inf 0x7fffffff
#define ll long long
const int maxn=155;
const int maxm=1e6;
int S,T,M,N,cnt,tot,ans;
int head[maxn],dp[maxn];
struct node{
int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
edg[++cnt].next=head[u];edg[cnt].to=v;edg[cnt].w=w;head[u]=cnt;
edg[++cnt].next=head[v];edg[cnt].to=u;edg[cnt].w=0;head[v]=cnt;
}
queue<int>Q;
bool bfs(){
while(!Q.empty()){
Q.pop();
}
for(int i=S;i<=T;i++)dp[i]=-1;
dp[S]=1;
Q.push(S);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==-1){
dp[to]=dp[u]+1;
Q.push(to);
}
}
}
return dp[T]!=-1;
}
int dfs(int u,int f){
if(u==T)return f;
int flow=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==dp[u]+1&&(flow=dfs(to,min(f,w)))){
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
void dinic(){
while(bfs()){
int minn;
while(minn=dfs(S,inf)){
ans+=minn;
}
}
}
int main(){
++cnt;
S=0;T=150;
scanf("%d%d",&M,&N);
for (int i = 1,c; i <= M; i++) {
scanf("%d", &c), tot += c;
add(S, i, c);
while (getchar() == ' ') {
scanf("%d", &c);
add(i, c + M, inf);
}
}
for(int i=1,c;i<=N;i++){
scanf("%d",&c);
add(i+M,T,c);
}
dinic();
for (int i = 1; i <= M; i++) if (dp[i]!=-1) cout << i << ' '; puts("");
for (int i = 1; i <= N; i++) if (dp[i + M]!=-1) cout << i << ' '; puts("");
printf("%d\n",tot-ans);
return 0;
}
又是一道最大流模板
因为每个人只能选一个房间,所以对每个人进行拆点,最后套个dinic模板即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define open(s) freopen( s".in", "r", stdin ), freopen( s".out", "w", stdout )
#define MAXN 405
#define MAXM 40005
int n, p, q;
int hd[MAXN], nxt[MAXM << 1], to[MAXM << 1], val[MAXM << 1], tot(1);
int ans, dis[MAXN];
queue<int> Q;
int x, y;
int S, T;
void Add( int x, int y, int z ){ nxt[++tot] = hd[x]; hd[x] = tot; to[tot] = y; val[tot] = z; }
bool BFS(){
while( !Q.empty() ) Q.pop();
memset( dis, 0, sizeof dis );
Q.push(S); dis[S] = 1;
while( !Q.empty() ){
x = Q.front(); Q.pop();
for ( int i = hd[x]; i; i = nxt[i] )
if ( val[i] && !dis[to[i]] ){
dis[to[i]] = dis[x] + 1;
Q.push( to[i] );
if ( to[i] == T ) return 1;
}
}
return 0;
}
int DFS( int x, int fl ){
if ( x == T ) return fl;
int k;
for ( int i = hd[x]; i ; i = nxt[i] ){
if ( val[i] && dis[to[i]] == dis[x] + 1 ){
k = DFS( to[i], min( fl, val[i] ) );
if(!k)continue;
val[i] -= k; val[i^1] += k;
return k;
}
}
return 0;
}
int main(){
scanf( "%d%d%d", &n, &p, &q );
S = 0; T = 1 + n + n + p + q;
for ( int i = 1; i <= n; ++i ) Add( i, i + n, 1 ), Add( i + n, i, 0 );
for ( int i = 1; i <= p; ++i ) Add( S, i + n + n, 1 ), Add( i + n + n, S, 0 );
for ( int i = 1; i <= q; ++i ) Add( i + n + n + p, T, 1 ), Add( T, i + n + n + p, 0 );
for ( int i = 1; i <= n; ++i )
for ( int j = 1; j <= p; ++j ){
int t; scanf( "%d", &t );
if ( t ) Add( j + n + n, i, 1 ), Add( i, j + n + n, 0 );
}
for ( int i = 1; i <= n; ++i )
for ( int j = 1; j <= q; ++j ){
int t; scanf( "%d", &t );
if ( t ) Add( i + n, j + n + n + p, 1 ), Add( j + n + n + p, i + n, 0 );
}
int t;
while( BFS() )
while( ( t = DFS( S, 0x7f7f7f7f ) ) > 0 ) ans += t;
printf( "%d\n", ans );
return 0;
}
这个题目特别点在于要求删点
那么就拆点:
x拆为(x,x')容量为1
原图边x->y 为无向边,所以变为x'->y和y'->x容量均为无穷大
割点x相当于割掉x->x'这条边
因为要经过x连通的边没有了x->x'这条边都联通不了
因为至少满足两个点不连通整个图就不联通了
最后枚举源点和汇点即可
点击查看代码
const int N = 100, M = 5e4+7, INF = 0x3f3f3f3f;
int s1,t1,n,m;
int head[N<<1],ver[M],nex[M],edge[M],tot;
int a[N * N],b[N * N],deep[N<<1];
inline void add(int x,int y,int z){//正边反边
ver[++tot] = y;edge[tot] = z;
nex[tot] = head[x];head[x] = tot;
ver[++tot] = x;edge[tot] = 0;
nex[tot] = head[y];head[y] = tot;
}
inline bool bfs(){
memset(deep,0,sizeof deep);
queue<int>q;
q.push(s1);
deep[s1] = 1;//分层
while(q.size()){
int x = q.front();
q.pop();
for(int i = head[x];i;i = nex[i]){
int y = ver[i],z = edge[i];//剩余容量>0才属于残量网络
if(z > 0 && !deep[y]){//不只是更新deep数组,是在残量网络上更新deep数组
q.push(y);
deep[y] = deep[x] + 1;
if(y == t1)return true;
}
}
}
return false;
}
inline int dinic(int x,int flow){
if(x == t1)return flow;
int res = flow;
for(int i = head[x];i && res;i = nex[i]){
int y = ver[i],z = edge[i];
if(z > 0 && (deep[y] == deep[x] + 1)){
int k = dinic(y,min(res,z));
if(!k)deep[y] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
res -= k;
}
}
return flow - res;
}
int main(){
while(cin>>n>>m){
for(int i = 0;i < m;++i){
char str[20];
scanf("%s",str);
a[i] = b[i] = 0;
int j;
for(j = 1;str[j] != ',';j++)
a[i] = a[i] * 10 + (str[j] - '0');
for(j++;str[j] != ')';j++)
b[i] = b[i] * 10 + (str[j] - '0');
}
int ans = INF;
for (s1 = 0; s1 < n; s1++)
for (t1 = 0; t1 < n; t1++)
if(s1 != t1){
memset(head,0,sizeof head);
tot = 1;
int maxflow = 0;
for(int i = 0;i < n;++i){
if(i == s1 || i == t1)//i是入点,i+n是出点
add(i,i + n,INF);//防止被割断
else add(i,i + n,1);
}
for(int i = 0;i < m;++i){
add(a[i] + n,b[i],INF);//不能割
add(b[i] + n,a[i],INF);
}
while(bfs()){
int num;
while((num = dinic(s1,INF)))
maxflow += num;
}
ans = min(ans,maxflow);
}
if(n <= 1 || ans == INF)ans = n;
cout<<ans<<endl;
}
return 0;
}
很明显的一道割点题,最后跑一遍最大流就好
点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int sss=0,www=1;
char chh=getchar();
while(chh<'0'||chh>'9'){
if(chh=='-') www=-1;
chh=getchar();
}
while(chh>='0'&&chh<='9'){
sss=sss*10+chh-'0';
chh=getchar();
}
return sss*www;
}
int n,m,q,s,t;
bool iscut[6005];
int depth[6005];
int head[6005],cnt=1;
struct dj{
int to,w,next;
}edg[1000005];
void add(int u,int v,int w){
edg[++cnt].next=head[u];edg[cnt].to=v;edg[cnt].w=w;head[u]=cnt;
edg[++cnt].next=head[v];edg[cnt].to=u;edg[cnt].w=0;head[v]=cnt;
}
bool bfs(){
for(int i=s;i<=t;i++)depth[i]=-1;
depth[s]=1;
queue<int> q; q.push(s);
while(!q.empty()){
int x=q.front(); q.pop();
for(register int i=head[x];i;i=edg[i].next){
int u=edg[i].to;
if(edg[i].w&&depth[u]==-1){
depth[u]=depth[x]+1;
q.push(u);
}
}
}
return depth[t]!=-1;
}
int dfs(int now,int flow){
if(now==t) return flow;
for(register int i=head[now];i;i=edg[i].next){
int u=edg[i].to;
if(edg[i].w&&depth[u]==depth[now]+1){
int tmp=dfs(u,min(edg[i].w,flow));
if(!tmp)continue;
edg[i].w-=tmp; edg[i^1].w+=tmp;
return tmp;
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,1e9);
return ans;
}
int main(){
n=read(),m=read(),q=read();
s=1,t=2*n+1;
add(1,n+1,1e9); //1节点不能被割掉
int u,v;
for(register int i=1;i<=m;i++){
//图中所给的边
u=read(),v=read();
add(u+n,v,1e9);
add(v+n,u,1e9);
}
for(register int i=1;i<=q;i++){
u=read(); iscut[u]=true;
add(u,u+n,1e9);
add(u+n,t,1e9);
}
for(register int i=1;i<=n;i++){
if(!iscut[i]){//可以被割掉
add(i,i+n,1);
}
}
printf("%d",Dinic());
return 0;
}
观察又是流动型问题 考虑网络流
最长时间最小 很明显的二分答案
建边 将牛棚拆点
S与每个牛棚i相连,边权为初始牛的数量
T与每个牛棚n+i相连,边权为最大容量
floyed预处理两点的最短路 二分的答案为maxx
如果dis[i,j]最短路<maxx
建边i->j 边权为inf 特别的i==j的时候 也要建边 边权也为inf
再跑一遍dinic即可
check函数判断最大流和总和牛是否相等
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e18
const int maxn=2005;
const int maxm=15005;
ll dis[maxn][maxn],have[maxn],hold[maxn];
int n,m,cnt,S,T,tot,ans;
int head[maxn*2],dp[maxn*2];
struct node{
int to,next;
ll w;
}edg[maxn*maxn*10];
void add(int u,int v,ll w){
edg[++cnt].next=head[u];edg[cnt].w=w;edg[cnt].to=v;head[u]=cnt;
edg[++cnt].next=head[v];edg[cnt].w=0;edg[cnt].to=u;head[v]=cnt;
}
void floyed();
void init(){
cnt=1;
for(int i=S;i<=T;i++)head[i]=0;
}
void rebuild(ll maxx){
for(int i=1;i<=n;i++){
add(S,i,have[i]);
add(i+n,T,hold[i]);
for(int j=1;j<=n;j++)
if(i==j||dis[i][j]<=maxx)
add(i,n+j,inf);
}
}
queue<int>Q;
bool bfs(){
while(!Q.empty())Q.pop();
for(int i=S;i<=T;i++)dp[i]=-1;
dp[S]=1;Q.push(S);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to;ll w=edg[i].w;
if(w&&dp[to]==-1){
dp[to]=dp[u]+1;
Q.push(to);
}
}
}
return dp[T]!=-1;
}
ll dfs(int u,ll f){
if(u==T)return f;
ll flow;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to;ll w=edg[i].w;
if(w&&dp[to]==dp[u]+1){
flow=dfs(to,min(f,w));
if(!flow)continue;
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
ll dinic(){
ll res=0;
while(bfs())res+=dfs(S,inf);
return res;
}
bool ck(ll aa){
return aa==tot;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>have[i]>>hold[i],tot+=have[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=inf;
for(ll i=1,ww,uu,vv;i<=m;i++){
cin>>uu>>vv>>ww;
dis[uu][vv]=min(dis[uu][vv],ww);
dis[vv][uu]=min(dis[vv][uu],ww);
}
floyed();
S=0,T=n<<1|1;
ll mid,l=0,r=inf;
while(l<=r){
mid=(l+r)>>1;
init();
rebuild(mid);
if(ck(dinic()))
r=mid-1,ans=mid;
else l=mid+1;
}
if(ans)cout<<ans<<endl;
else cout<<-1<<endl;
return 0;
}
void floyed(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
if(i!=k)
for(int j=1;j<=n;j++)
if(j!=i&&j!=k)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
这个题没啥好说的 就是妥妥的最大流模板题 唯一要注意的是 re可能是数组越界 也有可能分母为0 !!!
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
const int maxm=20005;
int dp[maxn],head[maxn];
int cnt=1,S,T,N,M,X;
struct node{
int to,next,w;
}edg[maxm];
void add(int u,int v,int w){
edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].w=w;
edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].w=0;
}
queue<int>Q;
bool bfs(){
while(!Q.empty())Q.pop();
for(int i=S;i<=T;i++)dp[i]=-1;
dp[S]=1;Q.push(S);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==-1){
dp[to]=dp[u]+1;
Q.push(to);
}
}
}
return dp[T]!=-1;
}
int dfs(int u,int f){
if(u==T)return f;
int flow=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==dp[u]+1){
flow=dfs(to,min(f,w));
if(!flow)continue;
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
ll dinic(){
ll res=0;
while(bfs())res+=dfs(S,inf);
return res;
}
int main(){
cin>>N>>M>>X;
S=1,T=N;
for(int i=1;i<=M;i++){
int uu,vv,ww;
cin>>uu>>vv>>ww;
add(uu,vv,ww);
}
ll ans=dinic();
if(!ans){
cout<<"Orz Ni Jinan Saint Cow!"<<endl;
return 0;
}
ll sum=X/ans;
if(X%ans)sum++;
cout<<ans<<" "<<sum<<endl;
return 0;
}
假设先取所有的格子 现在我们目标就是减去最小的 使得剩下的和最大
现在将(i+j)%2分为奇偶两种集合 和(i,j)相邻的奇偶性一定和(i,j)不同
我们将S与偶数相连 边权为点权 T与奇数相连 边权为点权
再将偶数与相邻奇数相连 边权为inf 因为这条边时不能割的
最后答案就是ans-dinic
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define inf 2e9
#define ll long long
const int maxn=100005;
int m,n,S,T,cnt=1,ans;
int head[maxn],dp[maxn],val[maxn];
int d[5][2];
struct node{
int to,next,w;
}edg[maxn*10];
void add(int u,int v,int w){
edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].w=w;
edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].w=0;
}
queue<int>Q;
bool bfs(){
while(!Q.empty())Q.pop();
for(int i=S;i<=T;i++)dp[i]=-1;
dp[S]=1;Q.push(S);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==-1){
dp[to]=dp[u]+1;
Q.push(to);
}
}
}
return dp[T]!=-1;
}
int dfs(int u,int f){
if(u==T)return f;
int flow=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,w=edg[i].w;
if(w&&dp[to]==dp[u]+1){
flow=dfs(to,min(f,w));
if(!flow)continue;
edg[i].w-=flow;
edg[i^1].w+=flow;
return flow;
}
}
return 0;
}
int dinic(){
int res=0;
while(bfs())res+=dfs(S,inf);
return res;
}
bool ck(int x,int y){
if(x<1||x>m)return false;
if(y<1||y>n)return false;
return true;
}
int main(){
cin>>m>>n;
for(int vv,i=1;i<=m;i++)
for(int j=1;j<=n;j++)
cin>>vv,val[(i-1)*n+j]=vv,ans+=vv;
S=0,T=n*m+1;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if((i+j)&1)add(S,(i-1)*n+j,val[(i-1)*n+j]);
else add((i-1)*n+j,T,val[(i-1)*n+j]);
d[1][0]=-1,d[1][1]=0;
d[2][0]=0,d[2][1]=-1;
d[3][0]=1,d[3][1]=0;
d[4][0]=0,d[4][1]=1;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if((i+j)&1)
for(int k=1;k<=4;k++){
int xx=i+d[k][0],yy=j+d[k][1];
if(!ck(xx,yy))continue;
add((i-1)*n+j,(xx-1)*n+yy,inf);
}
cout<<ans-dinic()<<endl;
return 0;
}
因为是无向图 所以要建立双向边 再跑一遍最大流 很容易tle要加各种优化
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <cctype>
using namespace std;
inline void read(int &x) {
x = 0; char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar();
}
#define MAXN 1003
struct node{
int fr, to, va, nxt;
}edge[MAXN * MAXN * 6];
int head[MAXN * MAXN], cnt;
inline void add_edge(int u, int v, int w) {
edge[cnt].fr = u, edge[cnt].to = v, edge[cnt].va = w;
edge[cnt].nxt = head[u], head[u] = cnt++;
edge[cnt].fr = v, edge[cnt].to = u, edge[cnt].va = w;
edge[cnt].nxt = head[v], head[v] = cnt++; //反向边初始化
}
int st, ed, rk[MAXN * MAXN];
int BFS() {
queue<int> q;
memset(rk, 0, sizeof rk);
rk[st] = 1;
q.push(st);
while(!q.empty()) {
int tmp = q.front();
//cout<<tmp<<endl;
q.pop();
for(int i = head[tmp]; i != -1; i = edge[i].nxt) {
int o = edge[i].to;
if(rk[o] || edge[i].va <= 0) continue;
rk[o] = rk[tmp] + 1;
q.push(o);
}
}
return rk[ed];
}
int dfs(int u, int flow) {
if(u == ed) return flow;
for(int i = head[u]; i != -1 && add < flow; i = edge[i].nxt) {
int v = edge[i].to;
if(rk[v] != rk[u] + 1 || !edge[i].va) continue;
int tmpadd = dfs(v, min(edge[i].va, flow));
if(!tmpadd) { //重要!就是这里!
rk[v] = -1;
continue;
}
edge[i].va -= tmpadd, edge[i ^ 1].va += tmpadd;
return tmpadd;
}
return 0;
}
int ans;
void dinic() {
while(BFS()) ans += dfs(st, 0x3fffff);
}
int n, m;
inline int gethash(int i, int j) {
return (i - 1) * m + j;
}
int main() {
memset(head, -1, sizeof head);
read(n), read(m);
int tmp;
st = 1, ed = gethash(n, m);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j < m; ++j)
read(tmp), add_edge(gethash(i, j), gethash(i, j + 1), tmp);
}
for(int i = 1; i < n; ++i) {
for(int j = 1; j <= m; ++j)
read(tmp), add_edge(gethash(i, j), gethash(i + 1, j), tmp);
}
for(int i = 1; i < n; ++i) {
for(int j = 1; j < m; ++j)
read(tmp), add_edge(gethash(i, j), gethash(i + 1, j + 1), tmp);
}
dinic();
cout<<ans<<endl;
return 0;
}
费用流的板子题目 和dinic差别在于 bfs换成了spfa dfs过程变成了从T回溯的过程
先回顾一下spfa:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e5+5;
int cnt=1,n,m,S,T,maxflow,mincost;
struct node{
int to,next,flow,dis;
}edg[maxn];
bool vis[maxn];
int dis[maxn],last[maxn],flow[maxn],pre[maxn],head[maxn];
void add(int u,int v,int flow,int dis){
edg[++cnt].next=head[u];
edg[cnt].to=v;
head[u]=cnt;
edg[cnt].flow=flow;
edg[cnt].dis=dis;
}
queue<int>Q;
bool spfa(){
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
Q.push(S);
vis[S]=1;dis[S]=0;pre[T]=-1;
while(!Q.empty()){
int u=Q.front();
Q.pop();
vis[u]=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to;
if(edg[i].flow&&dis[to]>dis[u]+edg[i].dis){
dis[to]=dis[u]+edg[i].dis;
pre[to]=u;
last[to]=i;
flow[to]=min(flow[u],edg[i].flow);
if(!vis[to]){
vis[to]=1;
Q.push(to);
}
}
}
}
return pre[T]!=-1;
}
void dd(){
while(spfa()){
int now=T;
maxflow+=flow[T];
mincost+=flow[T]*dis[T];
while(now!=S){
edg[last[now]].flow-=flow[T];
edg[last[now]^1].flow+=flow[T];
now=pre[now];
}
}
}
int main(){
cin>>n>>m>>S>>T;
for(int i=1;i<=m;i++){
int xx,yy,zz,ff;
scanf("%d%d%d%d",&xx,&yy,&zz,&ff);
add(xx,yy,zz,ff);
add(yy,xx,0,-ff);
}
dd();
cout<<maxflow<<" "<<mincost<<endl;
return 0;
}
很好很经典的一道题
我开始一直没想明白为什么S要和旧毛巾连ri的边 第一天的旧毛巾是哪里来的 第一天不是只能买新毛巾嘛?
观察图才发现 点1+n(第一天的新毛巾来源只有买,没有其他的来源了) S向旧毛巾连ri的边只是保证每天用完肯定是会有ri的旧毛巾的
这个建图确实不好想 但是真的好巧妙
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
int n,S,T;
int head[maxn<<1],cnt=1;
int d[maxn<<1],vis[maxn<<1],last[maxn<<1],pre[maxn<<1],flow[maxn<<1],r[maxn];
ll mincost;
struct node{
int to,next;
int f,cost;
}edg[maxn*maxn];
void add(int u,int v,int f,int w){
edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].cost=w;edg[cnt].f=f;
edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].cost=-w;edg[cnt].f=0;
}
queue<int>Q;
bool spfa(){
memset(d,0x7f,sizeof(d));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
vis[S]=1;d[S]=0;Q.push(S);pre[T]=-1;
while(!Q.empty()){
int u=Q.front();
Q.pop();vis[u]=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,f=edg[i].f,cost=edg[i].cost;
if(f&&d[to]>d[u]+cost){
d[to]=d[u]+cost;
pre[to]=u;
last[to]=i;
flow[to]=min(flow[u],f);
if(!vis[to]){
vis[to]=1;
Q.push(to);
}
}
}
}
return pre[T]!=-1;
}
void calc(){
while(spfa()){
int now=T;
mincost+=flow[T]*d[T];
while(now!=S){
edg[last[now]].f-=flow[T];
edg[last[now]^1].f+=flow[T];
now=pre[now];
}
}
}
int main(){
cin>>n;
S=0,T=(n<<1)+1;
for(int i=1;i<=n;i++)cin>>r[i];
int pp,mm,ff,nn,ss;
cin>>pp>>mm>>ff>>nn>>ss;
for(int i=1;i<=n;i++){
add(S,i,r[i],0);
add(i+n,T,r[i],0);
add(S,n+i,inf,pp);
if(i<n)add(i,i+1,inf,0);
if(i+mm<=n)add(i,i+n+mm,inf,ff);
if(i+nn<=n)add(i,i+n+nn,inf,ss);
}
calc();
cout<<mincost<<endl;
return 0;
}
这个题题很明显的求最小费用最大流和最大费用最大流
求最大费用最大流只需要将边权都取为相反数就好,为什么呢?
以前边权是求最小正值 现在边权是求小负值 就是负数的绝对值最大 就是最长路
因为要求两次 所以进行一次后就直接将边复原再跑一次就好了
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
int head[maxn],cnt=1;
struct node{
int to,next,w,f;
}edg[maxn*maxn*10];
void add(int u,int v,int f,int w){
edg[++cnt].w=w;edg[cnt].f=f;edg[cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;
edg[++cnt].w=-w;edg[cnt].f=0;edg[cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;
}
int vis[maxn],flow[maxn],d[maxn],pre[maxn],last[maxn],dd[maxn];
int n,m,S,T;
queue<int>Q;
bool spfa(){
memset(vis,0,sizeof(vis));
memset(flow,0x7f,sizeof(flow));
memset(d,0x7f,sizeof(d));
pre[T]=-1;vis[S]=1;Q.push(S);d[S]=0;
while(!Q.empty()){
int u=Q.front();
Q.pop();vis[u]=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,f=edg[i].f,w=edg[i].w;
if(f&&d[to]>d[u]+w){
d[to]=d[u]+w;
pre[to]=u;
last[to]=i;
flow[to]=min(flow[u],f);
if(!vis[to]){
Q.push(to);
vis[to]=1;
}
}
}
}
return pre[T]!=-1;
}
int calc(){
int cost=0;
while(spfa()){
int now=T;
cost+=flow[T]*d[T];
while(now!=S){
edg[last[now]].f-=flow[T];
edg[last[now]^1].f+=flow[T];
now=pre[now];
}
}
return cost;
}
int main(){
cin>>m>>n;
S=0,T=n*m+1;
for(int ai,i=1;i<=m;i++)
cin>>ai,add(S,i,ai,0);
for(int bi,i=1;i<=n;i++)
cin>>bi,add(m+i,T,bi,0);
for(int xx,i=1;i<=m;i++)
for(int j=1;j<=n;j++)
cin>>xx,add(i,m+j,inf,xx);
cout<<calc()<<endl;
for(int i=2;i<cnt;i+=2){
edg[i].f+=edg[i^1].f;
edg[i^1].f=0;
edg[i].w=-edg[i].w;
edg[i^1].w=-edg[i^1].w;
}
cout<<-calc()<<endl;
return 0;
}
点击查看代码
#include<bits/stdc++.h>
#define inf 1000000007
#define N 2000005
#define M 505
using namespace std;
struct Edge{
int u,v,next,f;
}G[N];
int head[N],tot=0,a[M],dp[M],n,len,s,t,ans;
void addedge(int u,int v,int f){
G[tot].u=u;G[tot].v=v;G[tot].f=f;G[tot].next=head[u];head[u]=tot++;
G[tot].u=v;G[tot].v=u;G[tot].f=0;G[tot].next=head[v];head[v]=tot++;
}
int level[100*M];
bool bfs(int s,int t){
memset(level,0,sizeof(level));
queue<int>q;q.push(s);level[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
if(u==t)return 1;
for(int i=head[u];i!=-1;i=G[i].next){
int v=G[i].v,f=G[i].f;
if(level[v]==0&&f)q.push(v),level[v]=level[u]+1;
}
}
return 0;
}
int dfs(int u,int maxf,int t){
if (u==t)return maxf;
int rat=0;
for (int i=head[u];i!=-1&&rat<maxf;i=G[i].next){
int v=G[i].v;int f=G[i].f;
if (level[v]==level[u]+1&&f){
int Min=min(maxf-rat,f);
f=dfs(v,Min,t);
G[i].f-=f;G[i^1].f+=f;rat+=f;
}
}
if (!rat)level[u]=N;
return rat;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),dp[i]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);
for(int i=1;i<=n;i++)len=max(len,dp[i]);
printf("%d\n",len);
if(len==1){
cout<<n<<endl<<n<<endl;
return 0;
}
s=0;t=5000;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)if(dp[i]==1)addedge(s,i,1);
for(int i=1;i<=n;i++)if(dp[i]==len)addedge(i+n,t,1);
for(int i=1;i<=n;i++)addedge(i,i+n,1);
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(a[j]<=a[i]&&dp[j]+1==dp[i])addedge(j+n,i,1);
while(bfs(s,t))ans+=dfs(s,inf,t);printf("%d\n",ans);
addedge(1,1+n,inf);addedge(s,1,inf);
if(dp[n]==len)addedge(n,n*2,inf),addedge(n*2,t,inf);
while(bfs(s,t))ans+=dfs(s,inf,t);
printf("%d\n",ans);
return 0;
}
这个题目恶心就在于中间建边的过程
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 751 * 15 + 10;
const int M = (N + 750 + 20 * 751 + 10) * 2;
const int INF = 1e9;
int n, m, k, S, T;
struct Edge
{
int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
struct Ship
{
int h, r, id[30];
}ships[30];
int fa[30];
int find(int x)
{
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
int get(int i, int day)
{
return day * (n + 2) + i;
}
void add(int x, int y, int z)
{
line[idx] = {y, fist[x], z};
fist[x] = idx ++;
line[idx] = {x, fist[y], 0};
fist[y] = idx ++;
}
bool bfs()
{
queue<int> q;
memset(d, -1, sizeof d);
q.push(S), d[S] = 0, cur[S] = fist[S];
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = fist[u]; i != -1; i = line[i].nxt)
{
int v = line[i].to;
if(d[v] == -1 && line[i].flow)
{
d[v] = d[u] + 1;
cur[v] = fist[v];
if(v == T) return 1;
q.push(v);
}
}
}
return 0;
}
int find(int u, int limit)
{
if(u == T) return limit;
int flow = 0;
for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
{
cur[u] = i;
int v = line[i].to;
if(d[v] == d[u] + 1 && line[i].flow)
{
int t = find(v, min(line[i].flow, limit - flow));
if(!t) d[v] = -1;
line[i].flow -= t;
line[i ^ 1].flow += t;
flow += t;
}
}
return flow;
}
int dinic()
{
int res = 0, flow;
while(bfs()) while(flow = find(S, INF)) res += flow;
return res;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
S = N - 2, T = N - 1;
memset(fist, -1, sizeof fist);
for(int i = 0; i < 30; ++ i) fa[i] = i;
for(int i = 0; i < m; ++ i)
{
int a, b;
scanf("%d%d", &a, &b);
ships[i] = {a, b};
for(int j = 0; j < b; ++ j)
{
int id;
scanf("%d", &id);
if(id == -1) id = n + 1;
ships[i].id[j] = id;
if(j)
{
int x = ships[i].id[j - 1];
fa[find(x)] = find(id);
}
}
}
if(find(0) != find(n + 1)) puts("0");
else
{
add(S, get(0, 0), k);
add(get(n + 1, 0), T, INF);
int day = 1, res = 0;
while(1)
{
add(get(n + 1, day), T, INF);
for(int i = 0; i <= n + 1; ++ i)
add(get(i, day - 1), get(i, day), INF);
for(int i = 0; i < m; ++ i)
{
int r = ships[i].r;
int a = ships[i].id[(day - 1) % r];
int b = ships[i].id[day % r];
add(get(a, day - 1), get(b, day), ships[i].h);
}
res += dinic();
if(res >= k) break;
++ day;
}
printf("%d\n", day);
}
return 0;
}
最小路径覆盖数=顶点数-最大匹配
为什么?
我们首先将原图用n条路径覆盖,每条边只经过每个节点。
现在尽量合并更多的路径(即将两个路径通过一条边首尾相连)。
可以知道,每合并两条路径,图中的路径覆盖数就会减少1。
所以我们只需要利用网络流合并相关的路径即可
本题麻烦就在 输出路径 只要我们找起点 之后在残留网络里面走就好
问题变为维护起点 可以在dfs遍历的过程中记录 也可以最后并查集记录
我这里用的并查集 合并两个的时候 一定是fa[find(to)]=find(u) 顺序不能变 因为一定要保证起点为fa[i]=i
判断起点就直接fa[i]=i即可
还有数组能开大点就开大点
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=3550;
const int maxm=(maxn<<2)+6e5;
int S,T,n,m;
int dp[maxn],vis[maxn],fa[maxn];
int head[maxn],cnt=1;
int find(int x){
if(fa[x]!=x)return fa[x]=find(fa[x]);
return x;
}
struct node{
int to,next,f;
}edg[maxn];
void add(int u,int v,int f){
edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].f=f;edg[cnt].to=v;
edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].f=0;edg[cnt].to=u;
}
queue<int>Q;
bool bfs(){
while(!Q.empty())Q.pop();
memset(dp,-1,sizeof(dp));
dp[S]=1;
Q.push(S);
while(!Q.empty()){
int u=Q.front();
Q.pop();
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,f=edg[i].f;
if(f&&dp[to]==-1){
dp[to]=dp[u]+1;
Q.push(to);
}
}
}
return dp[T]!=-1;
}
int dfs(int u,int flow){
if(u==T)return flow;
int tot=0;
for(int i=head[u];i;i=edg[i].next){
int to=edg[i].to,f=edg[i].f;
if(dp[to]==dp[u]+1&&f){
int t=dfs(to,min(f,flow-tot));
if(!t)continue;
edg[i].f-=t;
edg[i^1].f+=t;
tot+=t;
}
}
return tot;
}
void output(int x){
cout<<x<<" ";
for(int i=head[x];i;i=edg[i].next)
if(edg[i].to>n&&edg[i].to<T&&edg[i].f==0){
output(edg[i].to-n);
}
}
int dinic(){
int res=0;
while(bfs())
res+=dfs(S,inf);
for(int i=S;i<=T;i++)
fa[i]=i;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edg[j].next){
if(edg[j].f==0&&edg[j].to<T&&edg[j].to>n)
fa[find(edg[j].to-n)]=find(i);
}
for(int i=1;i<=n;i++)
if(fa[i]==i)
output(i),cout<<endl;
return res;
}
int main(){
cin>>n>>m;
S=0;T=2*n+1;
for(int i=1;i<=n;i++)
add(S,i,1),add(i+n,T,1);
for(int i=1;i<=m;i++){
int uu,vv;
cin>>uu>>vv;
add(uu,vv+n,1);
}
cout<<n-dinic();
return 0;
}