Bootstrap

2019年秋季学期上机赛&练习赛小总结(自用)

整除

题目描述:
已知四个整数A,B,C,D,请求出[A,B]中既不能被C整除也不能被D整除的整数的个数。
输入
多组输入,每组一行,分别为四个整数A,B,C,D,满足1≤A≤B≤1e18,1≤C,D≤1e9
输出
每组数据输出一行,为符合题意的整数个数

思路
由于1e18,逐个遍历会TLE,故使用容斥原理
[A,B]中能被c整除的整数个数为(b/c-(a-1)/c)个
辗转相除法求出cd的最大公约数mul
则最小公倍数为c*d/mul
则答案可以记为(b-a+1)-(n1+n2-n3)
代码(个人向)

#include <stdio.h>
#include <stdlib.h>

long long int lcm(long long int a,long long int b)
{
    long long int m;
    while(b>0)
    {m=a%b;a=b;b=m;}
    return a;
}

int main()
{
    long long int a,b,c,d;
    long long int x1,x2,x3,mul,ans;
    while (scanf("%lld%lld%lld%lld",&a,&b,&c,&d)!=EOF)
    {
        x1=b/c-(a-1)/c;
        x2=b/d-(a-1)/d;
        if(c<d)	  {mul=c;c=d;d=mul;}
        mul=(c*d)/lcm(c,d);
        x3=b/mul-(a-1)/mul;
        ans=b-a+1-(x1+x2-x3);
        printf("%lld\n",ans);
    }
    return 0;
}

约瑟夫环

题目描述
有n个人围成一环顺时针报数,报到m及m的正整数倍的人出列,其余人继续,求最后剩下的一个人的编号
输入
两个正整数n,m( 1≤n,m≤3000)
输出
一个正整数编号

一般算法:使用数组

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int a[3005]={0};
    int n,m,i,f=0,t=0,s=0;
    scanf("%d%d",&n,&m);
    do
    {
        ++t;
        if(t>n)	t=1;
        if(a[t]==0) s++;
        if(s==m)
        {
            s=0;
            a[t]=1;
            f++;
        }
    }while(f!=n-1);
    for(i=1;i<=n;i++)
    {if(a[i]==0) printf("%d",i);}
    return 0;
}

数学算法
每出列一人(k号),环上人从k+1开始重新编号,组成新的约瑟夫环。

k+1 k+2 k+3 … k-1
1-----2-----3-- … n-1

p重组后编号改变为p’,则有p=(p’+k)%n
∴有递推公式 f [ p ] = ( f [ p-1 ] + m )%p

#include <stdio.h>
#include <stdlib.h>
 int main()
 {
     int n,m,i,s = 0;
     scanf("%d%d",&n,&m);
     for (i = 2; i <= n; i++) {s=(s+m)%i;}
     printf ("%d\n", s+1);
     return 0;
 }

倒序汉诺塔

题目描述
在一根柱子上从上往下按照大小顺序摞着n片黄金圆盘,把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在大圆盘上不能放小圆盘,在三根柱子之间一次只能移动一个圆盘。请输出全部的移动。
输入
一个整数n,表示汉诺塔的层数(n < 20)
输出
step #%d : move %d from %c to %c
思路参考和基础原码来自这里

递归问题的重要思考方式:只考虑一步的过程,上一步当作已知即可

#include <stdio.h>
#include <stdlib.h>
int ctr,w;

void move(int id, char from, char to)
{printf("step #%d: move %d from %c to %c\n",++ctr,w-id+1,from,to);}

void hanoi(int n, char x, char y, char z)
{
	if(n==0) return;
	hanoi(n-1,x,z,y);//把x上的n-1个盘子经由z移到y上
	move(n,x,z);//把最下面的盘子移到z
	hanoi(n-1,y,x,z);//把y上n-1个盘子移到z上
}


 int main()
 {
	int n,ctr;
	scanf("%d",&n);
	w=n;
	hanoi(n,'A','B','C');
	return 0;
 }

整数拆分

问题描述
提供一个正整数,试把它拆成若干个2的非负整数幂的和,并输出有多少种拆分方法。
输入
一个整数n( 0<n ≤ 200 )
输出
一个整数,为拆分方法数

主要在于求出递推公式,然而我还是不会。
主要思路和递推公式来源于这里

注意一点,f(2m)=f(2m-1)+f(m)中 如果把2m-1写成2m,虽然理论上数值是一样的但是oj会REG,是因为f(2-2)没有定义。亲测如果增加判定f(2)=2就能过了。

