2022年暑假ACM热身练习3(详细)

更难了。。。其中E题完全当做数学题做了,抓住两个关键点,推了很久,推导过程还利用了放缩的方法,然后和星鑫同志交流有了更好的方法。
有两题(F和I)只有我一个人ac了,尽量详细的给大家讲讲里面的思想。
我感觉这次热身练习收获还是有的,我尝试着使用了更为多维的目光看待汉诺塔问题。

A:A==B?

题目描述
Give you two numbers A and B, if A is equal to B, you should print “YES”, or print “NO”.

输入
each test case contains two numbers A and B.

输出
for each case, if A is equal to B, you should print “YES”, or print “NO”.

输入样例
1 2
2 2
3 3
4 3

输出样例
NO
YES
YES
NO

做这个做的比较迟,一打开发现大家这一题基本都wa了好多遍,我就直接用string来存储了,再wa我就直接用string模拟出浮点型的数。想要比较注意这些,毕竟我们的比较是字符串之间的比较:
(1)一个数的前置0不需要直接去掉即可,如00002.56=2.56
(2)小数点后末尾的0直接删除,如1.120000=1.12
(3)如果小数点后只有0,删除完0之后的小数点也删除,即1.=1

代码如下:

#include<bits/stdc++.h>
using namespace std;
string fun(string s){
    if(s[0]=='0'){
        for(int i=0;i<s.size();)
            if(s[i]=='0')s.erase(i,1);
            else break;
    }
    if(s.find(".")!=-1){
        int pos=s.size();
        for(int i=s.size()-1;i>=0;--i)
            if(s[i]=='0')pos--;
            else break;
        s.erase(pos,s.size()-pos);
    }
    if(s[s.size()-1]=='.')s.erase(s.size()-1,1);
    return s;
}
int main(){
    string a,b;
    while(cin>>a>>b){
        a=fun(a),b=fun(b);
        //cout<<a<<" "<<b<<endl;
        if(a.compare(b)==0)cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
}

D:A+B Again

题目描述
There must be many A + B problems in our HDOJ , now a new one is coming.
Give you two hexadecimal integers , your task is to calculate the sum of them,and print it in hexadecimal too.
Easy ? AC it !

输入
The input contains several test cases, please process to the end of the file.
Each case consists of two hexadecimal integers A and B in a line seperated by a blank.
The length of A and B is less than 15.

输出
For each test case,print the sum of A and B in hexadecimal in one line.

输入样例
+A -A
+1A 12
1A -9
-1A -12
1A -AA

输出样例
0
2C
11
-2C
-90

模拟了半天。。。模拟一直都是最烦的!!!但是这题实际上可以不要模拟,仔细看一下,A和B的长度不超过15,转化为二进制也绝对不会超过64位,那岂不就是long long类型的16进制数而已吗?C语言的语法学的好的话会知道怎么输入和输出的,都用llX,注意是大写的X,因为输入和输出的十六进制中字母都是大写的。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    ll a,b,sum;
    while(~scanf("%llX %llX",&a,&b)){
        sum=a+b;
        if(sum<0)printf("-%llX\n",-1*sum);
        else printf("%llX\n",sum);
    }
}

E:The sum problem

题目描述
Given a sequence 1,2,3,…N, your job is to calculate all the possible sub-sequences that the sum of the sub-sequence is M.

输入
Input contains multiple test cases. each case contains two integers N, M( 1 <= N, M <= 1000000000).input ends with N = M = 0.

输出
For each test case, print all the possible sub-sequence that its sum is M.The format is show in the sample below.print a blank line after each test case.

输入样例
20 10
50 30
0 0

输出样例
[1,4]
[10,10]

[4,8]
[6,9]
[9,11]
[30,30]

这题想都不要想,N和M范围那么大,直接暴力找绝对超时。
这题的关键点在于(1)要找的区间是连续的;(2)整个数列就是个标准的可以直接推出求和公式的数列
而看起来这道题似乎真的推不出几个式子出来,那么自然是自变量越少越好,自变量只有两个,一个是左端点,一个是区间长度,不需要右端点。然而在我的数学理念来看只有区间长度是自变量,而左端点会被我当成伪变量,需要这个伪变量的原因是左端点范围大于等于1,我只需要通过左端点大于等于1的性质把需要for遍历的范围尽量缩小就好了,设置左端点的作用就这点了。
我设置区间为[x,x+L],则其左端点为x,区间长度为L+1,经如下推导:

在这里插入图片描述
这样可以大概得到L的范围,再经过放缩,推出L的大致范围:
在这里插入图片描述

容易发现,L越大,x越小,那么我们将L从根号2m开始遍历至0(L为0代表的是区间长度为1),如此推导的x为左端点的区间和一定会等于吗,但是我们需要确定x推导一定是正整数,接着就是x+L不能超过左端点。
但是就是这样,我还是wa了,这是因为虽然我通过x大于0推导得出L,我的的运算是经过放缩的,看式子就可以知道我把右边的式子放缩的更小了,那么对L来说,根号2m是虚高了,前面有提到过,L越大,x越小,直接这么遍历不一定就能满足x不会超过左端点。因此推导L的范围是为了让我们省去很多多余的遍历,但仍要注意细节。

