程序设计作业 week8

程序设计作业 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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值