程序设计思维与实践 Week8 作业

程序设计思维与实践 Week8 作业

  • A - 区间选点 II
    • 题目描述
    • 题目分析
    • 代码
  • B - 猫猫向前冲
    • 题目描述
    • 题目分析
    • 代码
  • C - 班长竞选
    • 题目描述
    • 题目分析
    • 代码

A - 区间选点 II

题目描述

给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点

使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
  1. 输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
  2. 输出一个整数表示最少选取的点的个数
    -input:
    5
    3 7 3
    8 10 3
    6 8 1
    1 3 1
    10 11 1
  3. output:
    6

题目分析

最近学的差分约束,这道题也要求使用差分约束做(我也想知道还有啥其他方式做),那么我们要求最小值就是构造大于号以及求最大路了

  1. 首先我们构造条件设计数组sum[i]代表从0到i所选点的个数,则根据题意得,sum[bi]-sum[ai-1]>=ci,是一个条件,还有我们要保证每个点只能是选一个或者不选,所以还有1>=sum[k]-sum[k-1]>=0,这样构造好了之后,就可以使用spfa进行最长路求解了
  2. 注意点:A和D点得差异应该是对于第一个点,如果使用上述的坐标系,我们无法保证第一个点的约束,因为不存在1>=sum[0]-sum[-1]>=0,所以我们把上述的坐标轴向右移动一格,

代码

#include
#include
using namespace std;
const int nmax=5E5+10;
const int inf=1E9;
int dis[nmax],inq[nmax],head[nmax],cnt[nmax];
int n,m,k,tmp,tot=0;
struct edge{
	int to,next,w;
}edges[nmax];
void add(int x,int y,int z){
	edges[++tot].to=y,edges[tot].w=z;
	edges[tot].next=head[x],head[x]=tot;
}
void init(){
	fill(dis,dis+nmax,-inf);
	fill(inq,inq+nmax,0);
	//fill(cnt,cnt+nmax,1);
}
void spfa(int st){//最长路
	init();
	queue<int> que;
	dis[st]=0;inq[st]=1;
	que.push(st);
	while(!que.empty()){
		int x=que.front();que.pop();inq[x]=0;
		for(int i=head[x];i;i=edges[i].next){
			int y=edges[i].to,w=edges[i].w;
			if(dis[y]<dis[x]+w){
				dis[y]=dis[x]+w;
				cnt[y]=cnt[x]+1;
				//if(cnt[y]>=n) 
				if(!inq[y]) {que.push(y);inq[y]=1;}
			}
		}
	}
}

int main(){
	init();
	scanf("%d",&n);
	int a,b,c,end=-1;
	for(int i=0;i<n;i++){
		scanf("%d%d%d",&a,&b,&c);
		if(b+1>end) end=b+1;
		add(a,b+1,c);
	}
	for(int i=1;i<=end;i++){
		add(i-1,i,0);
		add(i,i-1,-1);
	}
	spfa(0);
	printf("%d",dis[end]);
	return 0;
}

B - 猫猫向前冲

题目描述

众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。

  • 输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。
  • 给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
Sample Input

4 3
1 2
2 3
4 3

Sample Output

1 2 4 3

题目分析

这就是一个拓扑排序的题目,每个输入就是一个优先边,使用上课讲的kahn算法,一个像bfs一样的算法就能解决.首先遍历所有的边,将入度为0的放到最小堆中,之后每次取出堆顶,并且遍历他的所有的为出度的边,将每个到的点的入减1,相当于删去了这条边,并检查此时到的点的入度,为0则放入堆,直到堆为空,最小堆保证了最小的序列

代码

#include
#include
#include
using namespace std;
const int nmax=5E2+10;
const int inf=1E9;
int head[nmax],cnt[nmax];
int n,m,k,tmp,tot=0;
vector<int> res;
struct edge{
	int to,next,w;
}edges[nmax];
void add(int x,int y,int z){
	edges[++tot].to=y,edges[tot].w=z;
	edges[tot].next=head[x],head[x]=tot;
}
void init(){
	//fill(dis,dis+nmax,-inf);
	//fill(inq,inq+nmax,0);
	fill(cnt,cnt+nmax,0);
	fill(head,head+nmax,0);
	tot=0;
	res.clear();
}
void kahn(){
	priority_queue<int> heap;
	for(int i=1;i<=n;i++){if(cnt[i]==0) heap.push(-i);}
	while(!heap.empty()){
		int x=-heap.top();heap.pop();
		res.push_back(x);
		for(int i=head[x];i;i=edges[i].next){
			int y=edges[i].to;
			--cnt[y];
			if(!cnt[y]) heap.push(-y);
		}
	}
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		init();
		int a,b;
		for(int i=0;i<m;++i){
			scanf("%d%d",&a,&b);
			++cnt[b];
			add(a,b,1);
		}
		kahn();
		for(auto iter=res.begin();iter!=res.end();++iter){
			printf(iter==res.begin()?"%d":" %d",(*iter));
		}
		printf("\n");
	}
	return 0;
}