代码如下(注意我的代码里面,设置的区间不是[x,x+L]而是[L,L+len]):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        if(!n&&!m)return 0;
        for(int len=sqrt(2*m);len>=0;--len){
            double L=1.0*m/(len+1)-1.0*len/2;
            int Lx=(int)L;
            if(Lx==L&&L>=1&&L+len<=n)
                printf("[%d,%d]\n",Lx,Lx+len);
        }
        cout<<endl;
    }
}

星鑫同志的代码比我更加言简意赅,直接遍历区间长度,只要能抓住端点就行了。。。抓住不满足的端点直接啥操作都不做也不见得耗时间。
但是他的耗时比我大不少,应该主要就是cout比较占时间,是个好办法。
在这里插入图片描述

F:龟兔赛跑

题目描述
据说在很久很久以前,可怜的兔子经历了人生中最大的打击――赛跑输给乌龟后,心中郁闷,发誓要报仇雪恨,于是躲进了杭州下沙某农业园卧薪尝胆潜心修炼,终于练成了绝技,能够毫不休息得以恒定的速度(VR m/s)一直跑。兔子一直想找机会好好得教训一下乌龟,以雪前耻。
最近正值HDU举办50周年校庆,社会各大名流齐聚下沙,兔子也趁此机会向乌龟发起挑战。虽然乌龟深知获胜希望不大,不过迫于舆论压力,只能接受挑战。
比赛是设在一条笔直的道路上,长度为L米,规则很简单,谁先到达终点谁就算获胜。
无奈乌龟自从上次获胜以后,成了名龟,被一些八卦杂志称为“动物界的刘翔”,广告不断,手头也有了不少积蓄。为了能够再赢兔子,乌龟不惜花下血本买了最先进的武器――“"小飞鸽"牌电动车。这辆车在有电的情况下能够以VT1 m/s的速度“飞驰”,可惜电池容量有限,每次充满电最多只能行驶C米的距离,以后就只能用脚来蹬了,乌龟用脚蹬时的速度为VT2 m/s。更过分的是,乌龟竟然在跑道上修建了很多很多(N个)的供电站,供自己给电动车充电。其中,每次充电需要花费T秒钟的时间。当然,乌龟经过一个充电站的时候可以选择去或不去充电。
比赛马上开始了,兔子和带着充满电的电动车的乌龟并列站在起跑线上。你的任务就是写个程序,判断乌龟用最佳的方案进军时,能不能赢了一直以恒定速度奔跑的兔子。

输入
本题目包含多组测试,请处理到文件结束。每个测试包括四行:
第一行是一个整数L代表跑道的总长度
第二行包含三个整数N,C,T,分别表示充电站的个数,电动车冲满电以后能行驶的距离以及每次充电所需要的时间
第三行也是三个整数VR,VT1,VT2,分别表示兔子跑步的速度,乌龟开电动车的速度,乌龟脚蹬电动车的速度
第四行包含了N(N<=100)个整数p1,p2…pn,分别表示各个充电站离跑道起点的距离,其中0<p1<p2<…<pn<L
其中每个数都在32位整型范围之内。

输出
当乌龟有可能赢的时候输出一行 “What a pity rabbit!“。否则输出一行"Good job,rabbit!”;
题目数据保证不会出现乌龟和兔子同时到达的情况。

输入样例
100
3 20 5
5 8 2
10 40 60
100
3 60 5
5 8 2
10 40 60

输出样例
Good job,rabbit!
What a pity rabbit!

这道题只有我一个人做出来了,这题想挺久的。一开始我的做法是暴力搜索,直接利用dfs递归。在两个供电站之间的距离length,如果满电状态能够最多行驶的最多距离C小于等于length,则有电情况走C米,剩下的(length-C)米用脚蹬,到达供电站肯定要耗时T秒进行充电;而如果C大于length,那么就需要分情况进行递归,我到底要不要充电?充点要花时间,不充又能否保证到达终点的时间最短呢?因此这里我就直接进行了两层递归。如此递归,到终点就进行min操作取最短时间。但是我超时了。。。但是就是这个暴力的错误让我慢慢的有了感觉,因此我还是分享给大家。

错误代码如下:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
double VR,V1,V2,ti;
int L,C,T,N;
int p[105];
void dfs(int i,double t,double c){
    if(i>N){
        int length=L-p[N];
        if(c<length)ti=min(ti,t+c/V1+(length-c)/V2);
        else ti=min(ti,t+length/V1);
        return;
    }
    int length=p[i]-p[i-1];
    if(c<length)dfs(i+1,t+c/V1+(length-c)/V2,c);
    else{
        dfs(i+1,t+length/V1,c);
        dfs(i+1,t+length/V1,c-length);
    }
}
int main(){
    while(~scanf("%d%d%d%d%lf%lf%lf",&L,&N,&C,&T,&VR,&V1,&V2)){
        ti=INF;
        for(int i=1;i<=N;++i)scanf("%d",&p[i]);
        dfs(1,0,C);
        double t=L/VR;
        if(ti<t)printf("What a pity rabbit!\n");
        else printf("Good job,rabbit!\n");
    }
}

