zoukankan      html  css  js  c++  java
  • CSP-S2考前周末刷题班Day5

    大大大

    (Illyasviel):"两个数乘起来会比一个数大吗?"

    (Star-dust):"不知道啊,来算算吧。"

    读入一个 (n),对于一个三元组 ((i,j,k)) 满足要求当且仅当(1≤i,j,k≤n)(i×j≥k)

    输入描述:

    一行一个数字 (n)

    输出描述:

    一行一个 (ans) 表示满足要求的三元组的个数。

    输入样例:

    10
    

    输出样例:

    900
    

    数据范围:

    对于 (30\%) 的数据 (n≤100)
    对于 (60\%) 的数据 (n≤5000)
    对于 (100\%) 的数据 (n≤100000)

    Solution

    30pts

    (n<=100,O(n^3)) 即可满足需求。
    分别枚举 (i,j,k) 是什么,然后判断是否合法。

    60pts

    (n<=5000)
    考虑对于一组确定的 (i,j),满足要求的 (k) 的个数为 (min(n,i*j))
    考虑在枚举 (i,j)(O(1)) 的时间内求一个可行区间。
    (O(n^2)) 的时间内能解决问题。

    100pts

    (n<=100000)
    我们来反向考虑有多少个不满足要求的三元组,即 (i*j<k)
    那么我们考虑将 ((i,j)) 插入 (i*j+1) 中,即因为 (i*j<k),所以在 ([i*j+1,n]) 这个区间的 (k) 都是满足条件的,然后求一个前缀和即可。
    复杂度分析:
    (i*j>n) 时,直接 (break)
    对于一个 (i)(⌊frac{n}{i}⌋) 个满足要求的 (i) 使得 (i*j<n) 总有效点对个数为 (sum_{i=1}^{n}{⌊frac{n}{i}⌋}) 约等于 (nsum_{i=1}^{n}frac{1}{i})
    时间复杂度为调和级数 (O(nlog{n}))
    关于调和级数:
    (sum_{i=1}^{n}frac{1}{i}) 是调和级数。
    所以总复杂的为 (O(nlog{n}))
    调和级数估计的简略证明:
    (1+frac{1}{2}+...+frac{1}{2^n-1})
    考虑分组
    ({1},{frac{1}{2},frac{1}{3}},{frac{1}{4},frac{1}{5},frac{1}{6},frac{1}{7}}...{frac{1}{2^{n-1}}...frac{1}{2^n-1}})
    每一组的和小于 (1)
    当项数为 (2^n-1) 时和小于 (n),当项数为 (n) 时和的级别小于 (log{n})

    关于 (O(n)) 的算法是 (60pts) 算法的优化:
    (60pts)(n^2) 枚举写出来:(sum_{i=1}^{n}sum_{j=1}^{n}min(n,i*j))
    发现 (j) 的上界是 (frac{n}{i}),且 (i*j) 的变化由 (1*i->2*i->...->frac{n}{i}*i),那么 (i) 所能带来的贡献为:((n-1*i)+(n-2*i)+(n-frac{n}{i}*i)),发现这个是一个等差数列求和,可以 (O(1)) 算出。

    关于 (O(sqrt{n})) 的算法是用整数分块做的,不过由于这只是 (T1),就不多做介绍了。

    #include<bits/stdc++.h>
    #define N 120000
    using namespace std;
    long long a[N];
    long long b[N];
    long long ans=0;
    long long n;
    int main()
    {
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=n/i;j++)
    		{
    			a[j*i]++;
    		}
    	}
    	for(int i=1;i<=n;i++) b[i]=b[i-1]+a[i-1];
    	for(int i=1;i<=n;i++) ans+=b[i];
    	printf("%lld
    ",n*n*n-ans);
    }
    

    kkk

    (Star-dust):"你会最短路吗?"

    (Illyasviel):"当然!"

    (Star-dust):"那你来求一求这 (k) 个关键点中是否存在长度 (\%P)(L) 的路径吧。"

    (Illyasviel):"这和最短路有什么关系吗?"

    (Star-dust):"不知道啊~"

    输入描述:

    ​ 第一行一个数字 (T) 代表数据组数。

    ​ 对于每个数据,第一行五个数 (n,m,k,P,L​) ​表明有 (n) 个点,(m) ​条边,(k) ​个在图中的点。

    ​ 接下来一行 (k) 个数代表关键点。

    ​ 接下来 (m) 行每行三个数 (x,y,z) 表示 (x)(y) 之间有一条长度为 (z) 的路径。(图为无向联通图)

    输出描述:

    ​ 输出 (T) 行,当存在一条路径从起点和终点都在 (k) 个点中输出 "(YES)",否则输出 "(NO)"(不包含引号)。

    输入样例:

    1
    2 2 2 5 3
    1 2
    1 2 1
    2 1 1
    

    输出样例:

    YES
    

    样例解释:

    (1->2->1->2)

    数据范围:

    对于 (40\%) 的范围 (T≤500,0≤L,z≤P≤20,k≤n≤m≤500,k≤10)
    对于 (80\%) 的范围 (T≤500,0≤L,z≤P≤20,k≤n≤m≤500)。​​
    对于 (100\%) 的范围 (T≤500,0≤L,z≤P≤10^9,k≤n≤m≤500,P) ​是奇数。

    Solution

    40pts

    设置 (f[i][j][o]) 表示从第 (i) 个点出发,目前在第 (j) 个点,路径长度 (\%P=o),是否能达到。
    跑一遍 (bfs) 或者记忆化 (dfs) 来枚举整张图能否达到。
    如果存在了已经满足的话就退出。
    复杂度为 (O(T*n*m*k*L))。 加一点 (break) 剪枝能过。

    80pts

    考虑从任意一个点 (o) 出发,如果有两条路径能分别达到能到达两个关键点 (x)(y),他们的长度分别为 (A)(B),那么就会存在一条长度为 (A+B) 的路径。
    如果存在一条路径 (x->z->y) 使得条件满足,可以通过反复走 (2P)(z->o)(o->z) 的路径长度和为 (0)
    就是通过 (o) 的路径能满足所有长度的可行路径。
    只搜一个点即可。
    (f[i][k]) 表示从第 (o) 个点到达 (i) 点路径长度 (\%P=k) 是否存在。

    100pts

    我们考虑从若存在一条长度为 (l) 的边,走过去走回来的长度为 (2*l),我们可以反复走这条边,可以走 (k*2*l)
    那么它在 (\%P) 意义下等价于 (gcd(l,P)),如果我们有一条长度为 (x) 的路径经过这路径两端中的一个,那么就可以通过反复走这条边达到(k*gcd(x,l,P)) 的全部长度。

    证明:
    设通过走若干次这条边所能到达的长度为:(a)
    则有:
    (k*l≡a(mod P))
    (∴k*frac{l}{gcd(l,P)}≡frac{a}{gcd(l,P)}(mod frac{P}{gcd(l,P)}))
    (∵frac{l}{gcd(l,P)})(frac{P}{gcd(l,P)}) 互质
    (∴frac{a}{gcd(l,P)}) 能表示 (0) ~ (frac{P}{gcd(l,P)}-1) 内所有的数
    (∴a) 能表示的数为:(0,1*gcd(l,P),2*gcd(l,P),...,P-gcd(l,P))
    (∴a) 能表示 (k*gcd(l,P)) 的任何长度
    证毕

    现在我们考虑经过多条边 (w_1,w_2,...,w_m),已经每条边各自经过的次数 (k_1,k_2,...,k_m),那么路径会加上 (2*(k_1w_1+k_2w_2+...+k_mw_m)) 的长度。
    现在就是判断 (2*(k_1w_1+k_2w_2+...+k_mw_m)) 能否把路径修正为 (L(mod P))
    这个所能到达的值域跟原先的路径所能到达的值域是一样的,那么就看它们全部的 (gcd) 是否是 (L) 的因数。
    对于怎么保证起点和终点都是关键点呢?假设有个关键点为 (o),我们可以先走这样一条路径:(o->x_1->o->x_2->...->o->x_n->o),就是从 (o) 点开始把每个点都逛一遍再回来,发现这条原始路径也是由 (2*(k_1w_1+k_2w_2+...+k_mw_m)) 所构成的,发现并不影响我们修正。
    复杂度 (O(T*m*log{P}))

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int T,n,m,K,P,L,u,v,w;
    int GCD(int a,int b)
    {
    	if(b==0) return a;
    	return GCD(b,a%b);
    }
    int main()
    {
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d%d%d%d%d",&n,&m,&K,&P,&L);
    		int gcd=P;
    		for(int i=1;i<=K;i++) scanf("%d",&u);  //这K个关键点根本没用 
    		for(int i=1;i<=m;i++)
    		{
    			scanf("%d%d%d",&u,&v,&w);
    			if(w==0) w=P;                      //由于是在膜P意义下,我们把长度为0的边修正为P 
    			gcd=GCD(gcd,w);                    //对每条边进行gcd 
    		}
    		if(L%gcd==0) printf("YES
    ");          //判断能否将路径修正为L,即判断gcd是否为L的因数 
    		else printf("NO
    ");
    	}
    	return 0;
    }
    

    A的B次方

    题目描述:

    (Illyasviel):"今天我们学了 (A^B)?"

    (Star-dust):"我们也学了诶!"

    (Star-dust):"那我来考考你吧!"

    已知 (A)(P),求任意一个不等于 (A) 且小于 (2×10^{18})(B) 使得 (A^B≡B^A (mod) (P))

    输入描述

    一行输入两个整数 (A)(P)

    输出描述

    输出任意一个满足要求的数字 (B)

    (B) 要为一个不大于 (2×10^{18}) 的正整数。

    样例输入

    78 100
    

    样例输出

    16
    

    数据范围

    对于 (30\%) 的数据:(1≤A,P≤1000)
    对于 (30\%) 的数据:(P) 为质数。
    对于 (100\%) 的数据:(64≤A≤10^9,P≤10^9,1≤B≤10^{18})

    Solution

    30pts

    (for) 循环暴力枚举,然后快速幂判断。
    但实际上这个做法能拿 (50pts)

    100pts

    我们可以构造一个 (B),使得 (B=A(mod P)),即 (B=k*P+A①)
    这样使得 (B^A=A^A(mod P))
    我们想办法再构造 (A^B=A^A(mod P))
    扩展欧拉定理可得:(A^{kphi(P)+A}=A^A(mod P))
    所以可以让 (B=k*phi(P)+A②)
    由于我们构造的 (B) 要满足 (①②) 两式,所以我们让 (B=P*phi(P)+A) 即可。
    由于 (P) 的范围是 (1e9),所以我们要在 (sqrt{P}) 的时间内求出 (phi(P))
    我们把 (P) 写成唯一分解定理的形式:(P=p_1^{k_1}p_2^{k_2}p_3^{k_3}*...*p_n^{k_n})
    (phi(P)=P*(1-frac{1}{p_1})*(1-frac{1}{p_2})*(1-frac{1}{p_3})*...*(1-frac{1}{p_n}))
    所以我们可以去枚举 (P) 的每个质因子,枚举到一个就乘一下这个质因子的贡献 ((1-frac{1}{p_i})) 即可。
    时间复杂度 (O(sqrt{P}))

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #define ll long long
    using namespace std;
    inline int read()
    {
    	char ch=getchar();
    	int a=0;
    	while(ch<'0'||ch>'9') ch=getchar();
    	while(ch>='0'&&ch<='9') a=(a<<1)+(a<<3)+(ch^48),ch=getchar();
    	return a;
    }
    ll A,P;
    int main()
    {
    	A=read();P=read();
    	ll phi=P,p=P;              //phi就是我们要求的 φ(P) 
    	for(ll i=2;i*i<=P;i++)     //枚举P所有的质因子 
    	{
    		if(P%i==0)             //由于唯一分解定理可知,我们所能找到的因数i一定是P的质因子 
    		{
    			phi=phi/i*(i-1);   //找到一个质因子,就根据公式计算一下phi 
    			while(P%i==0) P/=i;//注意这里我们要把P中所有的质因子i全部除去,以保障答案不会重复计算和i为质因子 
    		}
    	}
    	if(P>1) phi=phi/P*(P-1);   //如果还剩了最后一个质因子,别忘了算上 
    	printf("%lld
    ",A+p*phi);  //输出A+P*φ(P) 
    	return 0;
    }
    

    灯塔

    (Star-dust):"每个人都是灯塔,灯塔之间相隔万里,无法触碰无法沟通,唯一能做的就是用自己的光去照耀别人。"

    (Illyasviel):"如果能被某个灯塔一直照耀,那一定很幸福吧。"

    (Star-dust):"我能成为你的灯塔吗?"

    (Illyasviel):"好啊~"

    海上有着 (n) 个灯塔,第 (i) 个灯塔在位置 (i) 闪耀着,灯塔的光覆盖着 ([i−d_i,i+d_i]) 的所有灯塔,对于第 (k) 个灯塔,他想知道有多少个 (i) 满足 (i<k) 且至少存在一个在 (i)(k) 中间的灯塔 (j) 满足灯塔 (j) 同时被灯塔 (i) 和灯塔 (k) 照耀,并且 (j)(k) 的距离小于等于 (j)(i) 之间的距离。

    输入描述:

    第一行一个整数 (n)

    接下来一行 (n) 个数字,第 (i) 个代表 (d_i)

    输出描述:

    一行一个答案 (ans)

    (f_k) 表示对于第 (k) 个灯塔有多少个灯塔满足条件。

    (ans)(n)(f_k) 的异或和。

    样例输入:

    10
    2 2 3 2 3 2 3 3 3 1
    

    样例输出:

    2
    

    样例解释:

    对应位置答案分别为 (0 ,0 ,1 ,2, 3, 3, 3, 4, 4, 2)

    数据范围:

    对于 (20\%) 的数据:(n≤100)
    对于 (20\%) 的数据:(n≤5000)
    对于 (20\%) 的数据: (d_i) 完全相同。

    对于 (20\%) 的数据:(n≤100000)
    对于 (100\%) 的数据:(n≤3000000,1≤d_i≤n)

    Solution

    20pts

    (O(n^3)) 暴力枚举三元组判断是否可行。

    40pts

    考虑一对 (i)(k),判断是否存在满足条件的 (j),使得:
    (①:i+d_i<=j)
    (②2*j>=i+k)
    (③j>=k-d_k)
    整理一下:(i+d_i<=j<=k-d_k,2*j<=i+k)
    对于一对 (i,k) 有一个 (j) 的可行区间,第二条限制贪心取最右的判断是否满足要求。
    时间复杂度 (O(n^2))

    60pts

    基于 (40pts) 的做法再对 (20\%) 的数据做特判。
    (20\%) 的数据的答案属于一个阶梯上升一个平台然后再下降。
    能人眼观察的到。

    100pts

    我们考虑 (j<=i+d_i) 这个限制条件,对于 (i) 来说,(j) 肯定是越大越好,所以让 (j=i+d_i)这样我们对于每一个 (i) 已经确定了一个最优的 (j)
    再考虑 (2*j>=i+k) 这个限制条件,我们移一下项:(k<=2*j-i),这样我们就知道了这个 ((i,j)) 最大所能满足的 (k)
    再考虑 (k-d_k<=j) 这个限制条件,这一条就是限制了对于 (k) 来说可行 (j) 的范围。
    对于一个 (i) 来说,它的最优的 (j) 就是 (i+d_i),它的可行 (k) 的区间范围在 ([i+2,min(2*j-i,n)])
    所以说,当 (k) 的值超过了 (2*j-i)((i,j)) 这一对灯塔就不会对 (k) 产生贡献了,且对于更往后的 (k) 也不会产生贡献。
    所以我们开一个 (vector) 记录此刻所需要删除的灯塔对 ((i,j))
    然后我们以 (k) 为时间线,开一个树状数组记录满足条件的 (i) 的贡献,我们把贡献记录到对应的 (j(j=i+d_i)) 上去。这样做的目的是为了更好的判断符合条件的 (j) 能否被 (k) 照到。
    由于树状数组维护的是 (j),所以 (vector) 里光存 (j) 即可。
    算法流程:
    (k) 不断向右枚举时,增加的可行的 (i=k-2,j=i+d_i),那么将这对 ((i,j)) 加入树状数组中(具体操作为将树状数组中下标为 (j) 的位置数值 (+1))。
    同时也要考虑从 (k-1) 转移到 (k) 的过程中,哪些点对 ((i',j')) 由原来的合法变成不合法了,我们已经将此刻新产生的不合法的点对都记录到了 (vector) 里了,我们需要遍历一遍然后将它们都从树状数组中删掉。
    然后我们要考虑当前 (k) 的答案是多少,也就是 (k) 所能照到的范围中有几个 (j)。由于前面我们是令 (j=i+d_i),没有规定它的上界,所以这个 (j) 可能会在 (k) 的后面(由于 (k) 随时在变,所以我们并没有让 (j=min(i+d_i,k)),因为这可能会对下一个 (k) 产生影响),但这并不影响我们统计答案(因为在 (k) 后面的 (j) 你都可以把它搞到 (k) 前面,起码能搞到 (k-1) 的位置),我们就做一次后缀和就好了,范围是 ([k-d_k,n])
    时间复杂度 (O(nlog{n}))

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #define ll long long
    using namespace std;
    inline int read()
    {
    	char ch=getchar();
    	int a=0,x=1;
    	while(ch<'0'||ch>'9') ch=getchar();
    	while(ch>='0'&&ch<='9') a=(a<<1)+(a<<3)+(ch^48),ch=getchar();
    	return a;
    }
    const int N=3e6+5;
    int n,c[N],d[N];
    vector<int> a[N];
    ll f[N],ans;
    int lowbit(int x)
    {
    	return x&(-x);
    }
    void insert(int x,int y)
    {
    	x=n-x+1;                        //这是树状数组求后缀和的方法,反着记录,这样求得的前缀和就是你要的后缀和  
    	for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
    }
    int query(int x)
    {
    	x=n-x+1;
    	int cnt=0;
    	for(int i=x;i;i-=lowbit(i)) cnt+=c[i];
    	return cnt;
    }
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;i++) d[i]=read();
    	for(int k=3;k<=n;k++)           //第1,2个灯塔不存在合法的j,所以不会产生贡献 
    	{
    		int i=k-2;                  //对于当前的k,新增的i就是k-2 
    		int j=min(i+d[i],n);        //j是对于当前i的最优的情况 
    		int m=min(2*j-i+1,n+1);     //对于(i,j),当k到了2j-i+1的时候就不合法了,注意这里上界是n+1,表示(i,j)存在永远合法的情况(始终不会被删去) 
    		a[m].push_back(j);          //记录当k到了m的时候需要删掉哪些j 
    		insert(j,1);                //把新增的合法的(i,j)记录上 
    		for(int l=0;l<a[k].size();l++)  //删掉那些不合法的 
    	    {
    	    	insert(a[k][l],-1);
    		}
    		f[k]=query(max(k-d[k],1));  //后缀和统计答案 
    		ans^=f[k];
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Git ---游离状态下的commit 分支切换与找回,commit之后无法找到历史记录
    mybatis异常invalid comparison: java.util.Date and java.lang.String
    Spring的注解@Qualifier
    Spring @Bean注解的使用
    Java的IO系统
    linkin大话面向对象--GC和jar包
    linkin大话面向对象--闭包和回调
    linkin大话面向对象--内部类
    linkin大话面向对象--枚举
    linkin大话面向对象--接口
  • 原文地址:https://www.cnblogs.com/xcg123/p/13909452.html
Copyright © 2011-2022 走看看