大大大
(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;
}