C - 班长竞选

题目描述

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

  1. 本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0

  2. 对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

  3. Sample Input
    2
    4 3
    3 2
    2 0
    2 1

    3 3
    1 0
    2 1
    0 2

  4. Sample Output
    Case 1: 2
    0 1
    Case 2: 2
    0 1 2

题目分析

这道题就是一个先求出所有的强连通分量,后将分量缩点,后遍历的题目

1.首先使用kosaraju算法,这个算法可以通过逆后序序列求出所有的强连通分量
求得逆后续序列于vetcor里:

void dfs(int st){
	if(vis[st]) return ;
	vis[st]=1;
	for(int i=head[st];i;i=edges[i].next){
		dfs(edges[i].to);
	}
	res.push_back(st);
}

使用kosaraju,遍历逆后序序列,使用dfs每次遍历到的都是同一个强连通分量,遍历后标记在c数组中
调用:

for(auto iter=res.begin();iter!=res.end();++iter){
		int key=*iter;
		if(!c[key]) {scnt++;cc[scnt]=1;dfs_scc(key);} 
	}

遍历

void dfs_scc(int st){
	c[st]=scnt;
	for(int i=head_revr[st];i;i=revr[i].next){
		int y=revr[i].to;
		if(!c[y]) {dfs_scc(y);++cc[scnt];}
	}
}

这样后我们就可以得到一个标记好1-n的分量了,我们再开一个图,进行缩点

for(int i=0;i<n;++i){
		for(int j=head[i];j;j=edges[j].next){
			if(c[i]!=c[edges[j].to]){
				st.insert(make_pair(c[edges[j].to],c[i]));
			}
		}
	}
	for(auto it=st.begin();it!=st.end();it++){
		add(it->first,it->second,3);
		//printf("%d->%d\n",it->first,it->second);
	}

遍历所有的点,如果不是同一个连通分量就建立边,用set防止重复的边,并且标记出读,因为最后的结果最多的点一定是出度为零的
之后遍历所有出度为0的点,计算出他们的票数

代码

#include
#include
#include
#include
#include
using namespace std;
const int nmax=5E4+10;
const int inf=1E9;
int head[nmax],vis[nmax],head_revr[nmax],c[nmax],cc[nmax],dis[nmax],ak[nmax];
int myhead[nmax];
int n,m,tot1=0,tot2=0,myround,scnt=0,mytot=0;
vector<int> res;
set<pair<int,int> > st;
struct edge{
	int to,next,w;
}edges[nmax],revr[nmax],myedge[nmax],*ped;

void add(int x,int y,int z){
	int *hh,*tot;
	if(z==1){ped=edges,hh=head,tot=&tot1;}
	else if(z==2){ped=revr,hh=head_revr,tot=&tot2;}
	else {ped=myedge,hh=myhead,tot=&mytot;dis[y]++;}
	(*tot)++;
	ped[*tot].to=y;
	ped[*tot].next=hh[x],hh[x]=*tot;
}
void init(){
	st.clear();
	res.clear();
	fill(ak,ak+n+1,0);
	fill(myhead,myhead+n+1,0);
	fill(head,head+n+1,0);
	fill(vis,vis+n+1,0);
	fill(head_revr,head_revr+n+1,0);
	fill(c,c+n+1,0);
	fill(cc,cc+n+1,0);
	fill(dis,dis+n+1,0);
	tot1=1,tot2=1,scnt=0,mytot=0;
}
void dfs(int st){
	if(vis[st]) return ;
	vis[st]=1;
	for(int i=head[st];i;i=edges[i].next){
		dfs(edges[i].to);
	}
	res.push_back(st);
}
void dfs_scc(int st){
	c[st]=scnt;
	for(int i=head_revr[st];i;i=revr[i].next){
		int y=revr[i].to;
		if(!c[y]) {dfs_scc(y);++cc[scnt];}
	}
}
int dfs_get(int st){
	if(vis[st]) return 0;
	int ans=0;
	vis[st]=1;
	for(int i=myhead[st];i;i=myedge[i].next){
		ans+=dfs_get(myedge[i].to);
	}
	return ans+cc[st];
}
void kosaraju(){
	for(int i=0;i<n;++i) dfs(i);
	reverse(res.begin(),res.end());//得到后逆序列
	for(auto iter=res.begin();iter!=res.end();++iter){
		int key=*iter;
		if(!c[key]) {scnt++;cc[scnt]=1;dfs_scc(key);} 
	}
	//for(int i=1;i<=scnt;i++) printf("dis[%d]=%d\n",i,dis[i]);
	for(int i=0;i<n;++i){
		for(int j=head[i];j;j=edges[j].next){
			if(c[i]!=c[edges[j].to]){
				st.insert(make_pair(c[edges[j].to],c[i]));
			}
		}
	}
	for(auto it=st.begin();it!=st.end();it++){
		add(it->first,it->second,3);
		//printf("%d->%d\n",it->first,it->second);
	}
	int mymax=-1;
	for(int i=1;i<=scnt;i++){
		if(!dis[i]) { fill(vis,vis+n+1,0);ak[i]=dfs_get(i);mymax=max(mymax,ak[i]);}
	}
	printf("%d\n",mymax-1);
	bool flag=true;
	for(int i=0;i<n;++i){
		int tmp_scnt=c[i];
		if(ak[tmp_scnt]==mymax) {
			if(flag){
				printf("%d",i);
				flag=false;
			}
			else printf(" %d",i);
		}
	}
}
int main(){
	int a,b;
	scanf("%d",&myround);
	for(int kp=1;kp<=myround;++kp){
		scanf("%d%d",&n,&m);
		init();
		for(int i=0;i<m;++i){
			scanf("%d%d",&a,&b);
			add(a,b,1);
			add(b,a,2);
		}
		printf("Case %d: ",kp);
		kosaraju();
		printf("\n");
	}
	return 0;
}