这题这样计算时间的方法是没有什么问题的,但是有一点是,直接对一个点分情况进行递归的复杂度实在是太高了。而我们又是需要到达终点的时间最短,可以直接利用dp。记dp[i]为到达第i个供电站的最短时间,初始值都设置为很大的数,然后遍历其之前的供电站(关键点在于,C的长度足够长的时候,可能不需要每次都那么无脑的递归。比如对于第i个供电站,如果遍历发现第i-2个供电站到这里的距离根本就不到C,那就没必要对i-2进行那些毫无意义的递归了,多出了太多没必要的情况了)。
如果有点题感的话,应该可以注意到处理一下起点和终点,把它们也当成供电站,进行一些特殊赋值,就不需要进行特殊的一些操作了。

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int L,C,T,N,VR,V1,V2;
double p[105],dp[105];
int main(){
    while(~scanf("%d%d%d%d%d%d%d",&L,&N,&C,&T,&VR,&V1,&V2)){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=N;++i)scanf("%lf",&p[i]);
        p[N+1]=1.0*L;
        dp[0]=-1*T;//第0站即起点本是不花时间的,为什么这样赋值直接看下面循环就行
        for(int i=1;i<=N+1;++i){
            double t=INF;
            for(int j=0;j<i;++j)
                if(p[i]-p[j]<=C)
                    t=min(t,dp[j]+T+1.0*(p[i]-p[j])/V1);
                else t=min(t,dp[j]+T+1.0*C/V1+1.0*(p[i]-p[j]-C)/V2);
            dp[i]=t;
        }
        if(dp[N+1]<1.0*L/VR)printf("What a pity rabbit!\n");
        else printf("Good job,rabbit!\n");
    }
}

I:Subset sequence

题目描述
Consider the aggregate An= { 1, 2, …, n }. For example, A1={1}, A3={1,2,3}. A subset sequence is defined as a array of a non-empty subset. Sort all the subset sequece of An in lexicography order. Your task is to find the m-th one.

输入
The input contains several test cases. Each test case consists of two numbers n and m ( 0< n<= 20, 0< m<= the total number of the subset sequence of An ).

输出
For each test case, you should output the m-th subset sequence of An in one line.

输入样例
1 1
2 1
2 2
2 3
2 4
3 10

输出样例
1
1
1 2
2
2 1
2 3 1

题目意思还是挺好懂的,但是我一没什么办法就暴力。先进行选择再进行全排,利用set装住实现去重。结果超时了。
因此我把打表做绝,set还搞了个数组。。。我自己以前都没试过。。。然后内存超限了。。唉,感觉我是在虐待我的编译软件。

排列组合的知识可以借鉴这个文章:
算法学习:排列组合
那两个错的代码也分享一下吧:

超时代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
set<vector<int> >s;
void subset(int n,int k){
    for(int i=0;i<(1<<n);++i){
        int num=0,kk=i;
        while(kk)kk=kk&(kk-1),num++;
        if(num==k){
            vector<int>v;
            for(int j=0;j<n;++j)
                if(i&(1<<j))v.push_back(j+1);
            do{
                for(int i=0;i<n;++i)s.insert(v);
            }while(next_permutation(v.begin(),v.end()));
        }
    }
}
int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        s.clear();
        for(int i=1;i<=n;++i)subset(n,i);
        int pos=1;
        for(set<vector<int> >::iterator it=s.begin();it!=s.end();++it){
            if(pos==m){
                vector<int>v=*it;
                for(int i=0;i<v.size();++i)
                    printf("%d%c",v[i],i==v.size()-1?'\n':' ');
                break;
            }
            ++pos;
        }
    }
}

内存超限代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
set<vector<int> >s[25];
void subset(int n,int k){
    for(int i=0;i<(1<<n);++i){
        int num=0,kk=i;
        while(kk)kk=kk&(kk-1),num++;
        if(num==k){
            vector<int>v;
            for(int j=0;j<n;++j)
                if(i&(1<<j))v.push_back(j+1);
            do{
                for(int i=0;i<n;++i)s[n].insert(v);
            }while(next_permutation(v.begin(),v.end()));
        }
    }
}
int main(){
    int n,m;
    for(int i=1;i<=20;++i)
        for(int j=1;j<=i;++j)subset(i,j);
    while(~scanf("%d%d",&n,&m)){
        int pos=1;
        for(set<vector<int> >::iterator it=s[n].begin();it!=s[n].end();++it){
            if(pos==m){
                vector<int>v=*it;
                for(int i=0;i<v.size();++i)
                    printf("%d%c",v[i],i==v.size()-1?'\n':' ');
                break;
            }
            ++pos;
        }
    }
}

