NEUQ2020-ACM实验班-训练001

最短路径算法(Floyd-Warshall)+栈的实现及基本操作+队列的实现及基本操作+字符串的冒泡排序+打印选课学生名单+房间+二分查找+最长上升子序列+求区间和+抽卡游戏

7-1 最短路径算法(Floyd-Warshall) (10分)
在带权有向图G中,求G中的任意一对顶点间的最短路径问题,也是十分常见的一种问题。

解决这个问题的一个方法是执行n次迪杰斯特拉算法,这样就可以求出每一对顶点间的最短路径,执行的时间复杂度为O(n​3​​)。 而另一种算法是由弗洛伊德提出的,时间复杂度同样是O(n​3​​),但算法的形式简单很多。

在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并使用Floyd算法求出每一对顶点间的最短路径长度。
输入格式:

输入的第一行包含1个正整数n,表示图中共有n个顶点。其中n不超过50。

以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。 当i和j相等的时候,保证对应的整数为0。
输出格式:

共有n行,每行有n个整数,表示源点至每一个顶点的最短路径长度。

如果不存在从源点至相应顶点的路径,输出-1。对于某个顶点到其本身的最短路径长度,输出0。

请在每个整数后输出一个空格,并请注意行尾输出换行。
输入样例:

4
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0

输出样例:

0 3 2 1
6 0 4 7
2 5 0 3
3 6 1 0

题解:

#include 
using namespace std;
typedef long long ll;
int main()
{
     
    int n,a[51][51],b[51][51];
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<= n;j++)
        {
     
            cin>>a[i][j];
            if (i!=j&&a[i][j]==0)a[i][j] = 999999;//使没有路径的初始化为正无穷
        }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
     
                if(a[i][j]>a[i][k]+a[k][j])
                    a[i][j]=a[i][k]+a[k][j];//找到最小路径
            }
    for(int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
            if (a[i][j]==999999&&i!=j)
                    a[i][j] = -1;//没有中转点的初始化为-1
    for (int i=1;i<=n;i++)
    {
     
        for (int j=1;j<=n;j++)
            cout<<a[i][j]<< " ";
        cout<<endl;
    }
    return 0;
}

7-2 栈的实现及基本操作 (10分)
给定一个初始为空的栈和一系列压栈、弹栈操作,请编写程序输出每次弹栈的元素。栈的元素值均为整数。
输入格式:

输入第1行为1个正整数n,表示操作个数;接下来n行,每行表示一个操作,格式为1 d或0。1 d表示将整数d压栈,0表示弹栈。n不超过20000。
输出格式:

按顺序输出每次弹栈的元素,每个元素一行。若某弹栈操作不合法(如在栈空时弹栈),则对该操作输出invalid。
输入样例:

在这里给出一组输入。例如:

7
1 1
1 2
0
0
0
1 3
0

输出样例:

在这里给出相应的输出。例如:

2
1
invalid
3

题解:

#include
using namespace std;
int main()
{
     
    int n,a[1000005]={
     },i=0;
    cin>>n;
    while(n--)
    {
     
        int k;
        cin>>k;
        if(k==1)
        {
     
            cin>>a[i];
            i++;
        }
        if(k==0)
        {
     
            if(i>0)
            {
     
                if(n==0)cout<<a[i-1];
                else cout<<a[i-1]<<endl;
                i--;
            }
            else cout<<"invalid"<<endl;
        }
    }
    return 0;
}

7-3 队列的实现及基本操作 (10分)
给定一个初始为空的队列和一系列入队、出队操作,请编写程序输出每次出队的元素。队列的元素值均为整数。
输入格式:

输入第1行为1个正整数n,表示操作个数;接下来n行,每行表示一个操作,格式为1 d或0。1 d表示将整数d入队,0表示出队。n不超过20000。
输出格式:

按顺序输出每次出队的元素,每个元素一行。若某出队操作不合法(如在队列空时出队),则对该操作输出invalid。
输入样例:

在这里给出一组输入。例如:

