程序设计思维与实践 Week8 作业(区间选点Ⅱ、猫猫向前冲、班长竞选)

**

A - 区间选点 II

**
题目描述
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
使用差分约束系统的解法解决这道题

Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <=
50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。

Output
输出一个整数表示最少选取的点的个数

Sample Input

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

Sample Output

6

解题思路
记sum[i]表示数轴上[0,i]之间选点的个数,对于第i个区间[ai,bi]需要满足sum[bi]-sum[ai-1]>=ci,且要保证sum是有意义的,即0<=sum[i]-sum[i-1]<=1,求该差分系统的最小解,可以将其转化为>=不等式组,构建有向图,寻找最长路。

C++代码

#include<algorithm>
#include<queue>
#include<iostream>
#include<cstring>
using namespace std;
int tot=0;
const int MAX=1e5;
const int inf=-1e8;
int head[MAX];
int dis[MAX];
int inq[MAX]={0};
int r=0;
queue<int> q;
struct Edge
{
 int to,w,next;
};
Edge edge[2*MAX];
void addEdge(int u,int v,int w)//添加一条边 
{
 edge[tot].to=v;
 edge[tot].w=w;
 edge[tot].next=head[u];
 head[u]=tot;
 tot++;
}
void dij()
{
 while(q.size()) q.pop();
 for(int i=1;i<=r;i++) dis[i]=inf;
 dis[0]=0;
 inq[0]=1;
 q.push(0);
 while(!q.empty())
 {
  int u=q.front();q.pop();
  inq[u]=0;
  for(int j=head[u];j!=-1;j=edge[j].next)
  {
   int v=edge[j].to,w=edge[j].w;
   if(dis[v]<dis[u]+w)
   {
    dis[v]=dis[u]+w;
    if(!inq[v])//如果v不在队列里,压入队列 
    {
     q.push(v);
     inq[v]=1;
    }
   }
  }
 }
}
int main()
{
 memset(head,-1,sizeof(head));
 int n=0;
 cin>>n;
 for(int i=0;i<n;i++)
 {
  int a=0,b=0,c=0;
  cin>>a>>b>>c;
  addEdge(a,b+1,c);
  if(b+1>r) r=b+1;
 }
 for(int i=1;i<=r;i++)
 {
  addEdge(i-1,i,0);
  addEdge(i,i-1,-1);
 }
 dij();
 cout<<dis[r]<<endl;
 return 0;
}

**

B - 猫猫向前冲

**
题目描述
众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。

有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up
主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT
的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。

不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。

Input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。

Output
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!

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

Sample Input

4 3
1 2
2 3
4 3

Sample Output

1 2 4 3

解题思路
题目要求对输入的数据输出一个拓扑排序序列。那么可以构建一个有向图,对于每一个顶点,记录该点的入度。将入度为0的顶点插入到set中(自动维护顺序),循环从set中取出第一个点,遍历该点可以到达的所有点,更新这些点的入度,并将入度为0的点再插入到set中。最后根据点出set的顺序输出即可。

C++代码

#include<iostream>
#include<cstring>
#include<queue>
#include<set> 
using namespace std;
int tot=0;
const int MAX=1e4;
int head[MAX];
int indegree[MAX];
set<int> st;
queue<int> ans;
struct Edge
{
 int to,next;
};
Edge edge[2*MAX];
void addEdge(int u,int v)//添加一条边 
{
 edge[tot].to=v;
 edge[tot].next=head[u];
 head[u]=tot;
 tot++;
}
void toposort()
{
 while(!st.empty())
 {
  int u=*(st.begin());
  ans.push(u);
  st.erase(st.begin());
  for(int j=head[u];j!=-1;j=edge[j].next)
  {
   int v=edge[j].to;
   indegree[v]--;
   if(indegree[v]==0)
   {
    st.insert(v);
   }
  }
 }
}
int main()
{
 int n=0,m=0;
 while(cin>>n>>m)
 {
  memset(head,-1,sizeof(head));
  memset(indegree,0,sizeof(indegree));
  for(int i=0;i<m;i++)
  {
   int p1=0,p2=0;
   cin>>p1>>p2;
   addEdge(p1,p2);
   indegree[p2]++;
  }
  for(int i=1;i<=n;i++) 
  {
   if(indegree[i]==0) st.insert(i);
  }
  toposort();
  cout<<ans.front();
  ans.pop();
  while(!ans.empty()) 
  {
   cout<<" "<<ans.front();
   ans.pop(); 
  }
  cout<<endl;
 }
 return 0;
}