以后还是要大概分析一下复杂度和内存,不要想当然,这题暴力的思路是无论如何都不可能出来的,应该换个思路,突破口在哪里?直接列各种情况下去找规律,正好锻炼锻炼我们找规律的思维。
首先我们列举A1 A2 A3的各种情况,按照字典序来列,列举后如下:
在这里插入图片描述
观察一下,可以看出来An可以分为n个分组,第一个分组的子列开头都为1,第二个分组的子列开头都为2,以此类推。(注:下列讲的子集都不包括空集)
我们可以看出,An和An-1有关系,拿A3和A2为例,先拿最简单的——A3的第三组来说,如果不计第一个3,那么后4个(1、1 2、2、2 1)正好可以看成是A2。你可以再推个A4,你的4个组的子集个数一定都是16个。那么可以知道An中n组的任意一组的子集数量等于An-1的子集数量+1。设置一个函数g(n),表示An的每一组的子集数量(注意关键还是要是组内的子集数量,因为第k组的子集开头一定是k,这是不会变的,要利用这个特殊性)。
那么An的子集数量自然为n*g(n),An-1的子集数量为(n-1)*g(n-1),而An的任意一组的子集数量g(n)为An-1的子集数量+1即g(n)=(n-1)*g(n-1)+1,如此得出了递推公式。
得到递推公式初始化后,我们就正式进入循环了。拿样例中的3 10为例,我们先知道其再A3的组号为第2组,这其中的计算公式自己可以很容易观察出来:组号k=(m-1)/g(3)+1=2。那么直接就可以打印出2了。然后进入下一个循环,接下来虽然子集是1和3的,但是排列的方式和规则仍然和A2一样。接下来通过样例想问题,想要打印出3,就得知道其是A2中的第2组,那我们就得先更新m=(k-1)*g(n),然后m–,因为多出来了一个,我们只要A2,接着就得到4了,直接用前面的规则就行,依此类推。
但是现在还需要解决一个问题,样例中的怎么直接打印3?你可以看成第一次选了第2组以后直接把2剔除了,所以2后面的3递补进来了,所以我们只需要再开一个数组表示第n组的头是哪个数就好了,具体看代码:

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll g[25];
int num[25];//num表示第n组的子集的头是哪个元素
void solve(){
    g[1]=1;
    for(int i=2;i<=20;++i)g[i]=(i-1)*g[i-1]+1;
}
int main(){
    int n;ll m;
    solve();
    while(~scanf("%d%lld",&n,&m)){
        for(int i=0;i<=20;++i)num[i]=i;
        while(1){
            if(!n)break;
            int k=(m-1)/g[n]+1;
            if(k>0){//如果组号为0了,那么就是指代空集,直接不理会
                printf("%d",num[k]);
                for(int i=k;i<=n;++i)num[i]=num[i+1];//已经选了,就把这个数剔除,后面的数递补进来
                m-=(k-1)*g[n];
                m--;
                if(m)printf(" ");
                else {cout<<endl;break;}
            }
            --n;
        }
    }
}

J:过山车

题目描述
RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个个男生做partner和她同坐。但是,每个女孩都有各自的想法,举个例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或伪酷儿做partner。考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?

输入
输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。0<K<=1000
1<=N 和M<=500.接下来的K行,每行有两个数,分别表示女生Ai愿意和男生Bj做partner。最后一个0结束输入。

输出
对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。

输入样例
6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0

输出样例
3

分析

匈牙利算法(二分图)模板,没啥好说的,一搜一大堆。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int match[1005],flag[1005],edge[505][505];
int k,m,n;
bool dfs(int s){
    for(int i=1;i<=n;++i){
        if(!flag[i]&&edge[s][i]){
            flag[i]=1;
            if(!match[i]||dfs(match[i])){
               match[i]=s;
               return true;
            }
        }
    }
    return false;
}
int main(){
    int s,e;
    while(~scanf("%d",&k)&&k){
        scanf("%d%d",&m,&n);
        memset(edge,0,sizeof(edge));
        memset(match,0,sizeof(match));
        while(k--){
            scanf("%d%d",&s,&e);
            edge[s][e]=1;
        }
        int sum=0;
        for(int i=1;i<=m;i++){
            memset(flag,0,sizeof(flag));
            if(dfs(i))sum++;
        }
        printf("%d\n",sum);
    }
    return 0;
}

K:汉诺塔III

题目描述
约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。
Daisy已经做过原来的汉诺塔问题和汉诺塔II,但碰到这个问题时,她想了很久都不能解决,现在请你帮助她。现在有N个圆盘,她至少多少次移动才能把这些圆盘从最左边移到最右边?

输入
包含多组数据,每次输入一个N值(1<=N=35)。

输出
对于每组数据,输出移动最小的次数。

输入样例
1
3
12

输出样例
2
26
531440

分析