7
1 1
1 2
0
0
0
1 3
0

输出样例:

在这里给出相应的输出。例如:

1
2
invalid
3

题解:

#include
using namespace std;
int main()
{
     
    int n,i=0,j=0;
    int a[10001];
    cin>>n;
    while(n--)
    {
     
        int k;
        cin>>k;
        if(k==1)
        {
     
            cin>>a[i];
            i++;
        }
        if(k==0)
        {
     
            if(j<i)
            {
     
                cout<<a[j]<<endl;
                j++;
            }
            else
            {
     
                cout<<"invalid"<<endl;
            }
        }
    }
    return 0;
}

7-4 字符串的冒泡排序 (10分)
我们已经知道了将N个整数按从小到大排序的冒泡排序法。本题要求将此方法用于字符串序列,并对任意给定的K( 输入格式:

输入在第1行中给出N和K(1≤K 输出格式:

输出冒泡排序法扫描完第K遍后的中间结果序列,每行包含一个字符串。
输入样例:

6 2
best
cat
east
a
free
day

输出样例:

best
a
cat
day
east
free

题解:
就是简单的冒泡排序的思路。

#include
#include
using namespace std;
int main()
{
     
    int n,k;
    cin>>n>>k;
    string a[105];
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=0;i<k;i++)
    {
     
        for(int j=0;j<n-i-1;j++)
        {
     
            if(a[j]>a[j+1])
            swap(a[j],a[j+1]);
        }
    }
    for(int i=0;i<n;i++)cout<<a[i]<<endl;
}

7-5 打印选课学生名单 (10分)
假设全校有最多40000名学生和最多2500门课程。现给出每个学生的选课清单,要求输出每门课的选课学生名单。
输入格式:

输入的第一行是两个正整数:N(≤40000),为全校学生总数;K(≤2500),为总课程数。此后N行,每行包括一个学生姓名(3个大写英文字母+1位数字)、一个正整数C(≤20)代表该生所选的课程门数、随后是C个课程编号。简单起见,课程从1到K编号。
输出格式:

顺序输出课程1到K的选课学生名单。格式为:对每一门课,首先在一行中输出课程编号和选课学生总数(之间用空格分隔),之后在第二行按字典序输出学生名单,每个学生名字占一行。
输入样例:

10 5
ZOE1 2 4 5
ANN0 3 5 2 1
BOB5 5 3 4 2 1 5
JOE4 1 2
JAY9 4 1 2 5 4
FRA8 3 4 2 5
DON2 2 4 5
AMY7 1 5
KAT3 3 5 4 2
LOR6 4 2 4 1 5

输出样例:

1 4
ANN0
BOB5
JAY9
LOR6
2 7
ANN0
BOB5
FRA8
JAY9
JOE4
KAT3
LOR6
3 1
BOB5
4 7
BOB5
DON2
FRA8
JAY9
KAT3
LOR6
ZOE1
5 9
AMY7
ANN0
BOB5
DON2
FRA8
JAY9
KAT3
LOR6
ZOE1

题解:

#include
using namespace std;
struct nm
{
     
    char name[5];
};
nm list1[2505][40000];//开一个二维数组用来存名字
bool cmp(nm a,nm b)
{
     
    return strcmp(a.name,b.name)<0;//sort的比较方法
}
int a[2500];//用来记录对应的选课人数
int main()
{
     
    long long n,k,t=0;
    scanf("%d %d", &n, &k);
    memset(a,0,sizeof(a));//将a数组初始化为0
    while(n--)
    {
     
        char n[5];
        cin>>n;
        int c;
        cin>>c;
        for(int i=0;i<c;i++)
        {
     
            int j;
            cin>>j;
            strcpy(list1[j][a[j]++].name,n);//将n的值拷贝到对应的列表位置
        }
    }
    for(int i=1;i<=k;i++)
    sort(list1[i],list1[i]+a[i],cmp);//对列表按字典排序
    for(int i=1;i<=k;i++)//依次输出
    {
     
        printf("%d %d\n", i, a[i]);
        for(int j=0;j<a[i];j++)
        printf("%s\n", list1[i][j].name);
    }
    return 0;

}

7-6 房间 (10分)
终于到了假期了,老师决定带领ACM队员们出去游山玩水,计划出行两天,这样的话中间就需要找个地方住宿一晚。

恰巧,老师带领队员们来到了这么一所酒店,这所酒店只有双人间(最多住两人)和三人间(最多住三人),但是价格不同,现在我们算上老师,一共有 n 个人,酒店的双人间价格是 a 元,三人间价格是 b 元,现在老师想知道怎样安排房间才能使得花销最小,你能帮助老师计算出最小的花销吗?
输入格式:

第一行给出一个正整数 T(1≤T≤1000),代表测试数据的组数。

接下来 T 行每行给出三个正整数 n,a,b,1≤n,a,b≤10​9​​,含义如题。
输出格式:

输出包含 T 行,每行对应一组样例的输出。
输入样例:

2
2 20 200
3 20 20

输出样例:

20
20

题解:

#include
using namespace std;
int main()
{
     
    int t;
    cin>>t;
    long long n,a,b;
    while(t--)
    {
     
        cin>>n>>a>>b;
        if(n==1||n==2)cout<<min(a,b)<<endl;//一个人或者两个人时候
        else if(a/2<b/3)//双人间更划算
        {
     
            if(n%2==0)cout<<n*a/2<<endl;//被2整除
            else cout<<min((n/2+1)*a,(n/2-1)*a+b)<<endl;//余一人时候,单独开一个双人间或者开一个三人间
        }
        else
        {
     
            if(n%3==0)cout<<n*b/3<<endl;//被3整除
            else if(n%3==1)
            cout<<min(min((n/3+1)*b,(n/3)*b+a),(n/3-1)*b+2*a)<<endl;//余一人时候,单独开一个三人间或者单独开一个双人间或者拿出三个人开两个双人间
            else if(n%3==2)
            cout<<min((n/3+1)*b,(n/3)*b+a)<<endl;//余两人时候,单独开一个三人间或者单独开一个双人间
        }
    }
    return 0;
}

7-7 二分查找 (10分)
请实现有重复数字的有序数组的二分查找。

输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一。
输入格式:

输入第一行有两个数,第一个数为数组长度n(≤10^6),第二个数为需要查找的数。

接下来有n个整数,以空格或换行符分隔。
输出格式:

输出待查找的数的位置。
输入样例:

5 4
1 2 4 4 5

输出样例:

3

样例解释:

有5个数,查找4出现的位置,4第一次出现在第3个位置,所以输出3。

题解:

#include
using namespace std;
int main()
{
     
    int n,k;
    cin>>n>>k;
    const int nn=n;
    int a[nn];
    for(int i=0;i<nn;i++)cin>>a[i];
    int low=0,high=nn-1;
    int mid=(low+high)/2;
    while(low<high)
    {
     
        if(a[mid]>=k)high=mid;
        else low=mid+1;
        mid=(low+high)/2;
    }
    if(k>a[nn-1])cout<<n+1<<endl;
    else cout<<mid+1<<endl;
}

7-8 最长上升子序列 (10分)
给定一个序列,求它的一个递增子序列,使它的元素个数尽量多,求该序列的最长上升子序列中元素个数。例如序列1,6,2,5,4,7的最长上升子序列是1,2,5,7或1,2,4,7,则其最长上升子序列中的元素个数为4。
输入格式:

第一行中输入序列的个数(不超过100),第二行中输入每个元素,以空格隔开。
输出格式:

输出该序列中最长上升子序列中的元素个数。
输入样例:

在这里给出一组输入。例如:

6
1 6 2 5 4 7

输出样例:

在这里给出相应的输出。例如:

4

题解:

#include
using namespace std;
int main()
{
     
    int a[1000],dp[1000];
    int n=0;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++)
    {
     
        dp[i]=1;
        for(int j=1;j<i;j++)
        {
     
            if(a[i]>a[j])
            {
     
                dp[i]=max(dp[i],dp[j]+1);//建立dp数列,通过对当前dp数列值与前一个dp数列值+1取max
			}
        }
	}
	int res=0;
	for(int i=1;i<=n;i++)
    {
     
        res=max(res,dp[i]);//找到最大值
    }
	cout<<res<<endl;	 
	return 0;
}

