week8作业:差分约束、kahn拓扑排序、kosaraju强联通分量问题

A/D

题意

区间选点升级版:给定一个数轴上的n个区间,要求在数轴上选取最少的点使得第i个区间里至少有ci个点

  • 输入
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
  • 输出
6

思路

  • 题目定位:差分约束
  • 构造不等式组:
    -1. 对于第i个区间[𝑎i, 𝑏i]需要满足𝑠𝑢𝑚 𝑏i − 𝑠𝑢𝑚 𝑎i − 1 ≥ 𝑐i
    -2. 0 ≤ 𝑠𝑢𝑚 𝑖 − 𝑠𝑢𝑚 𝑖 − 1 ≤ 1
  • 转化为≥不等式组跑最长路,𝑠𝑢𝑚[max{𝑏i}]即为答案

总结

  • 差分形式的不等式组,可以转换为图上求最短/长路的问题求解

仍然不是很明白为什么把spfa处理环的部分删了,D就能过…也许写错了???
然而A能过…略魔幻…

代码

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;

const int MAXN=50005;
const int INF=50005;

struct Edge{
	int to,w,next;
} edges[3*MAXN];
int head[3*MAXN],tot;
void addEdge(int from,int to,int w){
	edges[++tot].to=to;
	edges[tot].w=w;
	edges[tot].next=head[from];
	head[from]=tot;	
}

int N,sum[MAXN];
int mina=INF,maxb=-1;