这道题我想这么小的数据量大家估计还都是用的递归做的,但是我做过很多题目都是可以用递推去做递归的题目,当然了,你也可以把题目当作找规律来做,但是我觉得还是必须得明白规律的意义。
和之前的汉诺塔问题一样,都得要列出递归的步骤,步骤如下:
(1)将n-1个圆盘通过B放到C
(2)将第n个圆盘放到B
(3)将n-1个盘子通过B放到A
(4)将第n个盘子放到C
(5)将n-1个盘子通过B放到C
这里很容易理解1、3、5的步骤数都是一样的,因为递归过程直接把杆的编号换一下就好了,递归大家肯定都会写,谢谢递推吧。
步骤数设为f[n],则1、3、5的步骤为f[n-1],2、4各走一步,因此f[n]=3f[n-1]+2
我们可以直接打表做出来,也可以靠列式子直接得出最终公式:
f[1]=2
f[2]=3
2+2
f[3]=3*(3*2+2)+2
我知道看到这个大家可能习惯性的把式子数值打出来就去找规律了,然而我会说数学的解题步骤,其实也很容易,这既然不是等比数列,我们就列n个项进行处理
f(2)-3f(1)=2①
f(3)-3f(2)=2②
f(4)-3f(3)=2③
依次类推,然后f(n)+3×f(n-1)+3^2×f(n-2)=…移向就结束了。
直接给结果:f(n)=3^n-1
但是直接使用pow函数却错了,应该是因为pow返回的是double的,但是最大的数超过了double的范围,pow这玩意感觉还是要慎用,所以还是要写循环用long long,推了感觉就没什么用了,不如直接打表,但是这也算是一种思考方式吧!顺便说一下,汉诺塔1也类似的推法,推出f(n)=2^n-1,最后用pow没有给卡精度。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    int n;
    while(~scanf("%d",&n)){
        ll sum=1;
        for(int i=1;i<=n;++i)sum=sum*3;
        printf("%lld\n",sum-1);
    }
}

L:“红色病毒”问题(需补题)

M:一个人的旅行

题目描述
虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,0),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。

输入
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。

输出
输出草儿能去某个喜欢的城市的最短时间。

输入样例
6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10

输出样例
9

分析

这道题看似就只是一道dijkstra的模板题,但是这还是有点区别的,这是多起点多终点的情况,就算用优化版本的dijkstra算法一个个遍历仍然会超时。我们可以多设置两个点,超级起点和超级终点,并且设置各个起点和超级起点的距离为0,各个终点和超级终点的距离为0。当然了,这题我只设置一个超级起点然后遍历一次取最小值反而更快,两个代码都放一下

不含超级终点代码(31ms):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=1e9;
const int NUM=1005;
struct edge{
    int from,to,w;
};
vector<edge>e[NUM*NUM];
struct s_node{
    int id,n_dis;
    bool operator<(const s_node &a)const{
        return n_dis>a.n_dis;
    }
};
int t,s,d,dd[1005];
void dijkstra(int s){
    int dis[NUM];
    bool done[NUM];
    for(int i=0;i<=NUM;++i)dis[i]=INF,done[i]=false;
    dis[s]=0;
    priority_queue<s_node>q;q.push({s,dis[s]});
    while(!q.empty()){
        s_node u=q.top();q.pop();
        if(done[u.id])continue;
        done[u.id]=true;
        for(int i=0;i<e[u.id].size();++i){
            edge y=e[u.id][i];
            if(done[y.to])continue;
            if(dis[y.to]>y.w+u.n_dis)
               dis[y.to]=y.w+u.n_dis;q.push({y.to,dis[y.to]});
        }
    }
    int ans=INF;
    for(int i=1;i<=d;++i)ans=min(ans,dis[dd[i]]);
    printf("%d\n",ans);
}
int main(){
    int a,b,c;
    while(~scanf("%d%d%d",&t,&s,&d)){
        for(int i=0;i<=1000;++i)e[i].clear();
        for(int i=1;i<=t;++i){
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back({a,b,c});
            e[b].push_back({b,a,c});
        }
        for(int i=1;i<=s;++i)scanf("%d",&a),e[0].push_back({0,a,0});
        for(int i=1;i<=d;++i)scanf("%d",&dd[i]);
        dijkstra(0);
    }
}

含超级终点代码(46ms):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=1e9;
const int NUM=1005;
struct edge{
    int from,to,w;
};
vector<edge>e[NUM*NUM];
struct s_node{
    int id,n_dis;
    bool operator<(const s_node &a)const{
        return n_dis>a.n_dis;
    }
};
int t,s,d,dd[1005];
void dijkstra(int s){
    int dis[NUM];
    bool done[NUM];
    for(int i=0;i<=NUM;++i)dis[i]=INF,done[i]=false;
    dis[s]=0;
    priority_queue<s_node>q;q.push({s,dis[s]});
    while(!q.empty()){
        s_node u=q.top();q.pop();
        if(done[u.id])continue;
        done[u.id]=true;
        for(int i=0;i<e[u.id].size();++i){
            edge y=e[u.id][i];
            if(done[y.to])continue;
            if(dis[y.to]>y.w+u.n_dis)
               dis[y.to]=y.w+u.n_dis;q.push({y.to,dis[y.to]});
        }
    }
    printf("%d\n",dis[1001]);
}
int main(){
    int a,b,c;
    while(~scanf("%d%d%d",&t,&s,&d)){
        for(int i=0;i<=NUM;++i)e[i].clear();
        for(int i=1;i<=t;++i){
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back({a,b,c});
            e[b].push_back({b,a,c});
        }
        for(int i=1;i<=s;++i)scanf("%d",&a),e[0].push_back({0,a,0});
        for(int i=1;i<=d;++i)scanf("%d",&a),e[a].push_back({a,1001,0});
        dijkstra(0);
    }
}

