图论中的第K小值

图论中的第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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值