**

C - 班长竞选

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

Input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

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

Sample Input

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

Sample Output

Case 1: 2
0 1
Case 2: 2
0 1 2

解题思路
学生的意见具有传递性,可以构建一个有向图,对于图中的任一个连通分量,连通分量中的学生都可以拿到其他人的支持,如果连通分量中的某一个学生A能够拿到其他连通分量W中学生的支持,那么A所在联通分量的学生就能拿到W中所有学生的支持。所以可以分析得出,答案一定出现在出度为0的SCC中。
因此可以先利用Kosaraju算法找出有向图中所有的SCC并缩点。缩点后,不难发现对于属于第i个SCC的点来说,答案分为两部分,令SCC[i]表示第i个SCC中点的个数:
当前SCC中的点,ans==SCC[i]-1(去除自己)
其他SCC中的点 SUM(SCC[i]),其中j可到达i。
将边反向,对每个出度为0的点进行dfs,计算其能到达的SUM(SCC[i]),即可得到答案。

C++代码

#include<iostream>
#include<vector>
#include<cstring>
#include<set> 
using namespace std;
const int MAXN=1e4;
//c:记录每一个点所在连通分量的标号 dfn:存放dfs后序序列 vis和reach均为标记数组
//num:记录每一个连通分量中点的个数 deg:记录连通分量出度 ans:存放每个人的票数
//dcnt:dfs序计数 scnt:scc计数 
int n,sum,c[MAXN],dfn[MAXN],vis[MAXN],reach[MAXN],num[MAXN],deg[MAXN],ans[MAXN],dcnt,scnt;
vector<int> G[MAXN],G2[MAXN];//原图、反图 
set<int> G3[MAXN];//缩点的图 利用set自动去重 
void dfs1(int x)//确定原图的逆后序序列 
{
 vis[x]=1;
 for(int i=0;i<G[x].size();i++)
 {
  if(!vis[G[x][i]]) dfs1(G[x][i]);
 }
 dfn[dcnt]=x;//dfs后序列 
 dcnt++;
}
void dfs2(int x)//在反图中按照逆后序序列进行遍历 
{
 c[x]=scnt;//c[i]:i所在scc编号 
 num[scnt]++;//对应scc中点的个数 
 for(int i=0;i<G2[x].size();i++)
 {
  if(!c[G2[x][i]]) dfs2(G2[x][i]);
 }
}
void kosaraju()//得到原图的SCC 
{
 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]);
  } 
 }
}
void dfs3(int x)//计算sum(scc[j]) 
{
 reach[x]=1;
 sum=sum+num[x];
 set<int>::iterator it=G3[x].begin();
 for(;it!=G3[x].end();it++)
 {
  if(!reach[*it]) dfs3(*it);
 }
}
int main()
{
 int t=0;
 cin>>t;
 for(int i=1;i<=t;i++)
 {
  int m=0;
  cin>>n>>m;
  for(int j=0;j<n;j++)
  {
   G[j].clear();
   G2[j].clear();
   G3[j].clear();
  }
  for(int j=0;j<m;j++)
  {
   int a=0,b=0;
   cin>>a>>b;
   G[a].push_back(b);//原图 
   G2[b].push_back(a);//反图 
  }
  memset(num,0,sizeof(num));
  memset(deg,0,sizeof(deg));
  kosaraju();
  for(int j=0;j<n;j++)//缩点 
  {
   for(int k=0;k<G[j].size();k++)
   {
    int c1=c[j],c2=c[G[j][k]];
    if(c1!=c2)
    {
     G3[c2].insert(c1);//反向 
     deg[c1]++;
    }
   }
  }
  int maxx=0;
  for(int j=1;j<=scnt;j++)
  {
   if(deg[j]==0)
   {
    sum=0;
    memset(reach,0,sizeof(reach));
    dfs3(j);
    ans[j]=sum;
    if(sum>maxx) maxx=sum;//找到最大sum 
   }
  }
  int judge=0;
  cout<<"Case "<<i<<": "<<maxx-1;
  cout<<endl;
  for(int j=0;j<n;j++)
  {
   if(deg[c[j]]==0&&ans[c[j]]==maxx)//点所在的连通分量出度为0,且为最大值 
   {
    if(judge==0) 
    {
     cout<<j;
     judge=1;
    }
    else cout<<" "<<j;
   }
  }
  cout<<endl;
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值