N:小兔的棋盘

题目描述
小兔的叔叔从外面旅游回来给她带来了一个礼物,小兔高兴地跑回自己的房间,拆开一看是一个棋盘,小兔有所失望。不过没过几天发现了棋盘的好玩之处。从起点(0,0)走到终点(n,n)的最短路径数是C(2n,n),现在小兔又想如果不穿越对角线(但可接触对角线上的格点),这样的路径数有多少?小兔想了很长时间都没想出来,现在想请你帮助小兔解决这个问题,对于你来说应该不难吧!
输入
每次输入一个数n(1<=n<=35),当n等于-1时结束输入。
输出
对于每个输入数据输出路径数,具体格式看Sample。
输入样例
1
3
12
-1
输出样例
1 1 2
2 3 10
3 12 416024
分析

懂了题目以后感觉这题不难,但是没做,这是因为这题的输出没看懂,问了才知道,输出的意思是:样例号数、n、最短路径数。可以用递归做,因为数据量也不大,我用递推,思维差不多是一样的。还有一种做法是用到了卡特兰数,两个都介绍一下:

法一:常规dp
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[40][40];
void solve(){
    for(int i=0;i<=35;++i)f[i][0]=1;
    for(int i=1;i<=35;++i)
        for(int j=1;j<=i;++j)
            f[i][j]=f[i-1][j]+f[i][j-1];
}
int main(){
    solve();
    int n,cnt=0;
    while(~scanf("%d",&n)&&n!=-1){
        printf("%d %d %lld\n",++cnt,n,2*f[n][n]);
    }
    return 0;
}

法二:卡特兰数
强推大家看这篇文章:
卡特兰数Catalan

直接用公式:h(n) = h(0)h(n-1) + h(1)h(n-2)+……+h(n-1)h(0)
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll h[40];
void solve(){
    h[0]=1;
    for(int i=1;i<=35;++i){
        h[i]=0;
        for(int j=0;j<i;++j)
            h[i]+=h[j]*h[i-j-1];
    }
}
int main(){
    solve();
    int n,cnt=0;
    while(~scanf("%d",&n)&&n!=-1){
        printf("%d %d %lld\n",++cnt,n,2*h[n]);
    }
    return 0;
}

O:RGP的错排

题目描述
今年暑假杭电ACM集训队第一次组成女生队,其中有一队叫RPG,但做为集训队成员之一的野骆驼竟然不知道RPG三个人具体是谁谁。RPG给他机会让他猜猜,第一次猜:R是公主,P是草儿,G是月野兔;第二次猜:R是草儿,P是月野兔,G是公主;第三次猜:R是草儿,P是公主,G是月野兔;…可怜的野骆驼第六次终于把RPG分清楚了。由于RPG的带动,做ACM的女生越来越多,我们的野骆驼想都知道她们,可现在有N多人,他要猜的次数可就多了,为了不为难野骆驼,女生们只要求他答对一半或以上就算过关,请问有多少组答案能使他顺利过关。
输入
输入的数据里有多个case,每个case包括一个n,代表有几个女生,(n<=25), n = 0输入结束。
输出
输出有多少组答案能使他顺利过关
输入样例
1
2
0
输出样例
1
1
分析

这道题和上一次热身练习的新郎娶娘子有点像,都是错排问题,注意的就是想要的是各种能让人他过关的情况数,那就需要满足错的题目数量再[0,n/2]之间,循环+错排就能过。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[20],s[30][30];
int n;
void solve(){
    f[0]=1,f[1]=0,f[2]=1;
    for(int i=3;i<=15;++i)f[i]=(i-1)*(f[i-1]+f[i-2]);
    for(int i=1;i<=25;++i)
        for(int j=0;j<=25;++j)
            if(i<j)s[i][j]=0;
            else if(j==0||j==i)s[i][j]=1;
            else s[i][j]=s[i-1][j-1]+s[i-1][j];
}
int main(){
    solve();
    while(~scanf("%d",&n)&&n) {
        ll sum=f[0];
        for(int i=1;i<=(n>>1);++i)
            sum+=f[i]*s[n][i];
        printf("%lld\n",sum);
    }
    return 0;
}

P:Cion Change

题目描述
Suppose there are 5 types of coins: 50-cent, 25-cent, 10-cent, 5-cent, and 1-cent. We want to make changes with these coins for a given amount of money.

