1.1.3.3 最小割之最小权覆盖集、最大权独立集
1.1.3.3 最小割之最小权覆盖集、最大权独立集
最小权点覆盖
给定一个无向图,点带权。选择某些点,使得点所连边能够包含整张图的所有点,则这个点集叫做点覆盖集;点权之和最小的点覆盖集就叫最小权点覆盖集。
最小权点覆盖问题对于一般图是 NP-完全 问题,即不存在多项式解法的问题,已经证明只能爆搜求解
二分图的最小权点覆盖集
要求:点权非负
源点连一边,汇点连另一边,边容量等于权值;中间点连边容量为正无穷,这个问题就可以类比成最小割模型了。
接下来证明可行解与简单割的一一对应关系。首先可以发现,简单割由于不包含正无穷容量边,所以简单割一定可以对应构造得到点覆盖集;反之,我们可以通过点覆盖集发现某些边是否可走,那么按照此方式进行 dfs,就会把整个点集分为两部分:可到达为 S
,不可到达为 T。显然这样的分配方式满足简单割。综上,可行解与简单割一一对应,简单割的容量就等于点覆盖集的点权之和。
2325. 有向图破坏
这个题最关键的是拆点,对于每一条边u,v,要么选择u-,要么v+,那么对于这个图而言拆点后,每个点分为u+,u-.观察得出,这个图永远是个二分图(按照只有出边还是只有入边分)。所以自然会联想到用最大流求二分图匹配的算法。那么这里显然不是最大流,仔细想想,会发现和最大流的对偶问题——最小割有关。
咋建图呢?可新建一个S,T. S连接所有u- 边权是w- T连接所有u+ 边权是w+ 在原图的每条边上连上正无穷大小边(目的是不让这些边被选为最小割中的边)。然后用dinic跑最小割就行(观察发现所有的割边都是S,u or u,T ,而这些边的权值已经被赋值成了w- or w+ ,而原图中每一条边都会被算在最小割里面,实现了原问题与最小割的一一对应)
然后还需要求出操作方案,而原问题的每个操作都对应到二分图中的每个点,所以求操作方案其实等价于求最小权的点覆盖集。这里结合证明过程中根据最小割构造点覆盖集的方法。先从源点开始往下搜,所有能走到的点在 S 集合,所有走不到的点在 T 集合,然后枚举所有正向边,找出所有割边(起点在 S,终点在 T 的边),然后将所有割边中除了源点、汇点的点都找出来,就是最小权的点覆盖集。
#include<bits/stdc++.h>
using namespace std;
const int MX_N=510,MX_M=50100;
struct node{
int next,to,w;
}edge[MX_M<<1];
int head[MX_N]={0},edge_cnt=0;
inline void Add(int x,int y,int w){
node &i=edge[edge_cnt];
i.next=head[x],i.to=y,i.w=w;
head[x]=edge_cnt++;
}
inline void add(int x,int y,int w){
Add(x,y,w),Add(y,x,0);
}
int s=0,t=MX_N-1;
int cur[MX_N]={0},dist[MX_N]={0};
bool bfs(){
for(int i=0;i<MX_N;i++) dist[i]=-1,cur[i]=head[i];
queue<int >qu;
qu.push(s);dist[s]=0;
while(!qu.empty()){
int now=qu.front();qu.pop();
for(int i=head[now];~i;i=edge[i].next){
int to=edge[i].to,w=edge[i].w;
if(dist[to]==-1&&w){
dist[to]=dist[now]+1;
qu.push(to);
}
}
}
return dist[t]!=-1;
}
int dfs(int now,int flow){
if(now==t) return flow;
int left=flow;
for(int &i=cur[now];~i;i=edge[i].next){
int to=edge[i].to,w=edge[i].w;
if(dist[to]==dist[now]+1&&w){
int cur_flow=dfs(to,min(w,left));
left-=cur_flow;
edge[i].w-=cur_flow;
edge[i^1].w+=cur_flow;
if(left==0) break;
}
}
if(left==flow) dist[now]=-1;
return flow-left;
}
int dinic(){
int sum=0;
while(bfs()) sum+=dfs(s,0x3f3f3f3f);
return sum;
}
bool st[MX_N]={0};
void dfs1(int now){
st[now]=1;
for(int i=head[now];~i;i=edge[i].next){
int to=edge[i].to;
if(st[to]==0&&edge[i].w) dfs1(to);
}
}
signed main(){
memset(head,-1,sizeof(head));
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);//+
add(i+n,t,x);
}
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);//-
add(s,i,x);
}
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v+n,0x3f3f3f3f);
}
printf("%d\n",dinic());
dfs1(s);
int k=0;
for(int i=0;i<edge_cnt;i+=2){
int u=edge[i^1].to,v=edge[i].to;
if(st[u]&&!st[v]) k++;
}
printf("%d\n",k);
for(int i=0;i<edge_cnt;i+=2){
int u=edge[i^1].to,v=edge[i].to;
if(st[u]&&!st[v]){
if(v==t){
printf("%d +\n",u-n);
}
else if(u==s){
printf("%d -\n",v);
}
}
}
return 0;
}
最大权独立集
对于一个无向图,选取某些点使得两点之间没有直接连边,那么这个点集就是一个独立集。最大权独立集就是点权之和最大的独立集。
最大权独立集问题同样是一个 NP-完全问题,所以还是只能在二分图上考虑。
二分图的最大权独立集
要求:点权非负
证明一下最大权独立集与最小权覆盖集之间的对应关系。先看点覆盖集对应独立集。运用反证法,假设点覆盖集的补集不是独立集,那么在独立集中一定存在两点
使得这两个点都不在点覆盖集中,那么这两个点一定在独立集中,同样与定义矛盾。
至此,我们证明了最大权独立集等于点权之和减去最小权点覆盖集,把问题转化为了上一个模型。
2326. 王者之剑
本题让我们选一个起点,每一秒都会进行三种操作,综合看来每一秒都能走一步或不走,从而形成各种各样的走法,然后需要求出一种走法使拿走的宝石总价值最大
我们假设这个人并不傻,每走到一个格子,如果格子上有宝石,一定会拿走。
结合题意,偶数秒会使上、下、左、右的格子上的宝石消失,
因此得出第一个性质:只能在偶数秒拿宝石。
和第一个性质一样的原理,还能得到第二个性质:不能同时拿走相邻格子上的宝石。
如果将相邻两个格子之间都连一条边,则能拿的宝石一定是一个独立集。而每个格子上都有一个权值,又是求获得宝石的最大值,可以发现本题已经非常像最大权独立集问题。
到此已经能将任意一个合法方案对应到二分图中的一个独立集。但是还需要证明任意一个二分图中的独立集都能对应到一个合法方案。其实对于任意一个独立集都能构造出一个合法方案,可以从最左上角的一个有宝石的格子开始走,依次去取别的宝石,假设当前距离下一个宝石还剩两步,停下来判断一下,如果当前是偶数秒,直接走过去拿宝石,如果当前是奇数秒,原地停一秒再走过去拿宝石。且保证每次都优先取最近的宝石。这样的行走方案一定能将独立集中的所有宝石拿走,可以自行按照以上思路证明,这里的构造方式非常多,只要掌握好停顿一秒的精髓就能随便构造。
由此得出任意一个合法方案和任意一个独立集都是一一对应的,因此要想求最大能取的宝石价值就等价于求最大权独立集,而
#include<bits/stdc++.h>
using namespace std;
const int MX_N=50100,MX_M=5010000;
struct node{
int next,to,w;
}edge[MX_M];
int edge_cnt=0,head[MX_N]={0};
inline void Add(int x,int y,int w){
node &i=edge[edge_cnt];
i.to=y,i.w=w,i.next=head[x];
head[x]=edge_cnt++;
}
inline void add(int x,int y,int w){
Add(x,y,w),Add(y,x,0);
}
int cur[MX_N]={0},dist[MX_N]={0};
int s,t;
bool bfs(){
for(int i=0;i<MX_N;i++) dist[i]=-1,cur[i]=head[i];
dist[s]=0;queue<int >qu;qu.push(s);
while(!qu.empty()){
int now=qu.front();qu.pop();
for(int i=head[now];~i;i=edge[i].next){
int to=edge[i].to,w=edge[i].w;
if(dist[to]==-1&&w){
dist[to]=dist[now]+1;
qu.push(to);
}
}
}
return dist[t]!=-1;
}
int dfs(int now,int flow){
if(now==t) return flow;
int left=flow;
for(int &i=cur[now];~i;i=edge[i].next){
int to=edge[i].to,w=edge[i].w;
if(dist[to]==dist[now]+1&&w){
int cur_flow=dfs(to,min(left,w));
left-=cur_flow;
edge[i].w-=cur_flow;
edge[i^1].w+=cur_flow;
if(left==0) break;
}
}
if(left==flow) dist[now]=-1;
return flow-left;
}
int dinic(){
int sum=0;
while(bfs()) sum+=dfs(s,0x3f3f3f3f);
return sum;
}
int mapn[200][200]={0};
int fx[5]={0,1,0,-1,0};
int fy[5]={0,0,1,0,-1};
int n,m;
inline bool check(int x,int y){
return x>=1&&x<=n&&y>=1&&y<=m;
}
inline int has(int x,int y){
return (x-1)*m+y;
}
signed main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
s=0,t=n*m+10;int sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&mapn[i][j]);sum+=mapn[i][j];
if((i+j)%2==0){
add(s,has(i,j),mapn[i][j]);
}
else{
add(has(i,j),t,mapn[i][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=4;k++){
int dx=i+fx[k],dy=j+fy[k];
if(check(dx,dy)&&(i+j)%2==0){
add(has(i,j),has(dx,dy),0x3f3f3f3f);
}
}
}
}
printf("%d",sum-dinic());
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App