xju第一场训练赛
A.倍数
考点:容斥原理
给出一个数N,求1至N中,有多少个数不是2 3 5 7的倍数。 例如N = 10,只有1不是2 3 5 7的倍数。
Input
输入1个数N(1 <= N <= 10^18)。
Output
输出不是2 3 5 7的倍数的数共有多少。
Sample Input
10
Sample Output
1
我的思路:
if(n%2!=0||n%3!=0||n%5!=0||n%7!=0)
ans++;
正确思路:
分析: 之前做过几道容斥,没仔细总结过,顺便总结下趁着这个裸题 -
当两个数或三个的时候都好分析,这次是四个,也不难分析,如果多的话就不好分析了,先看四个的,我们形象的用集合的形式来表示2,3,5,7倍数
当一个数为n时,显然,所有2,3,5,7倍数的个数就是sum(甲,乙,丙,丁,Ⅰ,ⅡⅢ,Ⅳ,A,B,C,D,E),我们可以用n/2表示所有是2的倍数的个数也就相当于图中的sum(甲,Ⅰ,Ⅱ,A,B,C,E),当然n/3,n/5,n/7同理,我们发现如果把n/2,n/3,n/5,n/7加起来的话,答案是超的,其中红色为多算了一次,蓝色为多算了两次,紫色为多算了三次,我们现在想办法减掉。
我们可以这样减去一个n/(2*3),相当于sum(I,A,B,D,E),同理再减去n/(3*7)n/(7*5)n/(5*2)
我们如果都按照集合的方法结果正好缺一个E,如果我们再加上个E的话,算出来其实结果是不对的,算多了,为什么呢,细心地话就可以发现,其实不止这一种组合方式,也可以如下
所以我们会多算,那应该怎么算呢,才能保证正确的容斥呢,我们可以把每个交集都减去,这样就可避免了,也就是说在原来的基础上再减去 n/(2*7),n/(3*5),这两项,我们发现加了之后就会产生新的问题,答案缺少了A,B,C,D,我们发现n/(2*3*5) 代表sum(A,E),同理n/(2*3*7),n/(7*3*5),n/(2*7*5),我们可以发现如果加上之后正好多了个E,减去即可,而E = n/(2*3*5*7),从而答案得到了解决,代码很简单,主要是思想。
写出来后可以发现容斥的通式为
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ll n;cin>>n;
ll sum = n/2 + n/3 + n/5 + n/7 - n/6 - n/10 - n/35 - n/21 - n/14 - n/15 + n/30 + n/42 + n/70 + n/105 - n/210;
cout<<n-sum<<endl;
}
转自:
同类型的题目:
P3197 [HNOI2008]越狱
题目描述
监狱有 nn 个房间,每个房间关押一个犯人,有 mm 种宗教,每个犯人会信仰其中一种。如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱。
答案对 100,003100,003 取模。
输入格式
输入只有一行两个整数,分别代表宗教数 mm 和房间数 nn。
输出格式
输出一行一个整数代表答案。
输入输出样例
输入 #1复制
2 3
输出 #1复制
6
说明/提示
样例输入输出 1 解释
状态编号 | 1 号房间 | 2 号房间 | 3 号房间 |
---|---|---|---|
1 | 信仰 1 | 信仰 1 | 信仰 1 |
2 | 信仰 1 | 信仰 1 | 信仰 2 |
3 | 信仰 1 | 信仰 2 | 信仰 2 |
4 | 信仰 2 | 信仰 1 | 信仰 1 |
5 | 信仰 2 | 信仰 2 | 信仰 2 |
6 | 信仰 2 | 信仰 2 | 信仰 1 |
数据规模与约定
对于 100\%100% 的数据,保证 1 \le m \le 10^81≤m≤108,1 \le n \le 10^{12}1≤n≤1012。
正确解
【题解】
100100分满分思路:
由于直接计算越狱的方案数不是很好做,所以我们选择用所有的方案数减去不越狱的方案数。由于每一个房间的人的宗教有mm种可能,且有nn个房间,所以所有的方案数等于m^nmn。由于当相邻的两个房间所关押的罪犯当宗教相同时就有可能会越狱,所以不可能会越狱的方案数为m \times (m-1)^{n-1}m×(m−1)n−1,这是因为第一个房间的最烦的宗教有mm种选择,而第二个房间的罪犯的宗教有m-1m−1种选择,第三个房间的罪犯的宗教有m-2m−2种选择,以此类推。因此越狱的方案数就有m^n-m \times (m-1)^{n-1}mn−m×(m−1)n−1种。由于nn很大,所以我们用快速幂来解这道题,注意,本题的模数是100003
,不要输错了。
什么是快速幂?
快速幂可以快速地求出一个数的多少次方是多少(可能会对某个数取模)。
如果我们求的是a^nan的值的话,那么有性质:
a^b\;mod\;c=((a^2)^{\lfloor b/2 \rfloor} \times a)\;mod\;cabmodc=((a2)⌊b/2⌋×a)modc,bb是奇数。
a^b\;mod\;c=((a^2)^{\lfloor b/2 \rfloor})\;mod\;cabmodc=((a2)⌊b/2⌋)modc,bb是偶数。
由于在加法和乘法运算中对于取模运算时自由的(即中途取模和结束时再取模的答案是一样的),所以我们可以在中途取模,下面是一些取模运算的规则,如下:
模运算与基本四则运算有些相似,但是除法例外。其规则如下:
(a + b) % p = (a % p + b % p) % p
(a – b) % p = (a % p – b % p) % p
(a * b) % p = (a % p * b % p) % p
ab % p = ((a % p)b) % p
结合率:
((a+b) % p + c) % p = (a + (b+c) % p) % p
((ab) % p * c)% p = (a * (bc) % p) % p
#include <cstdio>
long long p=0;
long long qu(long long x,long long y)
{
if(y==0)
{
return 1;
}
else
{
long long dq=qu(x,y/2);
if(y%2==0)
{
return ((dq%p)*(dq%p))%p;
}
else if(y%2==1)
{
return ((dq%p)*(dq%p)*(x%p))%p;
}
}
}
int main()
{
long long m=0,n=0;
scanf("%lld %lld",&m,&n);
p=100003;
printf("%lld",(qu(m,n)-(m*qu(m-1,n-1))%p+p)%p);
return 0;
}
转自 https://www.luogu.com.cn/problem/solution/P3197侵删
B - 质中质
考点:质数筛
如果一个质数,在质数列表中的编号也是质数,那么就称之为质数中的质数。例如:3 5分别是排第2和第3的质数,所以他们是质数中的质数。现在给出一个数N,求>=N的最小的质数中的质数是多少(可以考虑用质数筛法来做)。
Input
输入一个数N(N <= 10^6)
Output
输出>=N的最小的质数中的质数。
Sample Input
20
Sample Output
31
我的思路:
思路来自桶排法,先把所有质数标记,然后再选取第一个比大的数
数据太大,时间太长
正确解法:
#include<bits/stdc++.h>
using namespace std;
const int N=10000010,NN=10000000;
int n,p[N],num=0;
bool np[N];
int main()
{
int i,j;
scanf("%d",&n);
np[0]=np[1]=1;//0和1都不为素数
for(i=2;i<=NN;i++)
{
if(!np[i]) p[++num]=i;//if(np[i]==0)//若当前数i没有被之前的所有数筛掉,表明i是素数,将i添加进素数表prime
for(j=1;j<=num&&p[j]*i<=NN;j++)//界在10e6
{
np[p[j]*i]=1;//进行筛选,所有之前出现的素数的倍数都会被标记
if(i%p[j]==0) break;/*如果i%prime[j]==0,那么i就可以看成prime[i]乘以一个数(记为m);
因为我们存的prime是从小到大存的,所以prime[j+1]>prime[j];那么i * prime[j+1]就可以看
成prime[j] * m * prime[j+1];那么i * prime[j+1]的最小质因子是prime[j]。加上
if(i%prime[j]==0) break后我们就可以保证每一个数只被它的最小质因子给筛去,即每一个数
只会被筛一次。
举个例子,6=2*3;当i=2时i%2=0,跳出循环,那它就不会被质因子3给筛去;当i=3时,6就被质因子2给筛去。*/
}
}
for(i=1;i<=num;i++)
if(p[i]>=n&&(!np[i]))
{
printf("%d",p[i]);
break;
}
return 0;
}
这种方法叫做欧拉筛(也叫质数线性筛)
比朴素法以及埃氏法好在不重复,取得是最小质因子
同类型的题目:
P3383 【模板】线性筛素数
题目背景
本题已更新,从判断素数改为了查询第 kk 小的素数
提示:如果你使用 cin
来读入,建议使用 std::ios::sync_with_stdio(0)
来加速。
题目描述
如题,给定一个范围 nn,有 qq 个询问,每次输出第 kk 小的素数。
输入格式
第一行包含两个正整数 n,qn,q,分别表示查询的范围和查询的个数。
接下来 qq 行每行一个正整数 kk,表示查询第 kk 小的素数。
输出格式
输出 qq 行,每行一个正整数表示答案。
输入输出样例
输入 #1复制
100 5
1
2
3
4
5
输出 #1复制
2
3
5
7
11
说明/提示
【数据范围】
对于 100\%100% 的数据,n = 10^8n=108,1 \le q \le 10^61≤q≤106,保证查询的素数不大于 nn。
正确答案:
#include<bits/stdc++.h>
bool isPrime[100000010];
int Prime[6000010], cnt = 0;
void GetPrime(int n)
{
memset(isPrime, 1, sizeof(isPrime));
isPrime[1] = 0;
for(int i = 2; i <= n; i++)
{
if(isPrime[i])
Prime[++cnt] = i;
for(int j = 1; j <= cnt && i*Prime[j] <= n; j++)
{
isPrime[i*Prime[j]] = 0;
if(i % Prime[j] == 0)
break;
}
}
}
int main()
{
int n, q;
scanf("%d %d", &n, &q);
GetPrime(n);
while (q--)
{
int k;
scanf("%d", &k);
printf("%d\n", Prime[k]);
}
return 0;
}
(xixi顺便扒了个板子,爆零的蒟蒻重头学起)
C - 游戏1
考点:博弈论-巴什博弈
有一堆石子共有N个。A B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。
例如N = 3,K = 2。无论A如何拿,B都可以拿到最后1颗石子。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000) 第2 - T + 1行:每行2个数N,K。中间用空格分隔。(1 <= N,K <= 10^9)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Sample Input
4
3 2
4 2
7 3
8 3
Sample Output
B
A
A
B
我的理解:
根据样例先减去最大值,前面一人一个拿,如果为奇数则后手以后赢
正确解法
#include<bits/stdc++.h>
using namespace std;
int n,k,t;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
if (n<=k) printf("A\n");//如果可以一次拿完A必胜
else
{
n%=(k+1);
if (n==0)
printf("B\n");
else
printf("A\n");
}
}
return 0;
}
你以为我会一道道来? 我突然发现四种常见博弈
我们先来看这道题的兄弟
G - 游戏2
有2堆石子。A B两个人轮流拿,A先拿。每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取。拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出2堆石子的数量,问最后谁能赢得比赛。
例如:2堆石子分别为3颗和5颗。那么不论A怎样拿,B都有对应的方法拿到最后1颗。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000) 第2 - T + 1行:每行2个数分别是2堆石子的数量,中间用空格分隔。(1 <= N <= 2000000)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Sample Input
3
3 5
3 4
1 9
Sample Output
B
A
A
然后看我找到了什么
D - 环
考点:约瑟夫环
N个人坐成一个圆环(编号为1 - N),从第1个人开始报数,数到K的人出列,后面的人重新从1开始报数。问最后剩下的人的编号。
例如:N = 3,K = 2。2号先出列,然后是1号,最后剩下的是3号。
Input
2个数N和K,表示N个人,数到K出列。(2 <= N, K <= 10^6)
Output
最后剩下的人的编号
Sample Input
3 2
Sample Output
3
正确解法:
#include<bits/stdc++.h>
using namespace std;
int n,k,ans,i;
int main()
{
scanf("%d%d",&n,&k);
for(i=2;i<=n;i++) ans=(ans+k)%i;
printf("%d",ans+1);
return 0;
}
wtf???
这是啥
经国我询问以及查询
发现约瑟夫环还可以用模拟做
事实证明大佬刚开始也是十分无解的
同类型(温柔版(难版)):
P2696 慈善的约瑟夫
题目描述
你一定听说过约瑟夫问题吧?即从N个人中找出唯一的幸存者。现在老约瑟夫将组织一个皆大欢喜的新游戏,假设N个人站成一圈,从第1人开始交替的去掉游戏者,但只是暂时去掉,直到最后剩下唯一的幸存者为止。幸存者选出后,所有比幸存者号码高的人每人得到1个金币,永久性离开。其余剩下的将重复以上的游戏过程,比幸存者号码主的人每人得到1个金币后离开。经过这们的过程后,一旦人数不再减少,则最后剩下的那些人将得到2个金币。请你计算一下老约瑟夫一共要付出多少钱?
输入格式
一行一个正整数N表示人数。
输出格式
一行一个正整数表示共需支付的钱数。
输入输出样例
输入 #1复制
10
输出 #1复制
13
说明/提示
1<=N<=100000
题解:
不会!会再回来更新
P1424 小鱼的航程(改进版)
题目描述
有一只小鱼,它平日每天游泳 250 公里,周末休息(实行双休日),假设从周 x(1\le x \le 7)x(1≤x≤7) 开始算起,过了 n(n\le 10^6)n(n≤106) 天以后,小鱼一共累计游泳了多少公里呢?
输入格式
输入两个整数x,n(表示从周x算起,经过n天)。
输出格式
输出一个整数,表示小鱼累计游泳了多少公里。
输入输出样例
输入 #1复制
3 10
输出 #1复制
2000
这道题虽然水,但是也涉及到了循环
可以为后来不需要删去数据的做个例子
AC代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
unsigned long long n,ans=0;
int x;
cin >> x >> n;
for(int i=0;i<n;i++)
{
if((x!=6)&&(x!=7))
ans+=250;
if(x==7)
x=1;
else
x++;
}
cout<<ans;
return 0;
}
E - 覆盖
考点:经典dp
在2*N的一个长方形方格中,用一个1*2的骨牌排满方格。
问有多少种不同的排列方法。
例如:2 * 3的方格,共有3种不同的排法。(由于方案的数量巨大,只输出 Mod 10^9 + 7 的结果)
Input
输入N(N <= 1000)
Output
输出数量 Mod 10^9 + 7
Sample Input
3
Sample Output
3
正确的解法:
#include<bits/stdc++.h>
using namespace std;
const int mo=1000000007;
int n,i;
long long f[1010];
int main()
{
scanf("%d",&n);
f[0]=f[1]=1;
f[2]=2;
for(i=3;i<=n;i++) f[i]=(f[i-1]+f[i-2])%mo;
printf("%lld",f[n]);
return 0;
}
F - 3的幂的和
求:3^0 + 3^1 +...+ 3^(N) mod 1000000007
Input
输入一个数N(0 <= N <= 10^9)
Output
输出:计算结果
Sample Input
3
Sample Output
40
正确的答案:
#include<bits/stdc++.h>
using namespace std;
const long long mo=1000000007;
long long n;
long long ksm(long long x,long long y)
{
long long ret=1;
while(y)
{
if(y&1) ret=(ret*x)%mo;
x=(x*x)%mo;
y>>=1;
}
return ret;
}
int main()
{
scanf("%lld",&n);
printf("%lld",(ksm(3,n+1)-1)*ksm(2,mo-2)%mo);
return 0;
}
H - 线段的重叠
X轴上有N条线段,每条线段包括1个起点和终点。线段的重叠是这样来算的, 1020和 1225的重叠部分为 1220。
给出N条线段的起点和终点,从中选出2条线段,这两条线段的重叠部分是最长的。输出这个最长的距离。如果没有重叠,输出0。
Input
第1行:线段的数量N(2 <= N <= 50000)。 第2 - N + 1行:每行2个数,线段的起点和终点。(0 <= s , e <= 10^9)
Output
输出最长重复区间的长度。
Sample Input
5
1 5
2 4
2 8
3 7
7 9
Sample Output
4
正确的答案:
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,ans,i;
struct node
{
int l,r;
}a[N];
bool cmp(node x,node y)
{
if(x.l==y.l) return x.r>y.r;
return x.l<y.l;
}
int main()
{
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%d%d",&a[i].l,&a[i].r);
sort(a+1,a+n+1,cmp);
for(i=2;i<=n;i++)
{
if(a[i].r>=a[i-1].r) ans=max(ans,a[i-1].r-a[i].l);
else
{
ans=max(ans,a[i].r-a[i].l);
a[i]=a[i-1];
}
}
printf("%d",ans);
return 0;
}
I - 蚂蚁
n只蚂蚁以每秒1cm的速度在长为Lcm的竿子上爬行。当蚂蚁爬到竿子的端点时就会掉落。由于竿子太细,两只蚂蚁相遇时,它们不能交错通过,只能各自反向爬回去。对于每只蚂蚁,我们知道它距离竿子左端的距离xi,但不知道它当前的朝向。请计算各种情况当中,所有蚂蚁落下竿子所需的最短时间和最长时间。
例如:竿子长10cm,3只蚂蚁位置为2 6 7,最短需要4秒(左、右、右),最长需要8秒(右、右、右)。
Input
第1行:2个整数N和L,N为蚂蚁的数量,L为杆子的长度(1 <= L <= 10^9, 1 <= N <= 50000) 第2 - N + 1行:每行一个整数Aii,表示蚂蚁的位置(0 < Aii < L)
Output
输出2个数,中间用空格分隔,分别表示最短时间和最长时间。
Sample Input
3 10
2
6
7
Sample Output
4 8
我的困惑:
无法确定最短时间的中间那一只的位置
正确的答案:
#include<bits/stdc++.h>
using namespace std;
int n,l,mxans,mians,i,a;
int main()
{
scanf("%d%d",&n,&l);
for(i=1;i<=n;i++)
{
scanf("%d",&a);
mians=max(mians,min(a,l-a));
mxans=max(mxans,max(a,l-a));
}
printf("%d %d",mians,mxans);
return 0;
}