我的作业之旅之程序设计思维WEEK八作业

A-区间选点

题干
在这里插入图片描述
思路
由题干可知该题利用差分约束,
每个区间看作一条边,
sum[ai—bi]=sum[bi]-sum[ai-1]>= ci
即sum[bi]>=sum[ai-1]+ci
dis[bi]>=dis[ai-1]+Wab
同时dis[i]-dis[i-1]>=0
dis[i-1]-dis[i]>=-1
即dis[i]>=dis[i-1]+0
dis[i-1]>=dis[i}-1
所以可以将[i-1,i]看作一条权值为0的边
将[i,i-1]看作一条权值为-1的边
这次取最长路径,也就是相同位置dis最大
代码

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int  n,a,b,c;//区间个数,区间端点和权值 
int dis[50010],vis[50010],l,r,tot,head[50010];
//存储最大距离,标记数组,区间左端点、右端点,head数组索引;
int cot[50010],vi[50010];
long long int inf=1e9;
queue <int> q;
struct edge{
	int to,next,w;
}edges[500100];//存储所有数组
void chushi()
{
	tot=0;
	for(int i=0;i<50010;i++)
	{
		dis[i]=-inf;
		vis[i]=0;
		head[i]=-1;
		cot[i]=0;
		vi[i]=0;
	}
 } 
void add(int x,int y,int w)
{
	edges[tot].to=y;
	edges[tot].next=head[x];
	edges[tot].w=w;
	head[x]=tot;
	tot++;
	//cout<<"加边"<<endl; 
}
void dfs(int s)//找到所有与负环相连的点,这些点都没有最短距离 
{
	vi[s]=1;
	for(int i=head[s];i!=-1;i=edges[i].next)
	{
		if(vi[edges[i].to]==0)
		dfs(edges[i].to);
	}
}
void SPFA()
{
	//cout<<1<<endl;
	while(!q.empty())
	q.pop();
	dis[l-1]=0;
	vis[l-1]=1;
	q.push(l-1);
	//cout<<1<<endl;
	while(!q.empty())
	{
		//cout<<2<<endl;
		int x=q.front();
		q.pop();
		//cout<<3<<endl;
		vis[x]=0;
		for(int i=head[x];i!=-1;i=edges[i].next)
		{
			//cout<<4<<endl;
			int y=edges[i].to;
			if(vi[y]!=1)
			{
				if(dis[y]<dis[x]+edges[i].w)
			{
				//cout<<5<<endl;
				cot[y]=cot[x]+1;
				if(cot[y]>n)
					dfs(y);
				dis[y]=dis[x]+edges[i].w;
				//cout<<6<<endl;
				if(!vis[y])
				{
					q.push(y);
					vis[y]=1;
					//cout<<7<<endl;
				}
				//cout<<8<<endl;
			}
			}
			
			//cout<<9<<endl;
		}
		//cout<<10<<endl;		
	}
	//return;
}
int main()
{
	scanf("%d",&n);
	chushi();
	l=50010;
	r=-1;
	for(int i=0;i<n;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a<l)
		l=a;
		if(b>r)
		r=b;
		add(a,b+1,c);
	}
	l++;
	r++;
	//cout<<l<<" "<<r<<endl;
	for(int i=l;i<=r;i++)
	{
		add(i-1,i,0);
		add(i,i-1,-1);
	}
	/*for(int k=0;k<n+r-l+1;k++)
	{
		cout<<edges[k].to<<endl;
	}*/
	//cout<<1<<endl;
	SPFA();
	//cout<<"SPAT结束\n"<<endl;
	int ans=dis[r];
	printf("%d",ans);
	return 0;
}

猫猫向前冲