补充一些我的思考。
易知f(2m+1)=f(2m),难点主要是f(2m)=f(2m-1)+f(m)的证明。
对于所有f(2m)的全拆分,将其分为包含1和不包含1的部分。其中包含1的部分肯定至少包含两个1,则该部分的数量和f(2m-2)是相等的(相当于各个分裂加上两个1);不含1的部分全部除2,则相当于f(m)的排列。

#include <stdio.h>
#include <stdlib.h>

int dev(int x)
{
    int ans;
    if(x%2==1 && x!=1) ans=dev(x-1);
    if(x%2==0) ans=(dev(x-1)+dev(x/2));
    if(x==1) ans= 1;
    return ans;

}

int main()
{
    int x, ans;
    scanf("%d",&x);

    ans=dev(x);
    printf("%d",ans);
    return 0;
}

求解非齐次线性方程组

问题描述
求解一方程个数和未知数个数相等的非齐次线性方程组,如果方程组有唯一解输出此解,否则输出字符串No Solution!
输入
一组数据,多行输入。
第一行一个整数 nn,代表方程个数和未知数个数。
接下来 nn 行每行 n+1n+1 个整数,代表每一个方程。
例如,对于第 i 行(首行是第11行)的 n+1 个整数分别是:ai1, ai2, ai3, …, ain,bi
其代表的方程是:ai1x1+ai2x2+ai3x3+ … +ainxn=bi
输出
一行,n个由单个空格隔开的两位小数(四舍五入)代表 x1,x2, … ,xn的唯一解。
如果没有唯一解则输出字符串No Solution!

高斯消元法的列主元消去法的参考

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
double a[110][110];
void print(double a[][110],int n)
{
    int i,j;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n+1;j++)
            printf("%.2lf ",a[i][j]);
        printf("\n");
    }

}

void swap(double *a,double *b){double t;t=*a;*a=*b;*b=t;}


void rref(double a[][110],int n)
{
    int i,j,k,r,m;double temp;
    for(i=1;i<=n;i++)
    {
        r=i;
        for(j=i+1;j<=n;j++)
        {
            if(a[j][r]>a[i][r])
                r=j;
        }
        if(r!=i)
        {
            for(k=i;k<=n+1;k++)
                    swap(&a[r][k],&a[i][k]);
        }
        for(j=i+1;j<=n;j++)
        {
            temp=a[j][i]/a[i][i];
            for(k=i;k<=n+1;k++)
            {   a[j][k]-=a[i][k]*temp;}
        }
    }
}

int  guess(double a[][110],int n)
{
    int i,j;

    rref(a,n);
    for(i=n;i>=1;i--)
    {
        if(a[i][i]==0) return 1;
        else{
        for(j=i+1;j<=n;j++)
            a[i][n+1]-=a[i][j]*a[j][n+1];
        a[i][n+1]/=a[i][i];}
    }
    return 0;
}

int main()
{

    int n,i,j,t;
    scanf("%d",&n);t=n;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n+1;j++)
            {scanf("%lf",&a[i][j]);}
    }
    if(guess(a,n)) {printf("No Solution!");}
    else
     {
         for(i=1;i<=n;i++)
        printf("%.2lf ",a[i][n+1]);
     }
    return 0;
}

表达式输出

问题描述
已知一个表达式,可以将小括号()内的内容匹配两遍。请输出表达式匹配的字符串,即将输入字符串小括号中的内容重复2遍,多层小括号作迭代处理。可结合输入输出样例理解。
输入
一行,一个字符串,即输入的表达式,保证所有字符ASCII码在[33,126]区间内。字符串长度不超过30。表达式的括号一定是前后匹配的。
输出
对于每组数据,输出一行,表达式匹配的字符串。
输入样例
123((0)(1))
输出样例
12300110011

递归,对每个字符单独处理

#incude<stdio.h>
int pr(int i){
    int t;
    if(s[i]=='('){
        t=pr(i+1);//寻找位置返回值
        pr(i+1);//输出两遍
        return pr(t);//从新位置开始递归
    }
    else if(s[i]==')'){
        return i+1;//返回位置
    }
    else if(s[i]=='\0'){
        return i;//返回位置
    }
    else{
        printf("%c",s[i]);
        return pr(i+1);//递归
    }
}
int main()
{
    fgets(s,50,stdin);
    pr(0);
    return 0;
}

 
;