第0届浙江工业校赛

来源:互联网 发布:500人企业网络搭建 编辑:程序博客网 时间:2024/04/30 00:27

Problem A: qwb与支教

Description

qwb同时也是是之江学院的志愿者,暑期要前往周边地区支教,为了提高小学生的数学水平。她把小学生排成一排,从左至右从1开始依次往上报数。

玩完一轮后,他发现这个游戏太简单了。于是他选了3个不同的数x,y,z;从1依次往上开始报数,遇到x的倍数、y的倍数或z的倍数就跳过。如果x=2,y=3,z=5;第一名小学生报1,第2名得跳过2、3、4、5、6,报7;第3名得跳过8、9、10,报11。

那么问题来了,请你来计算,第N名学生报的数字是多少?
Input

多组测试数据,处理到文件结束。(测试数据数量<=8000)

每个测试例一行,每行有四个整数x,y,z,N。( 2≤x,y,z≤107,1≤N≤1017)。
Output
对于每个测试例,输出第N名学生所报的数字,每个报数占一行。
Sample Input

2 3 5 2
6 2 4 10000

Sample Output

7
19999
这是一道容斥+二分的题目,首先分析,假设sum为从1~n中倍数为x,y,z的数,那么n-p就是报数的个数,这里用二分查找就可以很方便的找到,所以重点落在了求sum,这里用到了容斥定理:|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |B∩C| - |C∩A| + |A∩B∩C|,这里 |A∩B| =n/a和b的最小公倍数,而最小公倍数=a*b/a和b的最大公约数,这样就可以做出来啦
附上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;LL gcd(LL x,LL y) {    if(y==0) return x;    return gcd(y,x%y);}LL lcm(LL a,LL b) {    return (a/gcd(a,b))*b;}int main(){    LL x,y,z,n;    while(~scanf("%lld%lld%lld%lld",&x,&y,&z,&n)) {        LL l=0,r=1e18;        while(l+1<r) {            LL sum=0,m=(l+r)/2;            sum+=(m/x+m/y+m/z);            sum-=(m/lcm(x,y)+m/lcm(y,z)+m/lcm(x,z));            sum+=m/lcm(lcm(x,y),z);            if(m-sum>=n) r=m;            else l=m;        }        printf("%lld\n",r);    }    return 0;}

原来我在想能不能在m-sum==n的时候就退出,后来发现不可以,比如说第一个例子2,3,5,第一个数字是1,第二个数字是7,但如果直接从m-sum==n就直接退出,得到的答案是9,因为8,9,10都是不能报的数,但比第二个报的数7大比第三个报的数11小,所以不能直接退出

Problem B: qwb与矩阵

Description

做完了辣么多的数学题,qwb好好睡了一觉。但是他做了一个梦:

有一个n*m的矩阵,qwb在这个矩阵的左上角(1,1),终点在右下角(n,m)。
每个格子中有小钱钱,也可能没有,还有可能是要交过路费的,并且行走方向必须是靠近终点的方向。
往下走一次只能走一格,往右走一次可以走一格也可以走到当前列数的倍数格。
比如当前格子是(x,y),那么可以移动到(x+1,y),(x,y+1)或者(x,y*k),其中k>1。

qwb希望找到一种走法,使得到达右下角时他能够有最多的小钱钱。

你能帮助他吗?
Input
第一行是测试例数量 T (T<=100),接下来是T组测试数据。
每组测试数据的第一行是两个整数n,m,分别表示行数和列数(1<=n<=20,1<=m<=10000);
接下去给你一个n*m的矩阵,每个格子里有一个数字 k (-100<=k<=100)代表小钱钱的数量。 ∑nm<=3,000,000

Output
每组数据一行,输出L先生能够获得小钱钱的最大值(可能为负数)。
Sample Input

1
3 8
9 10 10 10 10 -10 10 10
10 -11 -1 0 2 11 10 -20
-11 -11 10 11 2 10 -10 -10