题干
在这里插入图片描述
思路
用拓扑排序,首先把每一只猫当做一个点,每一个前后顺序当做一条边,然后构建有向无权图(head和edges[])。
然后用一个优先队列(因为要输出字典序最小的拓扑序列)存储图中所有入度为0的点,然后用遍历每个入度为0的点,遍历完就在队列中删去,将其放入输出队列。遍历的时候每个邻点入度减一,当其入度为0时也放入优先队列,直到最后优先队列为空。
代码

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,p1,p2;
int ru[505],head[505];
queue<int>q1;//存储输出序列
priority_queue<int>q2;//存储入度为零的点
struct edge{
	int to,next;
}edges[1010];
int tot;
void chushi()
{
	tot=0;
	for(int i=0;i<505;i++)
	{
		ru[i]=0;
		head[i]=-1;
	}
}
void add(int a,int b)
{
	ru[b]++;
	edges[tot].to=b;
	edges[tot].next=head[a];
	head[a]=tot;
	tot++;
 }
 void kahn()
 {
 	while(!q2.empty())
 	{
 		int x=-q2.top();//由于是大根堆,所以所有数都是存的相反数 
 		q2.pop();
 		q1.push(x);
		 for(int i=head[x];i!=-1;i=edges[i].next)
		 {
		 	ru[edges[i].to]--;
		 	if(ru[edges[i].to]==0)
		 	q2.push(-edges[i].to);
		  } 
	 }
 }
 int main()
 {
 	while(scanf("%d%d",&n,&m))
 	{
 		chushi();
 		for(int i=0;i<m;i++)
 		{
 			scanf("%d%d",&p1,&p2);
 			add(p1,p2);
		 }
		 for(int i=1;i<=n;i++)
		 {
		 	if(!ru[i])
		 	q2.push(-i);
		 }
		 kahn();
		 int num=q1.size();
		 int ans;
		 ans=q1.front();
		 q1.pop();
		 printf("%d",ans);
		 //cout<<ans;
		 for(int i=1;i<num;i++)
		 {
		 	ans=q1.front();
		 	q1.pop();
		 	printf(" %d",ans); 
		 	//cout<<" "<<ans;
		 }
		 
		/* while(q1.size())
		 {
		 	ans=q1.front();
		 	q1.pop();
		 	printf(" ");
		 	printf("%d",ans);
		 }*/
		 //printf("\n");
		 cout<<endl;
	 }
	 return 0;
 }

C-班长竞选

题干
在这里插入图片描述
思路
这个题用强连通分量
首先把人当做点,选举关系当做边,构造原图和反图(head、edges[]);
然后对原图的每个点进行一次dfs,得到所有点的逆后序(递归返回顺序的逆);
之后在反图上按照逆后序遍历每个点dfs,将同一个环的元素用同一个sc值标记,用sum【sc】表示每个scc的点个数;
然后缩点,sc的大小就是scc的个数,在第二次dfs时,当一个环遍历结束的时候,他遇到的下一个元素dis标记一定为1,此时判断这个点的sc值是否等于当前环的sc值,若不相等,说明scc的反图里有一条这样的边,在加完边之后还要注意重复,我用一个二维数组来标记,避免重复加边。
最后找票数最多的人,就是scc反图入度为0的人的所有个数,此时进行第三次dfs,所有与该scc相连的scc的点个数加上他本身的点个数再减去1,就是最后的值。最后再把所有入度为0的scc的值进行比较,找一个/若干个最大的输出。
代码

#include<iostream>
#include<queue>
//#include<vector> 
#include<algorithm>
#include<cmath>
using namespace std;
int T,n,m,a,b,tot;
int ni=0;//逆序索引 
int sc=0;//scc的索引 
int head1[5005], vis[5005],nih[5005],nihx[5005];
int head2[5005],scc[5005],sum[5005];
int head3[5005],ru[5005];//scc反图,入度
int vi[5005][5005];//标记scc图的边,防止重复插入边 
int sum2[5005];
//queue<int>q;
struct edge{
	int to,next;
}edges1[10010],edges2[10010],edges3[10010];
void add(int x,int y)
{
	edges1[tot].to=y;
	edges1[tot].next=head1[x];
	head1[x]=tot;
	edges2[tot].to=x;
	edges2[tot].next=head2[y];
	head2[y]=tot;
	tot++;
	
 } //构建正反图
void adsc(int x,int y)
{
	edges3[++tot].to=y;
	edges3[tot].next=head3[x];
	head3[x]=tot;
	ru[y]++;
	tot++;
}//x,y都是scc的索引 
 void dfs1(int s)
 {
 	/*for(int i=0;i<=n;i++)
 	{
 		vis[i]=0;
	 }*/
	 vis[s]=1;//标记初始化
	 //head
	 for(int i=head1[s];i!=-1;i=edges1[i].next)
	 {
	 	int x=edges1[i].to;
	 	if(vis[x]==0)
	 	dfs1(x);
	  }
	  ni++;
	  nih[s]=ni; 	 
  }//第一次正图dfs,将每个点遍历,得到逆后序列 