7-9 求区间和 (10分)
本题会给你一段长度为N的整数序列,并进行K次询问。每次询问要求你给出区间a到b的和(序列下标由1到N)。由于区间和可能较大,请你输出它对10000019取模的结果。

(注意:如果你想不到高效的做法,可以用朴素算法得到一部分分,但本题满分需要你用一个比较高效的做法。)
输入格式:

首先一行整数N,代表序列长度。

接下来一行N个整数,为序列内容。

接下来一行整数K,代表对区间和的询问次数。

接下来K行,每行两个整数a和b,请你输入序列的第a到b号元素(含)的和取模后的结果。
输出格式:

一共K行,每行一个整数,代表询问的区间和取模后的结果。
输入样例:

在这里给出一组输入。例如:

5
1 2 3 4 5
3
1 5
2 3
3 3

输出样例:

在这里给出相应的输出。例如:

15
5
3

数据限制:

N<=10​6​​

K<=10​5​​

题解:

#include 
using namespace std;
int main() 
{
     
	int n;
	scanf("%d",&n);
	long long sum[n]={
     }; 
	for(int i=1;i<=n;i++)
	{
     
        int x;
		scanf("%d",&x);
		sum[i]=sum[i-1]+x;//求前缀和
	}
    int k;
	scanf("%d",&k);
	while(k--)
	{
     
        int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",(sum[b]-sum[a-1])%10000019);//用前缀和相减
	}
    return 0;
}