Sample Output

52
我也不知道这题怎么做….看别人才会的…..
第一次用记忆化搜索做题,本来没打算学,但看过的人好多,就临时学了学
记忆化搜索=动态规划+搜索,可以通过剪枝大大降低运行时间

附上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int INF=0x3f3f3f3f;int ans[21][10005],dp[21][10005],visit[21][10005];int n,m;int dfs(int x,int y) {    if(visit[x][y]) return dp[x][y];    int temp[2][2]={1,0,0,1},sum=-INF;    for(int i=0;i<2;i++) {        int temp_x=x+temp[i][0];        int temp_y=y+temp[i][1];        if(temp_x<=n&&temp_y<=m)            sum=max(sum,dfs(temp_x,temp_y));    }    for(int i=2;y*i<=m;i++) {        int temp_x=x;        int temp_y=y*i;        sum=max(sum,dfs(temp_x,temp_y));    }    visit[x][y]=1;    return dp[x][y]=sum+ans[x][y];}int main(){    int T;    scanf("%d",&T);    for(int i=0;i<T;i++) {        memset(visit,0,sizeof(visit));        memset(dp,0,sizeof(dp));        scanf("%d%d",&n,&m);        for(int j=1;j<=n;j++) {            for(int k=1;k<=m;k++)                scanf("%d",&ans[j][k]);        }        visit[n][m]=1;dp[n][m]=ans[n][m];        printf("%d\n",dfs(1,1));    }    return 0;}

visit[n][m]=1;dp[n][m]=ans[n][m];这步我觉得挺关键的,用visit标记,当深搜到dp[n][m]时就退出,然后用dp[x][y]=sum+ans[x][y]来计算最后的和,这里的sum是从(x,y)点的各个合法方向到(n,m)的最大和,所以最后要加上ans[x][y],当遇到(n,m)时就返回dp[n][m],所以dp[n][m]=ans[n][m];参考别人代码好久才写出来,我感觉给我肯定写不出来……继续努力吧。

Problem C: 勤劳的ACgirls

Description
zjc的ACgirls队的队员最近比较忙,为了能够取得更好的比赛成绩,他们制定了一个m天a掉n题的计划,a掉一题可以是这m天的任何时候。
为了表示对acmer事业的热爱,队长wc要求每天必须至少要ac掉k题,这m天每天ac掉的题数可以用一个m元组表示。
设不同的m元组一共有c个,请问c的末尾有多少个0?(如果c是0,输出0)
Input

多组测试数据,处理到文件结束。(测试例数量<=160000)

输入的每一行是一个测试例,分别是m、n和k(0<=m,n,k<=1e9),含义如前所述。

Output
每组测试例中m元组的数量的末尾0的个数,占一行。

Sample Input

3 11 0
3 11 1
999 99999 4

Sample Output

0
0
5

感觉是道组合数学的题,一上来就想用Lucas,毕竟组合数只会Lucas,然后看了大佬的题解,后来才发现是高中学的插板法…..当时老师说这种东西玄学,后来发现还是很有用的
插板法链接:维基百科
里面有一个公式:n个球放进k个盒子的方法总数(允许空盒子)为 这里写图片描述
我们把这个公式扩展一下就是把n个球放进m个盒子中,每个盒子最少有k个球,公式为:C(n+(1-k)*m-1,m-1)
然后怎么算有多少个0,只要算这个组合数中2和5有多少个就好了
这里用到这里写图片描述分别计算每个的因子
附上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int INF=0x3f3f3f3f;int main(){    LL m,n,k;    while(~scanf("%lld%lld%lld",&m,&n,&k)) {        LL x=m-1,y=n+(1-k)*m-1;        if(x>y||y<=0) printf("0\n");        else {            LL time_2=0,time_5=0;            for(LL i=2;i<=y;i*=2)                time_2+=y/i-x/i-(y-x)/i;            for(LL i=5;i<=y;i*=5)                time_5+=y/i-x/i-(y-x)/i;            printf("%d\n",min(time_2,time_5));        }    }    return 0;}其实还有好多不懂得地方,比如范围的确定和组合数那个公式到底怎么推的.....等暑假的时候好好看看组合数学吧,上次去北邮校赛最后也是卡在可重复选择的组合数上

