清北学堂周末刷题班第五场
A. 大大大
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\)
大致有这样:
我们可以先枚举\(k\),然后对于每一个\(i\),我们进行计算,\(O(nlogn)\)或\(O(n\sqrt n)\),我实现的是\(n\sqrt n\)的。(有巨佬会数论分块\(O(n)\)和\(O(\sqrt n))\)的教一下我)。
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
int n, sqr[1000000];
long long cnt = 0;
void read(int &x)
{
char ch = getchar();
bool mark = false;
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
if(mark) x = -x;
return;
}
int main()
{
read(n);
for(int i = 1; i <= n; ++ i) sqr[i] = ceil(sqrt(i));
for(int k = 1; k <= n; ++ k)
{
cnt += (long long)(n - sqr[k] + 1) * (n - sqr[k] + 1);
for(int j = 1; j < sqr[k]; ++ j) cnt += (long long)(n - ceil((double)k / j) + 1) * 2ll;
}
printf("%lld\n", cnt);
return 0;
}
B. 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≤109,k≤n≤m≤500\),\(P\)是奇数。
这道题思维好题。
这道题看起来是一道图论题,实际上跟图论没什么关联。
由于是张无向图,所以我们可以通过反复走一条边来修正路径\(\%P\)的取值。
假设存在一条长度为\(l\)的路径,我们可以来回的长度一定为\(2*k*l\)(当然也可以不往返走),在\(\%P\)意义下相当于走了\(k*gcd(P, l)\)。
进一步,我们可以通过反复走其他路径来修正路径\(\%P\)取值。
综上,我们能够走的取值一定是\(k*gcd(P,w_1,w_2,..,w_n)\),判断\(L\)是都为\(gcd(P,w_1,w_2,..,w_n)\)倍数即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int n, m, k, p, l;
int gcd(int x, int y)
{
if(!y) return x;
return gcd(y, x % y);
}
int main()
{
int T;
scanf("%d", &T);
while(T --)
{
scanf("%d %d %d %d %d", &n, &m, &k, &p, &l);
int u, v, tmp;
for(int i = 1; i <= k; ++ i)
{
scanf("%d", &tmp);
}
for(int i = 1; i <= m; ++ i)
{
scanf("%d %d %d", &u, &v, &tmp);
p = gcd(p, tmp);
}
if(l % p == 0) puts("YES");
else puts("NO");
}
return 0;
}
C. 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}\)
首先,我们令\(B≡A(mod\ P),B=k*P+A\)。
这样:\(A^A≡A^B(\mod P)\)。
再由扩展欧拉定理解:
\(B≡A(\mod \phi(P))\)
等价于:
\(B≡k*\phi(P)+A\)
令\(k\)等于\(P\),问题得解。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define ull unsigned long long
using namespace std;
int A, P;
int euler(int x)
{
int res = x;
for(int i = 2; i <= sqrt(x); ++ i)
{
if(x % i == 0)
{
res = res / i * (i - 1);
while(x % i == 0) x /= i;
}
if(x == 1) return res;
}
if(x > 1) res = res / x * (x - 1);
return res;
}
int main()
{
scanf("%d %d", &A, &P);
cout << (ull)((ull)P * euler(P) + A) << endl;
return 0;
}
D. 灯塔
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\)
仔细思考不难发现:对于\(k\),对于满足条件\(i\)需要满足:
-
\(i+d_{i}≥k-d_{k}\)
-
\(j≤i+d_{i}\)
-
\(i+k≤2*j\)
由于\(j\)至少要满足一个,所以\(j\)最大为\(i+d_i\)。以下,我们令\(j=i+d_{i}\)。
换句话说,对于任意一个\(i\),我们都有确定的\(j\)。
因此,对于第一个条件,等价于:
-
\(j≥k-d_k\)
这启发:所有的\(j\)只要在\(k-d_k\)以上,都满足第一个条件。
第二个条件已经用完了。
第三个条件,是限制所有满足第一个条件的基础上的一个条件,换句话说,满足第一个条件之后,还要满足第三个条件\(i\)才满足题意。
将第三个式子变形:
-
\(k≤2*j-i\)
容易看出,对所有满足第一个条件的\((i,j)\)而言,只对所有满足该条件的\(k\)产生影响,对于后面不满足的\(k\)没有影响。
具体地,我们关于值域建立一颗线段树,用以维护每个位置上\(j\)的个数。
对于每一个\(k\),对它统计的时候,我们需要将所有的\(k-d_k\)到\(n\)上的数统计\(j\)个数。因为探讨过,对于每一个\(i\),它有唯一确定的\(j\),所以\(j\)的个数就是满足题意的\(i\)的个数。
紧接着,对于第三个条件,对于\(2*j-i<k\)来说,\(i\)是有可能满足第一个条件的,我们刚刚统计的和需要删除的。
又由于该\(i\)对于后续的\(k\)都没有影响了,所有在遍历\(i\)的时候,顺便把它不满足要求的\(k\)中记录一下,所有当遍历到\(k\)的时候,将\(j\)对应的权值\(-1\)。通过这样做,就可以满足第三个条件了。
由于必须是\(i<k-1\),所以,我们正序扫一遍,同时添加,同时删除。
该算法貌似会T。将线段树改为树状数组即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cstdio>
using namespace std;
const int maxn = 3000000 + 5;
vector <int> p[maxn];
int n, ans = 0, d[maxn], c[maxn], f[maxn] = {};
void read(int &x)
{
bool mark = false;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
if(mark) x = -x;
return;
}
inline int lowbit(int x)
{
return x & (-x);
}
void add(int x, int v)
{
x = n - x + 1;
while(x <= n)
{
c[x] += v;
x += lowbit(x);
}
return;
}
int ask(int x)
{
int res = 0;
x = n - x + 1;
while(x)
{
res += c[x];
x -= lowbit(x);
}
return res;
}
int main()
{
read(n);
for(int i = 1; i <= n; ++ i) read(d[i]);
for(int k = 3; k <= n; ++ k)
{
int i = k - 2, j = i + d[i], l = min(n + 1, 2 * j - i + 1);
int cp = min(n, j);
add(cp, 1);
p[l].push_back(j);
for(int q = 0; q < p[k].size(); ++ q) add(p[k][q], -1);
f[k] = ask(max(1, k - d[k]));
}
for(int i = 3; i <= n; ++ i) ans ^= f[i];
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号