7-10 抽卡游戏 (10分)
本题的灵感来源于一个古典的概率模型。

Alice 在一个卡池里抽卡,里面有 x 张 s 卡和 y 张 a 卡。

Alice 每次会不放回的随机从卡池中抽出一张卡。

Bob 在一旁看 Alice 抽卡并对每次的结果进行预测:

若卡池里 s 卡的数量多于 a 卡,Bob 会猜 Alice 抽出 s 卡。

反之则会猜测 Alice 抽出 a 卡。

但是如果当卡池里的两种卡的数量相等的时候,Bob 就不对抽卡的结果做任何的猜测了。

Alice 会一直抽卡,直到卡池空为止。

现在告诉你初始的时候卡池里 s 卡和 a 卡的数量,你能算算 Bob 期望下猜对多少次?
输入格式:

在一行中给出两个整数 a,b(1≤a,b≤10​5​​)
输出格式:

一个实数表示期望,四舍五入保存两位小数。
输入样例:

1 1

输出样例:

1.00

样例解释:

初始局面的时候 Bob 不做任何猜测,第一次抽完之后,第二次抽的时候不管剩下的是哪一种卡,Bob 都能猜对,所以期望是1.00

题解:

#include
using namespace std;
int main()
{
     
    double a,b;
    cin>>a>>b;
    if(a>b)printf("%.2lf",a);
    else printf("%.2lf",b);
}
// 该题目其实很简单 答案就是max(a,b);
// 证明如下,我们令Ex(a,b)表示相应的期望。
// Ex(1,1) = 1;
// Ex(1,2) = Ex(2,1) = 2/3+(2/3)*(0+1)+(1/3)*(1+1) = 2;
// EX(a,b) =(a/a+b)+ (a/a+b)*Ex(a-1,b)+(b/a+b)*Ex(a,b-1)  //假设a>a-1>b;
// 数学归纳法: Ex(a-1,b) = a-1;
// Ex(a,b-1) = a;
// EX(a,b) =(a/a+b)+ (a/a+b)*(a-1)+(b/a+b)*a = a(a+b)/(a+b) = a;
// 同理当a = b 或 a < b证法相同

你可能感兴趣的:(数据结构,算法,动态规划,队列,列表)