Problem D: qwb与神奇的序列

Description

qwb又遇到了一道题目:

有一个序列,初始时只有两个数x和y,之后每次操作时,在原序列的任意两个相邻数之间插入这两个数的和,得到新序列。举例说明:
初始:1 2
操作1次:1 3 2
操作2次:1 4 3 5 2
……
请问在操作n次之后,得到的序列的所有数之和是多少?
Input

多组测试数据,处理到文件结束(测试例数量<=50000)。

输入为一行三个整数x,y,n,相邻两个数之间用单个空格隔开。(0 <= x <= 1e10, 0 <= y <= 1e10, 1 < n <= 1e10)。
Output
对于每个测试例,输出一个整数,占一行,即最终序列中所有数之和。
如果和超过1e8,则输出低8位。(前导0不输出,直接理解成%1e8)
Sample Input

1 2 2

Sample Output

15
一道找规律题,多写几项就会发现规律,每操作一次,就会增加(a+b)*3^n。
上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int mod=1e8;const int INF=0x3f3f3f3f;LL q_pow(LL x,LL n) {    LL ans=1;    while(n){        if(n&1) ans=(ans*x)%(mod*2);        n>>=1;        x=(x*x)%(mod*2);    }    return ans%(mod*2);}int main(){    LL a,b,n;    while(~scanf("%lld%lld%lld",&a,&b,&n)) {        printf("%lld\n",(a+b)*((q_pow(3,n)+1)/2)%mod);    }    return 0;}

中间用了一个快速幂模板,但注意的是这里的mod=1e8,而且根据公式
(a/b)%mod=a%(b*mod)/b%mod,快速幂的时候要%2e8。

Problem F: qwb has a lot of Coins

Description
qwb has a lot of coins. One day, he decides to play a game with his friend using these coins. He first puts some of his coins into M piles, each of which is composed of Ni (1<=i<=M) coins. Then, the two players play the coin game in turns. Every step, one can remove one or more coins from only one pile. The winner is the one who removes the last coin.
Then comes the question: How many different ways the first player can do that will ensure him win the game?
Input
Input contains multiple test cases till the end of file. Each test case starts with a number M (1 <= M<= 1000) meaning the number of piles. The next line contains M integers Ni (1 <= Ni <= 1e9, 1 <= i<= M) indicating the number of coins in pile i.
Output
For each case, put the method count in one line.
If the first player can win the game, the method count is the number of different ways that he can do to ensure him win the game, otherwise zero.
Sample Input

3
1 2 3
1
1

Sample Output

0
1
题目很简单,昨天刚看了Nim博弈,一看肯定是用Nim博弈做,但就是不知道有几种胜利方法到底是指什么,如果将所有方法全部列出来,复杂度O(num1*num2*num3*…num n),后来经大佬提醒才发现既然两个人都是绝顶聪明,那么肯定都会选择对自己最有利的方向,所以唯一能区分胜利方法的只有第一步,(这里假设他们不会在一次拿多个和多次拿一个这种方法上浪费时间….我当时就是卡在这上面)
贴上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int INF=0x3f3f3f3f;int main(){    int n;    while(~scanf("%d",&n)) {    LL sum,num[1005]={0};    for(int i=0;i<n;i++) {        scanf("%lld",&num[i]);    }    LL ans=0;    for(int i=0;i<n;i++) {            LL sum=0;        for(int j=0;j<n;j++) {            if(i!=j) sum^=num[j];        }    if(sum!=0&&sum<num[i]) ans++;    else if(sum==0) ans++;    }     printf("%lld\n",ans);    }    return 0;}