void tranni()
{
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			if(j==nih[i])
			nihx[j]=i;
		}
	}
}//将逆序序列正向存储到nihx里
void dfs2(int s)
{
	//cout<<2<<endl;
	 vis[s]=1;
	 scc[s]=sc;//一个环的用一个sc 
	 sum[sc]++;//记录同一个scc的点数 
	 //cout<<sum[sc]<<endl;
	 for(int i=head2[s];i!=-1;i=edges2[i].next)
	 {
	 	int x=edges2[i].to;
	 	if(vis[x]==0)
	 	dfs2(x);
	 	else//当该点被标记过,要么是同一个scc,要么是其他的scc 
	 	{
	 		if(scc[x]!=sc&&vi[sc][scc[x]]==0)//当是其他scc时,将边的关系插入新的scc反图	 
			{
				adsc(sc,scc[x]); 
				vi[sc][scc[x]]=1;
			 }
		 }
	  } 
	  //sc++;
	  
} //根据逆后序列dfs反图 ,将同一个环的scc的点标记为同一个数字 
void kosaraju()
{
	for(int i=0;i<5005;i++)
	{
		vis[i]=0;
		nih[i]=0;
		nihx[i]=0;
	}
	ni=0;
	for(int j=0;j<n;j++)
	{
		if(vis[j]==0)
		dfs1(j);
	}
	//for(int i=0;i<n;i++)
	//cout<<nih[i]<<endl;
	tranni();//得到后序nihx
	//for(int i=1;i<=n;i++)
	//cout<<nihx[i]<<endl;
	for(int i=0;i<5005;i++)
	{
		vis[i]=0;
		sum[i]=0;
		head3[i]=-1;
    }
    //cout<<sum[0]<<endl;
    for(int i=0;i<5005;i++)
        for(int j=0;j<5005;j++)
        	vi[i][j]=0;
    //cout<<vi[0][0]<<endl;
    tot=0;
    sc=0;
	 for(int i=n;i>0;i--)
	 {
	 	if(vis[nihx[i]]==0)
	 	{
	 		dfs2(nihx[i]);
	 		sc++;
		 }
	 }
	 //for(int i=0;i<n;i++)
	 	//cout<<scc[i]<<endl;
	//for(int i=0;i<sc;i++)
		//cout<<sum[i]<<endl;
 }
int dfs3(int s)
{
	vis[s]=1;
	int ans=sum[s];
	for(int i=head3[s];i!=-1;i=edges3[i].next)
	{
		int x=edges3[i].to;
		if(vis[x]==0)
		ans=ans+dfs3(x);
	}
	return ans;
 }
 int main()
 {
 	scanf("%d",&T);
 	int num=1;
 	while(T--)
 	{
 		scanf("%d%d",&n,&m);
 		tot=0;
 		for(int i=0;i<=n;i++)
 		{
 			head1[i]=-1;
 			head2[i]=-1;
 			ru[i]=0;
 			sum2[i]=0;
		 }
 		while(m--)
 		{
 			scanf("%d%d",&a,&b);
 			add(a,b);
 			//addf(b,a);
		 }
		 tot=0; 
		kosaraju();
		
		int ant=-1;
		//cout<<sc<<endl;
		for(int i=0;i<sc;i++)
		{
			//cout<<"*****"<<ru[i]<<endl;
			if(ru[i]==0)
			{
				//cout<<"&&&&&&&&"<<i<<endl;
				for(int i=0;i<=n;i++)
				vis[i]=0;
				sum2[i]=dfs3(i)-1;
				cout<<sum2[i]<<endl;
				ant=max(ant,sum2[i]);
			}
		}
		printf("Case %d: %d\n",num,ant);
		int f=0;
  		for(int i=0;i<n;i++)
  		{
   	    	if(sum2[scc[i]]==ant)
   	    	{
    			if(f==0)
     		   		printf("%d",i);
    			else 
					printf(" %d",i);
    			f++;
   	     	}     
  		}
  		printf("\n");
		num++;
	 }
	 return 0;
  } 

订正
在提交的时候我发现用以上这种方法空间复杂度太高,内存不够,在查询资料之后我发现比较普遍的算法是用vector数组,他可以用vector h[x][i]存储所有与点x相邻的点,比用edges[]数组存储临边关系要方便很多。