ps:其实我一开始写的是另一个,在做强连通分量的时候缩点,感觉应该可以,但是没过,求指教!代码如下:

#include
#include
#include
#include
using namespace std;
const int nmax=5E4+10;
const int inf=1E9;
int head[nmax],vis[nmax],head_revr[nmax],c[nmax],cc[nmax],dis[nmax];//cc[i]是每个强连通分量的中含有点的个数,dis是指向他的最大票数的强连通分量的的票数
int n,m,tot1=0,tot2=0,myround,scnt=0;
vector<int> res;
struct edge{
	int to,next,w;
}edges[nmax],revr[nmax],*ped;

void add(int x,int y,int z){
	int *hh,*tot;
	if(z==1){ped=edges,hh=head,tot=&tot1;}
	else {ped=revr,hh=head_revr,tot=&tot2;}
	(*tot)++;
	ped[*tot].to=y;
	ped[*tot].next=hh[x],hh[x]=*tot;
}
void init(){
	res.clear();
	fill(head,head+n+1,0);
	fill(vis,vis+n+1,0);
	fill(head_revr,head_revr+n+1,0);
	fill(c,c+n+1,0);
	fill(cc,cc+n+1,0);
	fill(dis,dis+n+1,0);
	tot1=1,tot2=1,scnt=0;
}
void dfs(int st){
	if(vis[st]) return ;
	vis[st]=1;
	for(int i=head[st];i;i=edges[i].next){
		dfs(edges[i].to);
	}
	res.push_back(st);
}
void dfs_scc(int st){
	c[st]=scnt;
	for(int i=head_revr[st];i;i=revr[i].next){
		int y=revr[i].to;
		if(!c[y]) {dfs_scc(y);++cc[scnt];}
		else if(c[y]!=scnt) dis[scnt]=max(dis[scnt],cc[c[y]]+dis[c[y]]);//防止循环吃自己家的
	}
}
void kosaraju(){
	for(int i=0;i<n;++i) dfs(i);
	reverse(res.begin(),res.end());//得到后逆序列
    //for(auto it=res.begin();it!=res.end();it++) printf("%d ",*it);
	for(auto iter=res.begin();iter!=res.end();++iter){
		int key=*iter;
		if(!c[key]) {scnt++;cc[scnt]=1;dfs_scc(key);} 
	}
	//for(int i=1;i<=scnt;i++) printf("dis[%d]=%d\n",i,dis[i]);
	int mymax=-1;
	vector<int> ans;
	for(int i=scnt;i>0;--i){
		mymax=max(mymax,dis[i]+cc[])
	}
	printf("%d\n",mymax-1);
	bool flag=true;
	for(int i=0;i<n;++i){
		int tmp_scnt=c[i];
		if(cc[tmp_scnt]+dis[tmp_scnt]==mymax) {
			if(flag){
				printf("%d",i);
				flag=false;
			}
			else printf(" %d",i);
		}
	}
}
int main(){
	int a,b;
	scanf("%d",&myround);
	for(int kp=1;kp<=myround;++kp){
		scanf("%d%d",&n,&m);
		init();
		for(int i=0;i<m;++i){
			scanf("%d%d",&a,&b);
			add(a,b,1);
			add(b,a,2);
		}
		printf("Case %d: ",kp);
		kosaraju();
		printf("\n");
	}
	return 0;
}

你可能感兴趣的:(程序设计思维与实践 Week8 作业)