2024/7/5 随笔+刷题笔记
2024/7/7就要去重庆集训了(
今天上午复习了一下网络流和dicnic:
P3376 【模板】网络最大流
https://www.luogu.com.cn/problem/P3376
借鉴一篇题解的思路;
定义有向图,n点m边。源点s,汇点t
c(x,y)为边的容量,允许的最大流量
函数的三大性质:
容量限制:每条边的流量总不可能大于该边的容量的(不然水管就爆了)
斜对称:正向边的流量=反向边的流量(反向边后面会具体讲)
流量守恒:正向的所有流量和=反向的所有流量和(就是总量始终不变)
残量网络
在任意时刻,网络中所有节点以及剩余容量大于
0的边构成的子图被称为残量网络
最大流
对于上面的网络,合法的流函数有很多,其中使得整个网络流量之和最大的流函数称为网络的最大流,此时的流量和被称为网络的最大流量
反向边
插入讲解一下反向边这个概念,这是网络流中的一个重点
为什么要建反向边?
因为可能一条边可以被包含于多条增广路径,所以为了寻找所有的增广路经我们就要让这一条边有多次被选择的机会
而构建反向边则是这样一个机会,相当于给程序一个反悔的机会!
Dicnic算法:
根据BFS搜索,d[x]表示深度,点x到s的边数
Dinic算法框架
在残量网络上BFS求出节点的层次,构造分层图
在分层图上DFS寻找增广路,在回溯时同时更新边权
using namespace std;
const int inf = 2147483647,maxn = 1e6+100;
int n,m,s,t,u,v;
#define ll long long
ll w,ans,dis[maxn];
int cnt = 1,now[maxn],head[maxn];
struct node{
int to,next;
ll val;
}e[maxn];
void addedge(int u,int v,ll w){
cnt++;
e[cnt].to = v;
e[cnt].val = w;
e[cnt].next = head[u];
head[u] = cnt;
cnt++;//建立流量为0的反向边
e[cnt].to = u;
e[cnt].val = 0;;
e[cnt].next = head[v];
head[v] = cnt;
}
int bfs(){//构造分层图
for(int i = 1;i <= n;i++) dis[i] = inf;
queue<int> q;
q.push(s);
dis[s] = 0;
now[s] = head[s];
while(!q.empty()){
int x = q.front();
q.pop();//并不会返回弹出元素的值!
for(int i = head[x];i;i = e[i].next){
int y = e[i].to;
if(e[i].val>0&&dis[y]==inf){
q.push(y);
now[y] = head[y];//
dis[y] = dis[x]+1;
if(y == t) return 1;//汇点直接返回
}
}
}
return 0;
}
int dfs(int x,ll sum){//sum是整条增广路对最大流的贡献
if(x == t) return sum;//到汇点,dfs结束
ll k,res = 0;//k最小剩余流量
for(int i = now[x];i&∑i=e[i].next){
now[x] = i;
int y = e[i].to;
if(e[i].val>0&&(dis[y]==dis[x]+1)){
k = dfs(y,min(sum,e[i].val));
if(k == 0) dis[y] = inf;
e[i].val -= k;//流过
e[i^1].val += k;//处理反向边
res+=k;//总流量
sum-=k;
}
}
return res;
}
int main(){
cin>>n>>m>>s>>t;
for(int i = 1;i <= m;i++){
cin>>u>>v>>w;
addedge(u,v,w);
}
ans = 0;
while(bfs()){
//cout<<bfs()<<endl;
ans+=dfs(s,inf);
}
cout<<ans<<endl;
}````
两道Wa待A题目:
P3386 【模板】二分图最大匹配 (使用最大流解法wa,90)
[https://www.luogu.com.cn/problem/P3386]()
左部点连源点,右部点连汇点,中间连边,流量均为1,中间记得连双向边
wa_code:
`#include<bits/stdc++.h>
using namespace std;
const int inf = 2147483647,maxn = 1e6+100;
int n,m,s,t,u,v;
int en;
#define ll long long
ll w,ans,dis[maxn];
int cnt = 1,now[maxn],head[maxn];
struct node{
int to,next;
ll val;
}e[maxn];
void addedge(int u,int v,ll w){
cnt++;
e[cnt].to = v;
e[cnt].val = w;
e[cnt].next = head[u];
head[u] = cnt;
cnt++;//建立流量为0的反向边
e[cnt].to = u;
e[cnt].val = 0;;
e[cnt].next = head[v];
head[v] = cnt;
}
int bfs(){//构造分层图
for(int i = 1;i <= n+m+2;i++) dis[i] = inf;
queue<int> q;
q.push(s);
dis[s] = 0;
now[s] = head[s];
while(!q.empty()){
int x = q.front();
q.pop();//并不会返回弹出元素的值!
for(int i = head[x];i;i = e[i].next){
int y = e[i].to;
//cout<<y<<" "<<dis[y]<<endl;
if(e[i].val>0&&dis[y]==inf){
q.push(y);
now[y] = head[y];//
dis[y] = dis[x]+1;
//cout<<y<<"a"<<t<<endl;
if(y == t) return 1;//汇点直接返回
}
}
}
return 0;
}
int dfs(int x,ll sum){//sum是整条增广路对最大流的贡献
if(x == t) return sum;//到汇点,dfs结束
ll k,res = 0;//k最小剩余流量
for(int i = now[x];i&∑i=e[i].next){
now[x] = i;
int y = e[i].to;
if(e[i].val>0&&(dis[y]==dis[x]+1)){
k = dfs(y,min(sum,e[i].val));
if(k == 0) dis[y] = inf;
e[i].val -= k;//流过
e[i^1].val += k;//处理反向边
res+=k;//总流量
sum-=k;
}
}
return res;
}
int main(){
cin>>n>>m>>en;
t = n+m+2;
s = 1;
for(int i = 1;i <=en;i++){
int u,v;
cin>>u>>v;
addedge(u+1,v+n+1,1);
addedge(v+n+1,u+1,1);
}
for(int i = 1;i <= n;i++){
addedge(1,i+1,1);
addedge(i+1,1,1);
}
for(int i = 1;i <= m;i++){
addedge(n+m+2,i+n+1,1);
addedge(i+n+1,n+m+2,1);
}
ans = 0;
while(bfs()){
//cout<<bfs()<<endl;
ans+=dfs(s,inf);
}
cout<<ans<<endl;
}`
匈牙利ac:
`#include<bits/stdc++.h>
using namespace std;
const int maxn = 550;
int mch[maxn],vistime[maxn];
vector<int> a[maxn];
int n,m,e;
bool dfs(int u,int tag){
if(vistime[u] == tag) return false;
vistime[u] = tag;
for(auto i : a[u]){
if(mch[i] == 0||dfs(mch[i],tag)){
mch[i] = u;
return true;
}
}
return false;
}
int main(){
cin>>n>>m>>e;
for(int i = 1;i <= e;i++){
int u,v;
cin>>u>>v;
a[u].push_back(v);
}
int ans = 0;
for(int i = 1;i <= n;i++){
if(dfs(i,i)) ans++;
}
cout<<ans<<endl;
return 0;
}`
P1402 酒店之王
[https://www.luogu.com.cn/problem/P1402]()
建立源点S,汇点T
顾客A,房间B,菜品C
我的思路:
匹配的a,b,c之间分别建立流量为1的边
建立两个b点(割点)防止同一个房间被用两次
最后跑最大流。理论ac?(
`#include<bits/stdc++.h>
using namespace std;
int An,Pn,Qn,n;
const int maxn = 500;
const int inf = 2147483647;
struct edge{
int to,next,val;
}e[maxn];
int dis[maxn],now[maxn],head[maxn];
int S,T;
int cnt = 1;
void addedge(int u,int v,int w){
cnt++;
e[cnt].to = v;
e[cnt].val = w;
e[cnt].next = head[u];
head[u] = cnt;
cnt++;//建立流量为0的反向边
e[cnt].to = u;
e[cnt].val = 0;;
e[cnt].next = head[v];
head[v] = cnt;
}
int bfs(){//构造分层图
for(int i = 1;i <= maxn;i++) dis[i] = inf;
queue<int> q;
q.push(S);
dis[S] = 0;
now[S] = head[S];
while(!q.empty()){
int x = q.front();
q.pop();//并不会返回弹出元素的值!
for(int i = head[x];i;i = e[i].next){
int y = e[i].to;
//cout<<y<<" "<<dis[y]<<endl;
if(e[i].val>0&&dis[y]==inf){
q.push(y);
now[y] = head[y];//
dis[y] = dis[x]+1;
//cout<<y<<"a"<<t<<endl;
if(y == T) return 1;//汇点直接返回
}
}
}
return 0;
}
int dfs(int x,int sum){//sum是整条增广路对最大流的贡献
if(x == T) return sum;//到汇点,dfs结束
int k,res = 0;//k最小剩余流量
for(int i = now[x];i&∑i=e[i].next){
now[x] = i;
int y = e[i].to;
if(e[i].val>0&&(dis[y]==dis[x]+1)){
k = dfs(y,min(sum,e[i].val));
if(k == 0) dis[y] = inf;
e[i].val -= k;//流过
e[i^1].val += k;//处理反向边
res+=k;//总流量
sum-=k;
}
}
return res;
}
int dinic(){// 1 1 0 0 0
int ans = 0;
while(bfs()){
ans += dfs(S,inf);
}
return ans;
}
bool room[maxn][maxn],dish[maxn][maxn],rd[maxn][maxn],edg[maxn][maxn];
int main(){
cin>>An>>Pn>>Qn;//客人下标:2~An+1 房间1下标:An+2~An+Pn+1 房间2下标:An+Pn+2~An+2Pn+1 菜下标:An+2Pn+2~An+2Pn+Qn+1 汇点:An+2Pn+Qn+2
int tot;
S = 1,T = An+2*Pn+Qn+2;
for(int i = 1;i <= An;i++){
addedge(1,i+1,1);//源点向客人连边
//cout<<1<<"a"<<i+1<<endl;
n++;
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Pn;j++){
bool flag;
cin>>flag;
room[i][j] = flag;
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Qn;j++){
bool flag;
cin>>flag;
dish[i][j] = flag;
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Pn;j++){
if(room[i][j]){
if(!edg[i+1][An+j+1]){
addedge(i+1,An+j+1,1);
edg[i+1][An+j+1] = true;
//cout<<i+1<<"b"<<An+j+1<<endl;
}
if(!edg[An+j+1][An+Pn+j+1]){
addedge(An+j+1,An+Pn+j+1,1);
//cout<<An+j+1<<"b"<<An+Pn+j+1<<endl;
edg[An+j+1][An+Pn+j+1] = true;
}
}
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Qn;j++){
if(dish[i][j]){
for(int k = 1;k <= Pn;k++){
if(room[i][k]){
if(!edg[An+Pn+k+1][An+2*Pn+j+1]){
addedge(An+Pn+k+1,An+2*Pn+j+1,1);//Pn->Qn
//cout<<An+Pn+k+1<<"c"<<An+2*Pn+j+1<<endl;
edg[An+Pn+k+1][An+2*Pn+j+1] = true;
}
if(!edg[An+2*Pn+j+1][An+2*Pn+Qn+2]){
addedge(An+2*Pn+j+1,An+2*Pn+Qn+2,1);
//cout<<An+2*Pn+j+1<<"c"<<An+2*Pn+Qn+2<<endl;
edg[An+2*Pn+j+1][An+2*Pn+Qn+2] = true;
}
}
}
}
}
}
cout<<dinic()<<endl;
}`
另一种同样是50pts的代码()
`#include<bits/stdc++.h>
using namespace std;
int An,Pn,Qn,n;
const int maxn = 800;
const int inf = 2147483647;
struct edge{
int to,next,val;
}e[maxn];
int dis[maxn],now[maxn],head[maxn];
int S,T;
int cnt = 1;
void addedge(int u,int v,int w){
cnt++;
e[cnt].to = v;
e[cnt].val = w;
e[cnt].next = head[u];
head[u] = cnt;
cnt++;//建立流量为0的反向边
e[cnt].to = u;
e[cnt].val = 0;;
e[cnt].next = head[v];
head[v] = cnt;
}
int bfs(){//构造分层图
for(int i = 1;i <= maxn;i++) dis[i] = inf;
queue<int> q;
q.push(S);
dis[S] = 0;
now[S] = head[S];
while(!q.empty()){
int x = q.front();
q.pop();//并不会返回弹出元素的值!
for(int i = head[x];i;i = e[i].next){
int y = e[i].to;
//cout<<y<<" "<<dis[y]<<endl;
if(e[i].val>0&&dis[y]==inf){
q.push(y);
now[y] = head[y];//
dis[y] = dis[x]+1;
//cout<<y<<"a"<<t<<endl;
if(y == T) return 1;//汇点直接返回
}
}
}
return 0;
}
int dfs(int x,int sum){//sum是整条增广路对最大流的贡献
if(x == T) return sum;//到汇点,dfs结束
int k,res = 0;//k最小剩余流量
for(int i = now[x];i&∑i=e[i].next){
now[x] = i;
int y = e[i].to;
if(e[i].val>0&&(dis[y]==dis[x]+1)){
k = dfs(y,min(sum,e[i].val));
if(k == 0) dis[y] = inf;
e[i].val -= k;//流过
e[i^1].val += k;//处理反向边
res+=k;//总流量
sum-=k;
}
}
return res;
}
int dinic(){// 1 1 0 0 0
int ans = 0;
while(bfs()){
ans += dfs(S,inf);
}
return ans;
}
bool room[maxn][maxn],dish[maxn][maxn],rd[maxn][maxn],edg[maxn][maxn];
int main(){
cin>>An>>Pn>>Qn;
//客人下标:2~An+1 房间1下标:150~150+Pn 房间2下标:350~500+Pn 菜下标:550~An+650+Qn 汇点:700
//客人下标:2~An+1 房间1下标:An+2~An+Pn+1 房间2下标:An+Pn+2~An+2Pn+1 菜下标:An+2Pn+2~An+2Pn+Qn+1 汇点:An+2Pn+Qn+2
//int tot;
S = 1,T = 700;
for(int i = 1;i <= An;i++){
addedge(1,i+1,1);//源点向客人连边
//cout<<1<<"a"<<i+1<<endl;
n++;
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Pn;j++){
bool flag;
cin>>flag;
room[i][j] = flag;
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Qn;j++){
bool flag;
cin>>flag;
dish[i][j] = flag;
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Pn;j++){
if(room[i][j]){
if(!edg[i+1][150+j]){
addedge(i+1,150+j,1);
edg[i+1][150] = true;
//cout<<i+1<<"b"<<150+j<<endl;
}
if(!edg[150+j][350+j]){
addedge(150+j,350+j,1);
//cout<<150+j<<"b"<<350+j<<endl;
edg[150+j][350+j] = true;
}
}
}
}
for(int i = 1;i <= An;i++){
for(int j = 1;j <= Qn;j++){
if(dish[i][j]){
for(int k = 1;k <= Pn;k++){
if(room[i][k]){
if(!edg[350+k][500+j]){
addedge(350+k,500+j,1);//Pn->Qn
//cout<<350+k<<"c"<<500+j<<endl;
edg[350+k][500+j] = true;
}
if(!edg[500+j][700]){
addedge(500+j,700,1);
//cout<<500+j<<"c"<<700<<endl;
edg[500+j][700] = true;
}
}
}
}
}
}
cout<<dinic()<<endl;
}`
AC代码:
`#include<bits/stdc++.h>
using namespace std;
const int t=500,INF=1e8;
struct p{
int x,y,dis;
}c[200001];
int n,p,q,num;
int h[501],d[501];
void add(int x,int y,int dis)
{
c[num].x=h[y];c[num].y=x;c[num].dis=0;h[y]=num++;
c[num].x=h[x];c[num].y=y;c[num].dis=dis;h[x]=num++;
}
bool bfs()
{
queue<int> qu;
memset(d,0,sizeof(d));
d[0]=1;
qu.push(0);
while(!qu.empty())
{
int tt=qu.front();
qu.pop();
for(int i=h[tt];i;i=c[i].x)
if(!d[c[i].y]&&c[i].dis)
{
d[c[i].y]=d[tt]+1;
qu.push(c[i].y);
}
}
return d[t];
}
int dfs(int x,int dix)
{
if(!dix||x==t) return dix;
int sum=0;
for(int i=h[x];i;i=c[i].x)
if(d[c[i].y]==d[x]+1&&c[i].dis)
{
int dis=dfs(c[i].y,min(dix,c[i].dis));
if(dis)
{
dix-=dis;
sum+=dis;
c[i].dis-=dis;
c[i^1].dis+=dis;
if(!dix) break;
}
}
if(!sum) d[x]=-1;
return sum;
}
int dinic()
{
int tot=0;
while(bfs())
tot+=dfs(0,INF);
return tot;
}
int main()
{
cin>>n>>p>>q;
for(int i=1;i<=p;i++)
add(0,i,1);
for(int i=1;i<=q;i++)
add(i+p,t,1);
for(int i=1;i<=n;i++)
add(i+p+q,i+p+q+n,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=p;j++){
int x;
cin>>x;
if(x) add(j,i+p+q,1);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=q;j++){
int x;
cin>>x;
if(x) add(i+p+q+n,j+p,1);
}
cout<<dinic();
return 0;
}`
最后记录一道之前比赛wa的题
P10673 【MX-S1-T2】催化剂
[https://www.luogu.com.cn/problem/P10673]()
树状数组解法
`#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn = 2e6+10;
ll n,m,q,mod,x,y,s,tree[maxn],t[maxn];
int read(){
int x=0;char ac=getchar();
while(ac<'0' || ac>'9') ac=getchar();
while(ac>='0' && ac<='9') x=x*10+ac-'0',ac=getchar();
return x;
}
ll lowbit(int num){
return num&-num;
}
void add(ll s,ll num){
for(ll i=s;i<=maxn-1;i+=lowbit(i)) tree[i]+=num;//循环上界一直不好找,索性就改成maxn了,居然A了?
}
ll ask(ll s){
ll ans=0;
for(ll i=s;i>=1;i-=lowbit(i)) ans+=tree[i];
return ans;
}
void update(ll x,ll y,ll s){
add(x,s);
add(y+1,-s);
return;
}
ll query(ll x){
return ask(x);
}
int main(){
cin>>n>>m;
for(ll i = 1;i <= n;i++){
ll a;
a=read();
t[a]++;
update(1,t[a]-1,1);
}
while(m--){
ll aa,bb;
cin>>aa>>bb;
if(aa == 1){
t[bb]++;
update(1,t[bb]-1,1);
}
if(aa == 2){
t[bb]--;
update(1,t[bb],-1);
}
if(aa == 3){
cout<<query(bb)<<endl;
}
}
return 0;
}`
后天去重庆集训,题难( )
能听多少是多少吧