图论中的第K小值
对于第K小值,通常使用优先队列+dfs或者二分+dfs,具体以下面题目为例。
2019牛客多校第二场D-Kth Minimum Clique
题意
给定一张 n(n≤ 100) 个点的图,求第 k 小点权完全子图。
分析
优先队列+dfs
将每个完全子图压入队列中,点权小的优先出队。为了在dfs遍历时不重不漏,先将每个点按照点权值从小到大排序,并且记录上次已经遍历过的位置。
那么如何判断它是一张完全子图呢?
在结构体中用vecor记录已经遍历过的完全子图的所有点。
注意:不能忽略0.
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105;
int n,k;
bool ma[N][N];//记录边的关系
ll u[N];
struct node{
ll weight;//记录这张完全子图的权值
int z;//vetor中压入最后一个点的位置(即dis.size()-1)
vector<int>dis;//记录点
bool operator<(const node &x)const
{
return weight>x.weight;
}
}a[N],cur;
priority_queue<node>q;
int main()
{
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>u[i];
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
for(int j=0;j<s.length();j++)
ma[i][j+1]=(s[j]=='1'?1:0);
}
k--;//去掉0;
if(k==0)
{
cout<<0<<endl;
return 0;
}
for(int i=1;i<=n;i++)//先将一个点的压入队列
{
a[i].weight=u[i];
a[i].z=0;
a[i].dis.push_back(i);
q.push(a[i]);
}
while(!q.empty())
{
cur=q.top();
k--;
q.pop();
if(k==0)
{
cout<<cur.weight<<endl;
return 0;
}
int now=cur.dis[cur.z]+1;//防止重复遍历
for(int i=now;i<=n;i++)
{
bool flag=1;
for(int j=0;j<=cur.z;j++)
{
if(ma[cur.dis[j]][i]==0)//如果出现不是完全子图的情况
{
flag=0;
break;
}
}
if(flag)
{
cur.weight+=u[i];
cur.z++;
cur.dis.push_back(i);
q.push(cur);//更新值压入队列
cur.weight-=u[i];//类似于回溯
cur.z--;
cur.dis.pop_back();
}
}
}
cout<<-1<<endl;//如果不存在第k小完全子图
return 0;
}
hdu 6041 I Curse Myself
题意
给一张仙人掌图,求第 k 小生成树
分析
二分+dfs
将一张仙人掌图转化成树,即需要找到仙人掌图上的环,并从每个环中删除一条边,即可构成一棵树。
我们可以将问题转化为,求删除元素总和中的第K大,等价于每个数组选出一个数字求和,求这些 和中第 k 大元素。
能否进一步化简呢?
我们可以将每个数组中的元素转化为和数组中最大数字的差值,同时将数组中元素数量减少一,并将 k 大值问题转化为 k 小值问题 。
所以,我们可以二分值,dfs搜索统计验证。
1.先将点边关系记录在结构体中,建立关系
2.将图中形成的环的边取出(对应代码void Tarjan() )。
3.将每个环中的边从大到小排序,并转化成和这个环中最大数值的差值,并将每个环排序。
4.二分值(树的权值),dfs计算符合该值的个数,求出个数大于等于k的值(树的权值)
5.处理答案,因为在二分检验的时候,遇到个数大于等于k的情况就return了,所以无法记录答案,则我们需要将所得的值(树的权值)-1,计算结果。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const ll N = 2e3+10;
const ll INF = 0x3f3f3f3f3f3f3f;
const ll mod = 1ll<<32;
const ll M = 1e6+10;
ll n,m,sum,cas,a,b,c,tot,k;
ll head[N];
struct node{
ll v,w,ne;
}edge[N<<1];
void addedge(ll a,ll b,ll c)
{
edge[++tot].v=b;
edge[tot].w=c;
edge[tot].ne=head[a];
head[a]=tot;
}
ll dfs_num,cnt,dfn[N];
vector<ll>E[N];
PII low[N];
void Tarjan(ll u)//将环中的边取出
{
dfn[u]=++dfs_num;
for(ll i=head[u];i;i=edge[i].ne)
{
ll v=edge[i].v,w=edge[i].w;
if(low[u].first==v)//有重边
continue;
if(!dfn[v])
{
low[v].first=u;//记录v前一个位置u
low[v].second=w;//记录v,u之前的边权值
Tarjan(v);//深搜
}
else if(dfn[u]>dfn[v])//成环,记录这个环中的所有边
{
E[cnt].push_back(w);
for(ll k=u;k!=v;k=low[k].first)
E[cnt].push_back(low[k].second);
cnt++;
}
}
}
bool cmp1(const ll &a,const ll &b)
{
return a>b;
}
bool cmp2(const vector<ll> &a,const vector<ll> &b)
{
return a[1]<b[1];
}
void get()
{
for(int i=0;i<cnt;i++)
{
sort(E[i].begin(),E[i].end(),cmp1);//从大到小排序
sum-=E[i][0];//先将所有环中的最大值去掉
for (int j=1;j<E[i].size();j++)//转化成和这个环中最大数值的差值
E[i][j]=E[i][0]-E[i][j];
E[i][0]=0;
}
sort(E,E+cnt,cmp2);
ll ma=1;
bool flag=0;
for(int i=0;i<cnt;i++)//计算可能的个数
{
ma*=E[i].size();
if(ma>=k)
{
flag=1;
break;
}
}
if(!flag)
k=ma;
}
ll res[M],p,t,ans,an;
ll le,ri,mid;
void dfs(ll num,ll cou)
{
if(cou==cnt||p>=k)//若已经遍历到最后一个环或者个数大于等于K个,就return
return;
if(num+E[cou][1]>mid)//如果当前环中的第一个都没法满足
return;
for(int i=1;i<E[cou].size();i++)
{
an=num+E[cou][i];
if(an>mid)
break;
res[++p]=an;
if(p>=k)
return;
dfs(an,cou+1);//取这个数
}
dfs(num,cou+1);//不取这个数
}
void Binary_Search()
{
le=0;ri=INF;
while(le<ri)
{
mid=(le+ri)/2;
p=1;//此处p为1,是因为最小的情况就是所有环中最大边都删去
dfs(0,0);
if(p>=k)
ri=mid;
else
le=mid+1;
}
ll pre=ri;
mid=ri-1;//将二分结果减一,再去dfs一遍,得出答案
p=1;
res[p]=0;
dfs(0,0);
for(int i=p+1;i<=k;i++)//如果个数不够,那么说明剩余情况都是pre(即二分结果)
res[i]=pre;
sort(res+1,res+k+1);
for(int i=1;i<=k;i++)
{
res[i]+=sum;//重新把它加上
ans=(ans+(ll)res[i]*i%mod)%mod;
}
}
void init()//初始化
{
fill(dfn,dfn+n+1,0);
fill(head,head+n+1,0);
fill(low,low+1+n,PII(0,0));
for(int i=0;i<n;i++)
E[i].clear();
cnt=tot=dfs_num=sum=ans=0;
}
int main()
{
while(~scanf("%lld %lld",&n,&m))
{
init();
for(int i=1;i<=m;i++)
{
scanf("%lld %lld %lld",&a,&b,&c);
addedge(a,b,c);
addedge(b,a,c);
sum+=c;
}
scanf("%lld",&k);
Tarjan(1);
get();
Binary_Search();
printf("Case #%d: %lld\n",++cas,ans);
}
}