#include<iostream>
#include<cstring>
#include<vector> 
#include<algorithm>
using namespace std;
int T,n,m,a,b;
int c[5005],vis[5005],nih[5005],ni,sc; //scc标记数组,dfs遍历数组,存储逆后序数组 
vector<int> h1[5005],h2[5005],h3[5005];//原图,反图,缩点后新图 
int ru[5005],scc[5005],sum[5005];//缩点图入度 
void dfs1(int x)
{
	//cout<<1<<endl;
    vis[x]=1;
    for(int i=0;i<h1[x].size();i++)
    {
  		if(!vis[h1[x][i]])
   	    	dfs1(h1[x][i]);
    }
    nih[++ni]=x;
}//第一次dfs,得到逆后序 
void dfs2(int x)
{
	//vis[x]=1;
    c[x]=sc;
    for(int i=0;i<h2[x].size();i++)
    {
  		if(!c[h2[x][i]])
   	    	dfs2(h2[x][i]);
    }
}//第二次dfs,把同一个环的用c标记为一个sc 
void kosaraju()
{
	//cout<<"kosaraju"<<endl;
    ni=-1;
    sc=0;
    for(int i=0;i<5005;i++)
    {
    	c[i]=0;
    	vis[i]=0; 
    	nih[i]=0;
	}
	//cout<<"IIIIIIIII"<<endl;
	//cout<<n<<endl;
    for(int j=0;j<n;j++)
    {
    //	cout<<"&&&&&&&&&&"<<endl;
  		if(vis[j]==0) 
		  dfs1(j);
    }
    //cout<<"***********"<<endl;
    //for(int i=0;i<n;i++)
    //	cout<<nih[i]<<endl;
    //for(int i=0;i<5005;i++)
    //	vis[i]=0;
    for(int i=n-1;i>=0;i--)
    {
   		if(!c[nih[i]])
  		{
  			sc++;
    	    dfs2(nih[i]);
  		}
    }
}//完成前两次循环,得到一个个scc
int dfs3(int x)
{
    vis[x]=1;
    int ans=scc[x];
    for(int i=0;i<h3[x].size();i++)
    {
  		if(!vis[h3[x][i]])
   	    ans=ans+dfs3(h3[x][i]);
    }
    return ans;
}
int main()
{
	int num=1;
    scanf("%d",&T);
    while(T--)
    {
  		scanf("%d %d",&n,&m);
  		//cout<<"NNNNNNNNNNNNN"<<endl<<n<<endl;
  		int k=0;
  		int r=n;
  	while(r--)
    {
  		h1[k].clear();
  		h2[k].clear();
  		k++;
    }
    for(int i=0;i<5005;i++)
    {
    	ru[i]=0;
    	scc[i]=0;
    	sum[i]=0;
	}
	//cout<<"abababababab"<<endl;
  	while(m--)
  	{
   	    scanf("%d %d",&a,&b);
   	    h1[a].push_back(b);
   	    h2[b].push_back(a);
   	    //cout<<a<<" "<<b<<endl;
  	}
    kosaraju();
    //for(int i=0;i<n;i++)
    //{
    //	cout<<c[i]<<endl;
	//}
     for(int i=0;i<n;i++)
   		scc[c[i]]++; //索引是sc,里面放的是每个sc的集合数 
    for(int i=0;i<=sc;i++)
    {
  		ru[i]=0; 
  		h3[i].clear();
    }
    for(int i=0;i<n;i++)
    {
   		for(int j=0;j<h1[i].size();j++)//遍历每个点的临边 
        {
   	    	if(c[i]!=c[h1[i][j]])//当两个点不在一个scc中时,将这两个点所在sc加入新的图里 
   	    	{
    			h3[c[h1[i][j]]].push_back(c[i]);
    			ru[c[i]]++;
   	    	}
  		}
    }
  	int cnt=-1;
  	for(int i=1;i<=sc;i++)
  	{
 	    if(ru[i]==0)
   	    {
    		for(int j=0;j<=sc;j++)
     		    vis[j]=0;
    		sum[i]=dfs3(i)-1;
    		cnt=max(cnt,sum[i]);
   	    }
  	}
    printf("Case %d: %d\n",num,cnt); 
  	int f=0;
  	for(int i=0;i<n;i++)
  	{
   	    if(sum[c[i]]==cnt)
   	    {
    		if(f==0)
     		    printf("%d",i);
    		else 
				printf(" %d",i);
    		f++;
   	     }     
  	}
    printf("\n"); 
    num++;
    } 
     return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值