For example, if we have 11 cents, then we can make changes with one 10-cent coin and one 1-cent coin, or two 5-cent coins and one 1-cent coin, or one 5-cent coin and six 1-cent coins, or eleven 1-cent coins. So there are four ways of making changes for 11 cents with the above coins. Note that we count that there is one way of making change for zero cent.

Write a program to find the total number of different ways of making changes for any amount of money in cents. Your program should be able to handle up to 100 coins.
输入
The input file contains any number of lines, each one consisting of a number ( ≤250 ) for the amount of money in cents.
输出
For each input line, output a line containing the number of different ways of making changes with the above 5 types of coins.
输入样例
11
26
输出样例
4
13
分析

硬币问题变化,但是再dp数组上再加一维来存储硬币数量,题目要求兑换的硬币数量不超过100个

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int coins[]={1,5,10,25,50};
int f[255][105],s[255];
void solve(){
    f[0][0]=1;
    for(int i=0;i<5;++i)
        for(int j=coins[i];j<=250;++j)
            for(int k=1;k<=100;++k)
                f[j][k]+=f[j-coins[i]][k-1];
}
int main(){
    int n;
    solve();
    while(~scanf("%d",&n)){
        if(!n){printf("1\n");continue;}
        int ans=0;
        for(int i=1;i<=100;++i)ans+=f[n][i];
        printf("%d\n",ans);
    }
    return 0;
}

S:单词数

题目描述
lily的好朋友xiaoou333最近很空,他想了一件没有什么意义的事情,就是统计一篇文章里不同单词的总数。下面你的任务是帮助xiaoou333解决这个问题。
输入
有多组数据,每组一行,每组就是一篇小文章。每篇小文章都是由小写字母和空格组成,没有标点符号,遇到#时表示输入结束。
输出
每组只输出一个整数,其单独成行,该整数代表一篇文章里不同单词的总数。
输入样例
you are my friend

输出样例
4
分析

这题直接整麻了,用cin的方法一直过不了,用了getline输入然后stringstream处理才过的,顺带写一下

代码如下:

#include<bits/stdc++.h>
using namespace std;
int main(){
    set<string>s;
    string str;
    while(getline(cin,str)&&str.compare("#")!=0){
        stringstream ss(str);
        while(ss>>str)s.insert(str);
        cout<<s.size()<<endl;
        s.clear();
    }
}

T:无限的路

题目描述
甜甜从小就喜欢画图画,最近他买了一支智能画笔,由于刚刚接触,所以甜甜只会用它来画直线,于是他就在平面直角坐标系中画出如下的图形:
在这里插入图片描述
甜甜的好朋友蜜蜜发现上面的图还是有点规则的,于是他问甜甜:在你画的图中,我给你两个点,请你算一算连接两点的折线长度(即沿折线走的路线长度)吧。
输入
第一个数是正整数N(≤100)。代表数据的组数。
每组数据由四个非负整数组成x1,y1,x2,y2;所有的数都不会大于100。
输出
对于每组数据,输出两点(x1,y1),(x2,y2)之间的折线距离。注意输出结果精确到小数点后3位。
输入样例
5
0 0 0 1
0 0 1 0
2 3 3 1
99 99 9 9
5 5 5 5
输出样例
1.000
2.414
10.646
54985.047
0.000
分析

没啥难的,要注意(0,0)这个小细节点,然后抓住点的横纵坐标相加就是那条线,我们知道相邻两点的距离为根号2,只要知道两点之间有多少个根号2以及两点中间经过多少个长折线以及计算方式就行了

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define Swap(a,b) {double temp=a;a=b;b=temp;}
using namespace std;
int main(){
    int n,x1,x2,y1,y2;cin>>n;
    while(n--){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        if(x1+y1==x2+y2){printf("%.3lf\n",sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));continue;}
        if(x1+y1>x2+y2){Swap(x1,x2);Swap(y1,y2);}
        double len=0;
        if(x1==0&&y1==0){
            if(x2==0&&y2==1){cout<<"1.000"<<endl;continue;}
            else if(x2==1&&y2==0){cout<<2.414<<endl;continue;}
            y1=1,len++;
        }
        int k=y1+x2;
        for(int i=x1+y1+1;i<x2+y2;++i)k+=i;
        len+=k*sqrt(2);
        for(int i=x1+y1;i<x2+y2;++i)len+=sqrt((i*i)+((i+1)*(i+1)));
        printf("%.3lf\n",len);
    }
}

U:叠框

题目描述
需要的时候,就把一个个大小差一圈的筐叠上去,使得从上往下看时,边筐花色交错。这个工作现在要让计算机来完成,得看你的了。
输入
输入是一个个的三元组,分别是,外筐尺寸n(n为满足0<n<80的奇整数),中心花色字符,外筐花色字符,后二者都为ASCII可见字符;
输出
输出叠在一起的筐图案,中心花色与外筐花色字符从内层起交错相叠,多筐相叠时,最外筐的角总是被打磨掉。叠筐与叠筐之间应有一行间隔。
输入样例
11 B A
5 @ W
输出样例
AAAAAAAAA
ABBBBBBBBBA
ABAAAAAAABA
ABABBBBBABA
ABABAAABABA
ABABABABABA
ABABAAABABA
ABABBBBBABA
ABAAAAAAABA
ABBBBBBBBBA
AAAAAAAAA

