- “最大权值闭合图,即给定一张有向图,每个点都有一个权值(可以为正或负或0),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。”
- 这样的定义可以让你理解算法执行的逻辑,却难以在你赛场上遇到它时牵动你的思绪
- 更符合你做题时真切感受的描述应该是:给你一些点,消耗一些点的代价可以得到另一些点的收益,要求收益最大化。 设想情境
- 前置思考:最小割模型中两个集合的性质分别由源点和汇点刻画;求出最小割后,边权再无意义,我们只关心图的连通情况
- 建模方法是:提取模型,点的取否类似于最小割中的点集划分,A->B类似于最小割中的“防止割断”;点边转化,把点的权值体现在边上;正负分类,源点连接收益点,汇点连接代价点(考虑到边权非负);源点为超级置位点,汇点为超级重置点,这样,割断s->x意味着不选某个收益点;割断y->t意味着选择某个代价点,两者均使得总收益减少,故要求最小割,初始所有的边都未被割断,对于s,意味着选择所有收益点;对于t,意味着不选所有代价点,得到初始收益
点击查看代码
#include <bits/stdc++.h>
using namespace std;
long long num[105];
int pr1[205],pr2[205],l[205];
int s,t,flow;
vector<int>a[205],c[205],d[205];
void add(int u,int v,int w)
{
a[u].push_back(v);
a[v].push_back(u);
c[u].push_back(w);
c[v].push_back(0);
d[u].push_back(a[v].size()-1);
d[v].push_back(a[u].size()-1);
}
void update()
{
int cur=t;
flow+=l[t];
while(cur!=s)
{
c[pr1[cur]][pr2[cur]]-=l[t];
c[cur][d[pr1[cur]][pr2[cur]]]+=l[t];
cur=pr1[cur];
}
}
long long v[205];
int q[3000005];
bool bfs()
{
memset(v,false,sizeof(v));
q[1]=s;
l[s]=INT_MAX;
int L=0,r=1;
while(L<r)
{
L++;
int n1=q[L];
for(int i=0;i<a[n1].size();i++)
{
if(v[a[n1][i]]==false&&c[n1][i]>0)
{
r++;
q[r]=a[n1][i];
v[a[n1][i]]=true;
l[a[n1][i]]=min(l[n1],c[n1][i]);
pr1[a[n1][i]]=n1;
pr2[a[n1][i]]=i;
if(a[n1][i]==t)
{
return true;
}
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=0;i<=200;i++)
{
a[i].clear();
c[i].clear();
d[i].clear();
}
s=199,t=200;
for(int i=1;i<=n;i++)
{
cin>>num[i];
add(s,i+60,1);
for(int j=0;j<60;j++)
{
if((num[i]>>j)&1)
{
add(i+60,j,n);
}
}
}
for(int i=0;i<60;i++)
{
add(i,t,1);
}
flow=0;
while(bfs())
{
update();
}
cout<<n-flow<<endl;
}
return 0;
}