int vis[MAXN];
void spfa(int s){
    queue<int> q;
    vis[s]=1,sum[s]=0;
    q.push(s);

    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;

        for(int i=head[u]; i; i=edges[i].next){
            int v=edges[i].to,w=edges[i].w;
            if(sum[v]<sum[u]+w){
                sum[v]=sum[u]+w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }

}



int main(void){
	scanf("%d",&N);
	for(int i=0;i<N;i++){
		int ta,tb,tc;
		scanf("%d%d%d",&ta,&tb,&tc);
		addEdge(tb,ta-1,tc);
		maxb=max(tb,maxb);
		mina=min(ta,mina);
	}
	for(int i=mina;i<=maxb;i++){
		addEdge(i,i-1,0);
		addEdge(i-1,i,-1);
	}

	for(int i=mina-1;i<=maxb;i++)
		sum[i]=-INF;
	spfa(maxb);
	printf("%d\n",sum[mina-1]);

	return 0;
}

B

题意

一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。已知每场比赛的结果,确定字典序最小的名次序列。

思路

  • 问题定位:拓扑排序
  • 利用算法:Kahn算法
  • 思路:每次从优先队列中将入度为0的点取出,添加至答案向量中。每处理一个入度为0的点,要将该点连接的各个点的入度-1.

总结

用矩阵WA了一版,但是感觉思路是一样的,不太明白为什么换成了邻接表就AC了…

  • 拓扑排序模板
    在这里插入图片描述

代码

#include<cstdio>
#include<queue>
#include<vector>
#include<functional>
using namespace std;


const int MAXN=505;
int N,M;
int in_degree[MAXN];
vector<int> G[MAXN];

void init(){
	for(int i=0;i<=N;i++){
		in_degree[i]=0;
		G[i].clear();
	}
}

void topology_sort(){
	priority_queue<int,vector<int>,greater<int>> pq;
	for(int i=1;i<=N;i++)
		if(in_degree[i]==0)
			pq.push(i);

	vector<int> ans;
	while(!pq.empty()){
		int p=pq.top();
		pq.pop();
		ans.push_back(p);
		for(auto x:G[p]){
			in_degree[x]--;
			if(in_degree[x]==0)
				pq.push(x);
		}
	}

	for(int i=0;i<ans.size();i++){
		if(i!=0) printf(" ");
		printf("%d",ans[i]);
	}
	printf("\n");
}


int main(void){
	while(scanf("%d%d",&N,&M)!=EOF){
		init();
		for(int i=0;i<M;i++){
			int P1,P2;
			scanf("%d%d",&P1,&P2);
			G[P1].push_back(P2);
			in_degree[P2]++;
		}
		topology_sort();
	}
	return 0;
}
  • WA的版本(用的矩阵,感觉也没错???)
#include<cstdio>
#include<queue>
#include<vector>
#include<functional>
using namespace std;


const int MAXN=505;
int N,M;
int G[MAXN][MAXN],in_degree[MAXN];

void init(){
	for(int i=1;i<=N;i++){
		in_degree[i]=0;
		for(int j=1;j<=N;j++)
			G[i][j]=0;
	}
}

void topology_sort(){
	priority_queue<int,vector<int>,greater<int>> pq;
	for(int i=1;i<=N;i++)
		if(in_degree[i]==0)
			pq.push(i);

	vector<int> ans;
	while(!pq.empty()){
		int p=pq.top();
		pq.pop();
		ans.push_back(p);
		for(int i=1;i<=N;i++){
			if(G[p][i]==1){
				G[p][i]=0;
				in_degree[i]--;
				if(in_degree[i]==0)
					pq.push(i);
			}
		}
	}

	for(int i=0;i<ans.size();i++){
		if(i!=0) printf(" ");
		printf("%d",ans[i]);
	}
	printf("\n");
}


int main(void){
	while(scanf("%d%d",&N,&M)!=EOF){
		init();
		for(int i=0;i<M;i++){
			int P1,P2;
			scanf("%d%d",&P1,&P2);
			G[P1][P2]=1;
			in_degree[P2]++;
		}
		topology_sort();
	}
	return 0;
}

C

题意

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适。
现想要知道最高票数,并给出一份候选人名单。

思路

  • 问题定位:图,强连通分量的问题(SCC)
  • 问题算法思想:Kosaraju算法
  • 思路:
    1. Kosaraju算法步骤:
      -第一遍dfs 确定原图的逆后序序列
      -第二遍dfs 在反图中按照逆后序序列进行遍历,每次由起点遍历到的点即构成一个SCC
    1. 缩点:每一个SCC看成1个点,重构新的图和反图,并记录图的出度
    1. 最终答案一定出现在缩点图,出度为0的SCC部分。
  • 答案分为2部分:1.自己SCC的内部互投 2.别的SCC投进来的

总结

Kosaraju算法模板:
在这里插入图片描述

代码

#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;

const int MAXN=5005;
int N;

vector<int> G1[MAXN],G2[MAXN];
int dfn[MAXN],dcnt,c[MAXN],scnt,vis[MAXN];
void dfs1(int x){
	vis[x]=1;
	for(auto y:G1[x])
		if(!vis[y]) dfs1(y);
	dfn[++dcnt]=x;
}
void dfs2(int x){
	c[x]=scnt;
	for(auto y:G2[x])
		if(!c[y]) dfs2(y);
}
void kosaraju(){
	dcnt=scnt=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	memset(dfn,0,sizeof(dfn));

	for(int i=0;i<N;i++)
		if(!vis[i]) dfs1(i);

	for(int i=N-1;i>=0;i--)
		if(!c[dfn[i]]) ++scnt,dfs2(dfn[i]);
}

vector<int> G3[MAXN],G4[MAXN];
int out_degree[MAXN],SCC[MAXN];
void shrink(){
	memset(out_degree,0,sizeof(out_degree));
	memset(SCC,0,sizeof(SCC));
	for(int x=0;x<N;x++){
		SCC[c[x]]++;
		for(auto y:G1[x]){
			if(c[x]==c[y])
				continue;
			G3[c[x]].push_back(c[y]);
			G4[c[y]].push_back(c[x]);
			out_degree[c[x]]++;
		}
	}
}


void dfs3(int x){
	vis[x]=1;
	for(auto y:G4[x])
		if(!vis[y]) dfs3(y);
}

vector<int> SCC_points;
void solve(){
	int ans=0;
	for(int i=1;i<=scnt;i++){
		memset(vis,0,sizeof(vis));

		if(out_degree[i]==0){
			dfs3(i);
			int temp=SCC[i]-1;
			for(int j=1;j<=scnt;j++){
				if(i!=j&&vis[j])
					temp+=SCC[j];
			}

			if(temp>ans){
				ans=temp;
				SCC_points.clear();
				SCC_points.push_back(i);
			}
			else if(temp==ans) SCC_points.push_back(i);
		}
	}

	printf("%d\n",ans);
	int pre=0;
	for(int i=0;i<N;i++){
		for(auto x:SCC_points){
			if(c[i]==x){
				if(pre==0) pre=1;
				else if(pre!=0) printf(" ");
				printf("%d",i);
			}
		}
	}
	printf("\n");
}

void init(){
	for(int i=0;i<N;i++){
		G1[i].clear();
		G2[i].clear();
		G3[i].clear();
		G4[i].clear();
	}
	SCC_points.clear();
}



int main(void){
	//freopen("input.txt","r",stdin);

	int T;
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		init();

		int M;
		scanf("%d%d",&N,&M);
		while(M--){
			int A,B;
			scanf("%d%d",&A,&B);
			G1[A].push_back(B);
			G2[B].push_back(A);
		}
		kosaraju();
		shrink();
		printf("Case %d: ",i);
		solve();
	}
	//fclose(stdin);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值