@@@
@WWW@
@W@W@
@WWW@
@@@

分析

一个简单的搜索题,直接从最中间开始8个方向广搜,flag标志来确定输出什么。不过这题有坑,坑就在n=1的情况,还有就是要防止输出出错的,这里面要求的是两个叠框之间有空行,而不是输出一个叠框就空行。直接举个例子,如果输入终止(ctrl+z)呢?那也就根本不会产生叠框,因此前面不需要空行,所以要判断输入的情况来确定会不会输出叠框,从而确定要不要空行

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
    int x,y,flag;
};
int dir[8][2]={1,0,-1,0,0,1,0,-1,1,1,1,-1,-1,1,-1,-1};
char a,b;int n;
char maze[85][85];
bool vis[85][85];
bool judge(int x,int y){
    if(x>=1&&y>=1&&x<=n&&y<=n&&!vis[x][y])return true;
    return false;
}
void bfs(int x,int y,int flag){
    memset(vis,false,sizeof(vis));
    queue<node>q;q.push({x,y,flag});
    vis[x][y]=true;
    while(!q.empty()){
        node u=q.front();q.pop();
        int x=u.x,y=u.y,flag=u.flag;
        if(flag)maze[x][y]=a;
        else maze[x][y]=b;
        int newx,newy;
        for(int i=0;i<8;++i){
            newx=x+dir[i][0],newy=y+dir[i][1];
            if(judge(newx,newy))
                if(flag)q.push({newx,newy,0}),vis[newx][newy]=true;
                else q.push({newx,newy,1}),vis[newx][newy]=true;
        }
    }
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j)
            if(i==1&&j==1||i==1&&j==n||i==n&&j==1||i==n&&j==n)printf(" ");
            else printf("%c",maze[i][j]);
        cout<<endl;
    }
}
int main(){
    int cnt=0;
    while(~scanf("%d %c %c",&n,&a,&b)){
        if(cnt)cout<<endl;
        else cnt=1;
        if(n==1){printf("%c\n",a);continue;}
        int m=n>>1;
        bfs(m+1,m+1,1);
    }
}

X:汉诺塔IV

题目描述
还记得汉诺塔III吗?他的规则是这样的:不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到小盘的上面。xhd在想如果我们允许最大的盘子放到最上面会怎么样呢?(只允许最大的放在最上面)当然最后需要的结果是盘子从小到大排在最右边。
输入
输入数据的第一行是一个数据T,表示有T组数据。
每组数据有一个正整数n(1 <= n <= 20),表示有n个盘子。
输出
对于每组输入数据,最少需要的摆放次数。
输入样例
2
1
10
输出样例
2
19684
分析

有些人是直接找规律的,当然也可以,不过仍然要知道规律的意义。首先我们和汉诺塔3一样把步骤列出来:
(1)把n-1个盘子通过C放到B
(2)把第n个盘子通过B放到C
(3)把n-1个盘子通过A放到C
我们可以把其和汉诺塔3做比较,那就是第n个盘子可以放到最上层,仅此而已,而剩下n-1个盘子呢?仍然是按照汉诺塔3的规则放的!
因此我们只需要把那n-1个盘子按照汉诺塔3的规矩放,然后+2即可啦。
即ans=f[n-1]+2=2*f[n-2]+2

代码如下(推出了直接公式,打表也行,懒得写推导了,和前面思维一样):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    int t;cin>>t;
    while(t--){
        int n;scanf("%d",&n);
        int sum=1;
        for(int i=1;i<n;++i)sum=sum*3;
        printf("%d\n",sum+1);
    }
}

Z:选课时间

题目描述
又到了选课的时间了,xhd看着选课表发呆,为了想让下一学期好过点,他想知道学n个学分共有多少组合。你来帮帮他吧。(xhd认为一样学分的课没区别)
输入
输入数据的第一行是一个数据T,表示有T组数据。
每组数据的第一行是两个整数n(1 <= n <= 40),k(1 <= k <= 8)。
接着有k行,每行有两个整数a(1 <= a <= 8),b(1 <= b <= 10),表示学分为a的课有b门。
输出
对于每组输入数据,输出一个整数,表示学n个学分的组合数。
输入样例
2
2 2
1 2
2 1
40 8
1 1
2 2
3 2
4 2
5 8
6 9
7 6
8 8
输出样例
2
445
分析

多重背包

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int f[45];
int main(){
    int t;cin>>t;
    while(t--){
        int n,k;
        scanf("%d%d",&n,&k);
        memset(f,0,sizeof(f));
        f[0]=1;
        while(k--){
            int a,b;
            scanf("%d%d",&a,&b);
            for(int j=n;j>=a;--j)
                for(int m=1;m<=b&&j>=m*a;++m)f[j]+=f[j-a*m];
 
        } 
        printf("%d\n",f[n]);
    }
}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最负责的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值