北京化工大学寒假集训【BUCTOJ】(1)1-6题

1.快速幂

题目描述
求 a 的 b 次方对 p 取模的值,其中 0≤a,b≤10^9 , 0<p≤10^9
输入
三个用空格隔开的整数a,b和p。
输出
一个整数,表示a^b mod p的值。
样例输入
2 3 9
样例输出
8
对于本题,按照以往的思路是直接循环进行乘法,同时,边乘边取模,最终能够得到结果,但是本题由于乘的次数过多导致运算时间过于长,所以要考虑的就是怎样通过改进算法来缩短计算时间
这里就有了快速幂,快速幂的目的就是快速求出幂,假设我们要求a^b,按照朴素算法就是把a连乘b次,这样一来时间复杂度是O(b)也即是O(n)级别,快速幂能做到O(logn),快了好多好多。它的原理如下:
当幂指数非常大的时候,我们并没有必要一次又一次的反复求,假设我们本来要求21024我们可以依次求得22、24、28、……21024这样,原本需要1024次循环,现在只用了10次,时间也就大大缩短了。
同理我们可以推进到一般情况,b可以拆解为若干不重复的2的次幂的和的形式,即可以用二进制的形式来表示,巧妙的运用位运算或者模运算,可以得到每一位前的系数为0或者1来达到快速幂的目的

#include<bits/stdc++.h>
using namespace std;
long long a,b,p;
long long f(long long a,long long b,long long p)
{
    
    
    if(!p) return 0;
    long long flag=1;
    while(b)
    {
    
    
        if(b&1)  flag=flag*a%p;
        a=a*a%p;
        b>>=1;
    }
    flag %=p;
    return flag;
}
int main()
{
    
    
    scanf("%lld%lld%lld",&a,&b,&p);
    printf("%lld",f(a,b,p));
}

2.高精度乘法

题目描述
求a乘b对p取模的值,其中1≤a,b,p≤1018
输入
输入3个long long型整数,a,b,p
输出
输出a*b%p的值
样例输入
250182048980811753
413715569939057660
133223633696258584
样例输出
19308689043391716
对于本题,基本思想与第一题相同,只不过是将原本最终处理的乘法换为加法

#include<bits/stdc++.h>
using namespace std;
long long a,b,p;
long long f(long long a,long long b,long long p)
{
    
    
    long long flag=0;
    while(b)
    {
    
    
        if(b&1)  flag=(flag+a)%p;
        a=(a+a)%p;
        b>>=1;
    }
    flag%=p;
    return flag;
}
int main()
{
    
    
    scanf("%lld%lld%lld",&a,&b,&p);
    printf("%lld",f(a,b,p));
}

3.Raising Modulo Numbers

题目描述
People are different. Some secretly read magazines full of interesting girls’ pictures, others create an A-bomb in their cellar, others like using Windows, and some like difficult mathematical games. Latest marketing research shows, that this market segment was so far underestimated and that there is lack of such games. This kind of game was thus included into the KOKODáKH. The rules follow:

Each player chooses two numbers Ai and Bi and writes them on a slip of paper. Others cannot see the numbers. In a given moment all players show their numbers to the others. The goal is to determine the sum of all expressions AiBi from all players including oneself and determine the remainder after division by a given number M. The winner is the one who first determines the correct result. According to the players’ experience it is possible to increase the difficulty by choosing higher numbers.

You should write a program that calculates the result and is able to find out who won the game.
输入
The input consists of Z assignments. The number of them is given by the single positive integer Z appearing on the first line of input. Then the assignements follow. Each assignement begins with line containing an integer M (1 <= M <= 45000). The sum will be divided by this number. Next line contains number of players H (1 <= H <= 45000). Next exactly H lines follow. On each line, there are exactly two numbers Ai and Bi separated by space. Both numbers cannot be equal zero at the same time.
输出
For each assingnement there is the only one line of output. On this line, there is a number, the result of expression
(A1B1+A2B2+ … +AHBH)mod M.
样例输入
3
16
4
2 3
3 4
4 5
5 6
36123
1
2374859 3029382
17
1
3 18132
样例输出
2
13195
13
只要读懂了本题的大概题意,就会发现这一题其实就是第一题的求和版,具体思路与第一题相同,将第一题的快速幂作为一个函数,每次输入都运行一次这个函数最终求和取模就可以达到目的

#include<bits/stdc++.h>
using namespace std;
long long f(long long a,long long b,int m){
    
    
    int flag=1;
    while(b)
    {
    
    
        if(b&1)  flag=flag*a%m;
        a=a*a%m;
        b>>=1;
    }
    flag%=m;
    return flag;
}
int main()
{
    
    
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n;i++)
    {
    
    
        int m;
        scanf("%d", &m);
        int k;
        scanf("%d", &k);
        long long sum = 0;
        for (int j = 0; j < k;j++)
        {
    
    
            long long a, b;
            scanf("%lld %lld", &a, &b);
            sum += f(a, b, m);
        }
        sum %= m;
        printf("%lld\n", sum);
    }
}

4.最短Hamilton路径

题目描述
给定一张 n(n≤20) 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
输入
第一行一个整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10^7的正整数,记为a[i,j])。