这里有一点值得注意,在最后判断sum!=0&&sum < num[i]这里,不能写成sum<=num[i],当时想按位异或,只要sum=num[i],就是先手必输情况。博弈论真的很有意思啊。

Problem G: qwb去面试

Description
某一天,qwb去WCfun面试,面试官问了他一个问题:把一个正整数n拆分成若干个正整数的和,请求出这些数乘积的最大值。
qwb比较猥琐,借故上厕所偷偷上网求助,聪明的你能帮助他吗?
Input
第一行为一个正整数T.(T<=100000)
接下来T行,每行一个正整数n(n<=1e9),意义如题目所述。
Output
每一行输出一个整数,表示乘积的最大值,由于答案可能很大,请将答案对10^9+7取模后输出。
Sample Input

2
2
5

Sample Output

2
6
后来发现我要分的数越小越好

  1. 如果n%3==0,就将n全部分为3
  2. 如果n%3==1,就将n分为一个4和(n-4)/3个3
  3. 如果n%3==2,就将n分为一个2和(n-2)/3个3
    这题似乎有证明,我是试了几组数猜出啦的…..
    AC代码:
#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int INF=0x3f3f3f3f;const LL mod=1e9+7;const int maxn=1e6+5;LL ans[maxn];void init(){    memset(ans,0,sizeof(ans));    ans[0]=1;    for(int i=1;i<maxn;i++) {        ans[i]=ans[i-1]*3%mod;    }    return;}LL q_pow(LL x,LL n) {    if(n<maxn) return ans[n]%mod;    LL sum=n/(1000000);    LL sums=1;    for(int i=0;i<sum;i++) {        sums=sums%mod*ans[1000000]%mod;    }    return sums%mod*ans[n%1000000]%mod;}int main(){    int T;    init();    scanf("%d",&T);    for(int i=0;i<T;i++) {            LL a;        scanf("%lld",&a);        LL sum3=a/3;        if(a==1) printf("1");        else if(a%3==0) {            printf("%lld",q_pow(3,sum3));        }        else if(a%3==1) {            printf("%lld",q_pow(3,sum3-1)%mod*4%mod);        }        else if(a%3==2){            printf("%lld",q_pow(3,sum3)%mod*2%mod);        }        if(i!=T) printf("\n");    }    return 0;}

当时wa了好多次,以为是快速幂啊什么错了,最后发现是n==1的时候没有特判,还有,最开始初始化能节省不少时间。

Problem K: qwb与小数

Description

qwb遇到了一个问题:将分数a/b化为小数后,小数点后第n位的数字是多少?

做了那么多题,我已经不指望你能够帮上他了。。。
Input

多组测试数据,处理到文件结束。(测试数据<=100000组)

每组测试例包含三个整数a,b,n,相邻两个数之间用单个空格隔开,其中0 <= a <1e9,0 < b < 1e9,1 <= n < 1e9。
Output
对于每组数据,输出a/b的第n位数,占一行。
Sample Input

1 2 1
1 2 2

Sample Output

5
0
这题据说用long double也能过去,但之后群里有人说可以直接暴力,遂学习了
附上AC代码:

#include<bits/stdc++.h>using namespace std;using LL=int64_t;const int INF=0x3f3f3f3f;LL b;LL q_pow(LL x,LL n) {    LL ans=1;    while(n){        if(n&1) ans=ans*x%b;        n>>=1;        x=(x*x)%b;    }    return ans%b;}int main(){    LL a,n;    while(~scanf("%lld%lld%lld",&a,&b,&n)) {        printf("%lld\n",a*q_pow(10,n-1)%b*10/b);    }    return 0;}

其实就是一个算小数点的时候可以先把a扩大,然后用b取余,就是用一个a*q_pow(10,n-1)%b*10/b公式,这里注意不能讲a直接扩大10^n倍,被b取余后小于b,就算不出来a/b的小数部分了

原创粉丝点击