程序设计作业 week8
to sum up
本周主要学习三个算法,分别是差分约束(与图中最短路最长路的联系),拓扑排序(Kahn)算法以及强连通分量(Kosaraju算法)。作业题陈列如下:
A题 区间选点
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
Sample input and output
input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
output
输出一个整数表示最少选取的点的个数
6
解题思路及源代码
每个区间即为一个不等式
再加一个保证每个点最多只能选1次的条件,即保证sum有意义:
这样就变成了一个差分问题。将不等式中的每一个xi看作图中的一个节点,每个不等式看作是一条有向边,该问题便转化为了最长路问题。
#include <iostream>
#include <queue>
const int MAXN=1e6;
const int inf=1e8;
const int num=50010;
using namespace std;
struct edge
{
public:
int u;
int v;
int w;
int nxt;
}Edges[MAXN];
int head[num],tot;
void init()
{
tot=0;
for(int i=0;i<num;i++)
head[i]=-1;
}
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].nxt=head[u];
head[u]=tot;
tot++;
}
int vis[num],dis[num];
queue<int> q;
void SPFA(int s)
{
while(!q.empty()) q.pop();
for(int i=s;i<num;i++)
{
vis[i]=0;
//cnt[i]=0;
//tar[i]=0;
dis[i]=-inf;
}
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front(); q.pop();
vis[x]=0;
//如果在负环
/*if(tar[x]==1)
continue;*/
for(int i=head[x];i!=-1;i=Edges[i].nxt)
{
int y=Edges[i].v;
//如果在负环
/*if(tar[y]==1)
continue;*/
//松弛操作
if(dis[y] < dis[x]+Edges[i].w)
{
dis[y]=dis[x]+Edges[i].w;
if(vis[y]!=1)
vis[y]=1,q.push(y);
}
}
}
}
int main()
{
init();
int lmin=inf;
int rmax=-inf;
int n;
cin>>n;
int a,b,c;
for(int i=0;i<n;i++)
{
cin>>a;
cin>>b;
cin>>c;
b++;
addEdge(a,b,c);
//cout<<'!'<<endl;
lmin=min(lmin,a);
rmax=max(rmax,b);
}
for(int i=lmin;i<=rmax;i++)
{
addEdge(i-1,i,0);
addEdge(i,i-1,-1);
}
SPFA(lmin);
cout<<dis[rmax]<<endl;
return 0;
}
B题 猫猫向前冲
N只猫编号依次为1,2,3,…,N进行比赛。已知每一场比赛的结果,求字典序最小的名次顺序。
Sample input and output
input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。
4 3
1 2
2 3
4 3
output
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
1 2 4 3
解题思路及源代码
模版题
需要注意的地方是,要求字典序最小的结果,所以可以用一个最小堆来存储入度为0的点(我这里用了vector,每次插入都sort)。
#include <iostream>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int num=505;
const int MAXN=1e8;
int N,M;
struct edge
{
public:
int u;
int v;
int w;
int nxt;
}Edges[MAXN];
int head[num],tot;
int in_degree[num];
queue<int> list;
vector<int> tmp;
void init()
{
while(!list.empty()) list.pop();
tmp.clear();
tot=0;
for(int i=0;i<num;i++)
head[i]=-1,in_degree[i]=0;
}
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].nxt=head[u];
head[u]=tot;
tot++;
}
void topo()
{
//现将一开始入度为0的点倒入tmp,(此时自动为字典序最小)
for (int i = 1; i <= N; i++)
if (in_degree[i] == 0) tmp.push_back(i);
while (tmp.front() != 10000)
{
int u=tmp.front();
list.push(u);
tmp[0]=10000;
for(int i= head[u];i!=-1;i=Edges[i].nxt)
{
int v=Edges[i].v;
in_degree[v]--;
if(in_degree[v]==0) tmp.push_back(v);
}
sort(tmp.begin(),tmp.begin()+tmp.size());
}
int s=list.size();
for(int i=0;i<s-1;i++)
{
cout<<list.front()<<' ';
list.pop();
}
cout<<list.front();
}
int a,b;
int main()
{
while(cin>>N)
{
init();
cin >> M;
for (int i = 0; i < M; i++) {
cin >> a;
cin >> b;
addEdge(a, b, 1);
in_degree[b]++;
}
topo();
cout<<endl;
}
return 0;
}
C题 班长竞选
大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?
Sample input and output
input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2
output
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
Case 1: 2
0 1
Case 2: 2
0 1 2
解题思路及源代码
Kosaraju算法,一共进行三遍DFS。建立好图之后第一遍DFS确定原图的逆后序序例,存储在栈st中,然后在反图中按照st的弹出顺序进行遍历,每次遍历的结果即为一个SCC,最后进行缩点操作,每个点的权值为强连通图内点的个数。最后在缩点之后的反图内找入度为0且dfs到的点最多的点(强连通子图),子图内的点即为答案。
#include<cstdio>
#include<queue>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
const int Mnum=1e4;
const int MAXN=1e5;
int T,N,M,a,b;
int head1[Mnum],head2[Mnum],tot=0,tot1=0,tot2=0;
int tar[Mnum],num[Mnum],vis[Mnum],cnt=0;
struct edge
{
int u;
int v;
int nxt;
int w;
} e1[MAXN],e2[MAXN];
void addEdge(int u,int v)
{
e1[++tot1].v=v;
e1[tot1].nxt=head1[u];
head1[u]=tot1;
e2[++tot2].v=u;
e2[tot2].nxt=head2[v];
head2[v]=tot2;
}
stack<int> st;
//将逆后序序列存储在栈st中
void dfs1(int x)
{
vis[x]=1;
for(int i=head1[x]; i; i=e1[i].nxt)
if(!vis[e1[i].v])
dfs1(e1[i].v);
st.push(x);
}
void dfs2(int x)
{
num[cnt]++,tar[x]=cnt;
for(int i=head2[x]; i; i=e2[i].nxt)
if(!tar[e2[i].v]) dfs2(e2[i].v);
}
void kosaraju()
{
for(int i=1; i<=N; i++)
if(!vis[i]) dfs1(i);
while(!st.empty())
{
int x=st.top();
st.pop();
if(!tar[x])
cnt++,dfs2(x);
}
}
edge e[MAXN];
int head[Mnum],in_degree[Mnum],sum[Mnum],ans,tmp;
void add(int u,int v)
{
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void dfs(int x)
{
vis[x]=1;
tmp+=num[x];
for(int i=head[x]; i; i=e[i].nxt)
{
if(!vis[e[i].v])
dfs(e[i].v);
}
}
void init()
{
tot=tot1=tot2=0;
for(int i=0;i<Mnum;i++)
head[i]=0,head[1]=0,head2[i]=0,in_degree[i]=0,num[i]=0,sum[i]=0,tar[i]=0;
}
int main()
{
scanf("%d",&T);
for(int t=1; t<=T; t++)
{
init();
scanf("%d",&N);
scanf("%d",&M);
/*cin>>N;
cin>>M;*/
for(int i=1; i<=M; i++)
{
scanf("%d",&a);
scanf("%d",&b);
// cin>>a;
// cin>>b;
addEdge(a+1,b+1);
}
kosaraju();
for(int i=1; i<=N; i++)
for(int j=head1[i]; j; j=e1[j].nxt)
{
if(tar[i]!=tar[e1[j].v])
{
add(tar[e1[j].v],tar[i]);
in_degree[tar[i]]++;
}
}
ans=-1;
for(int i=1; i<=cnt; i++)
{
if(in_degree[i]==0)
{
tmp=0;
memset(vis,0,sizeof(vis));
dfs(i);
sum[i]=tmp;
ans=max(ans,sum[i]);
}
}
//cout<<"Case "<<t<<": "<<ans-1<<endl;
printf("Case %d: %d\n",t,ans-1);
queue<int> list;
for(int i=1;i<=N;i++)
if (sum[tar[i]] == ans) list.push(i-1);
int po=list.size();
for(int i=0;i<po-1;i++)
{
// cout<<list.front()<<' ';
printf("%d ",list.front());
list.pop();
}
printf("%d\n",list.front());
}
return 0;
}