对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。
输出
一个整数,表示最短Hamilton路径的长度。
样例输入
4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0
样例输出
4
本题第一眼想到的算法就是暴力算法:就是枚举n个点的全排列,计算路径长度的最小值,时间复杂程度为o(n*n!),而通过DP压缩我们可以把时间缩短到o(n2*2n).
在任意时刻如何表示哪些点已经被经过了呢?可以用一个n位的二进制数,如果它第i位为1,那么就表示第i个点已经被经历过,反之则未经历,同时我们还要知道当前的位置,所以就用一个二维数组f[1 << 20][20]来表示状态和位置。
下面我们列举任意时刻的常规情况,可以得到一个递推公式为:f[i][j] = min(f[i][j],f[i - (1 << j)][k] + weight[k][j])
举个例子,例如当i等于11的时候,i的二进制表示为1011,i表示编号为0,1,3的人都已经走过了,我们就枚举当前位置,比如当前在1号位置,那么前一个状态就是1001,我们可以从0号位,或者3号位走到1号位,也就是在前一个状态中找到最小的出发点到1号位。
当每次走过这个位置的时候只需要通过位运算删除这个点就行了

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
int f[1 << 20][20]; //f[i][j]表示i的状态,为1则代表经过,为0则代表没经过。j代表当前所处的位置。
int weight[20][20];
int main()
{
    
    
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            cin >> weight[i][j];
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;  //起点为0,i为1代表经过0这个起点,j为0代表当前在0这个位置
    for(int i = 0; i < 1 << n; i++)
        for(int j = 0 ; j < n; j++)
            if(i >> j & 1){
    
     //如果经过了第j位,则与上一状态比较,寻找最小值
                for(int k = 0; k < n; k++)
                    if(i - (1 << j) >> k & 1)//表示上一状态没有经过第j位,经过了第k位的情况
                        f[i][j] = min(f[i][j],f[i - (1 << j)][k] + weight[k][j]);}//取最小值
    cout << f[(1 << n) - 1][n - 1] << endl;
    return 0;
}

5.起床困难综合征

题目描述
背景
21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因:在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。正是由于 drd 的活动,起床困难综合症愈演愈烈,以惊人的速度在世界上传播。为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。
描述
历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd 的防御战线由 n扇防御门组成。每扇防御门包括一个运算op和一个参数t,其中运算一定是OR,XOR,AND中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为x,则其通过这扇防御门后攻击力将变为x op t。最终drd 受到的伤害为对方初始攻击力x依次经过所有n扇防御门后转变得到的攻击力。
由于atm水平有限,他的初始攻击力只能为0到m之间的一个整数(即他的初始攻击力只能在0,1,…,m中任选,但在通过防御门之后的攻击力不受 m的限制)。为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。
输入
第1行包含2个整数,依次为n,m,表示drd有n扇防御门,atm的初始攻击力为0到m之间的整数。接下来n行,依次表示每一扇防御门。每行包括一个字符串op和一个非负整数t,两者由一个空格隔开,且op在前,t在后,op表示该防御门所对应的操作, t表示对应的参数。
n<=105,0<=m<=109,0<=t<=109 ,op一定为OR,XOR,AND 中的一种

输出
一行一个整数,表示atm的一次攻击最多使 drd 受到多少伤害。
样例输入
3 10
AND 5
OR 6
XOR 7
样例输出
1
本题看似复杂,不难发现位运算有基本规律,就是不会出现进位或者借位的现象,并且在二进制中只用两种,即1或者0,我们就可以通过全为1和全为0的数位运算后得到的结果,从高到低,用每个参数的第k位进行位运算,运用贪心的思想,让运算后的高位尽可能为1,同时原本的数不超过m的大小即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    
    
    int n,m;
    cin>>n>>m;
    int a0=0,a1=-1;
    for(int i=1;i<=n;i++){
    
    
        string s;
        cin>>s;
        int d;
        cin>>d;
            if(s[0]=='A')a1&=d,a0&=d;
            if(s[0]=='O')a1|=d,a0|=d;
            if(s[0]=='X')a1^=d,a0^=d;
    }
    ll ans=0;
    for(int i=29;i>=0;i--){
    
    
        if(a0>>i&1)ans+=1<<i;
        else if((a1>>i&1)&&((1<<i)<=m))ans+=1<<i,m-=1<<i;
    } 
    cout<<ans<<endl;
}

6.递归性实现排列性枚举

题目描述
把 1~n 这 n(n<10) 个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入
一个整数n。
输出
按照从小到大的顺序输出所有方案,每行1个。 首先,同一行相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
样例输入
3
样例输出
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
这一题就是一个简单的递归问题,递归,本质上来讲就是每次都能够套用相同的结局方式,假如给的只有两个数,那么排列方式很简单,加入有三个呢?我们把第一个固定,再求后两个数的全排列,假如更多呢?如果有6个呢?那就先固定第一个求剩下的全排列,固定的这一个可能是第一个,也可能是第二个,第三个……,固定一个后再固定一个,以此类推,这样就相当于可以套用相同的公式的递归了。

扫描二维码关注公众号,回复: 12235991 查看本文章
#include<stdio.h>
int a[9]={
    
    0}, flag[10]={
    
    0};
int p(int c,int n){
    
    
    if(c==n){
    
    
        for (int i = 0; i < n;i++)
            printf("%d ", a[i]);
        printf("\n");
    }
    else{
    
    
        for (int i = 1; i <= n;i++)
        {
    
    
            
            if(flag[i])
                continue;
            a[c] = i;
            flag[i] = 1;
            p(c + 1, n);
            flag[i] = 0;
        }
    }

}
int main()
{
    
    
    int n;
    while(scanf("%d", &n)!=EOF)
    p(0, n);
}

猜你喜欢

转载自blog.csdn.net/weixin_51327661/article/details/113013197
1-6
今日推荐