230219题解(码量极少但思路有点难)
A 250
题目描述
众所周知\(250 = 2\times 5 \times 5 \times 5\) 。
请你求出有多少小于\(n\) 的形如\(k = p\times q^3\) 的数,且\(p < q\)。其中\(p,q\)都是质数。
输入格式
输入一行一个整数\(n\)。保证\(1\le n \le 10^{18}\)。
输出格式
输出一行一个整数,表示答案。
样例 1
输入
250
输出
2
解释
\(54=2\times 3^3,250=2\times 5^3\) 。
样例 2
输入
123456789012345
输出
226863
题解
对于这个\(10^{18}\)级别的数来说我们是肯定的计算不完所有质数的,
不过题目中给的\(3\)次方是明显可以缩小数据范围到\(10^6\)的
二话不说,直接放上线性筛计算质数,顺便记录一下到这个位置总共有多少质数
然后进行依次遍历,计算出当前第\(i\)个质数的\(3\)次方的值,
然后计算有多少个数可以与它组合,也就是除\(n\)
若除\(n\)后的数都还比当前质数大,那我们直接给答案加上\(i-1\),也就是所有小于它的质数
若除\(n\)后的结果是小于当前质数的,那我们就加上所有小于这个结果的质数
也就是我们在线性筛里顺带记录的那个数组
思路不算很难,码量也挺少,但我就是没做出来
#include<bits/stdc++.h>
using namespace std;
long long n,ans,pri[10000010],cnt,bc[10000010];
bool vis[10000010];
void init(){
for(int i = 2;i<=1000000;i++){
if(!vis[i]) pri[++cnt] = i,bc[i] = 1;
bc[i]+=bc[i-1];
for(int j = 1;j<=cnt&&i*pri[j]<=1000000;j++){
vis[i*pri[j]] = 1;
if(i%pri[j]==0) break;
}
}
}
int main(){
scanf("%lld",&n);
init();
long long ans = 0;
for(int i = 1;i<=cnt;i++){
if(i*i*i*1ll>n) break;
long long t = pri[i]*pri[i]*pri[i]*1ll,c = n/t;
if(c>=pri[i]) ans+=i-1;
else ans+=bc[c];
}
printf("%lld",ans);
return 0;
}
B 距离
题目描述
给定一个\(n\)个点\(m\)条边的简单无向图。保证每个点的度数不超过\(3\) 。
再给出\(q\)次询问,每次询问给出\(x_i,k_i\),请你求出与\(x_i\) 距离不超过\(k_i\)的点的编号的和。
输入格式
第一行两个整数\(n,m\)
接下来\(m\)行,每行两个整数\(u,v\),表示图中一条\(u,v\) 间的无向边。
接下来一行一个整数 。
接下来\(q\)行,每行两个整数\(x_i,k_i\)。
保证\(1 \le n,q \le 1.5\times 10^5,0\le m \le \min(\frac{n(n-1)}{2},\frac{3n}{2}), 1\le x_i \le n,0 \le k_i \le 3\) ,图中无重边且每个点度数不超过 \(3\)。
输出格式
输出\(q\)行,每行一个整数表示答案。
样例 1
输入
6 5
2 3
3 4
3 5
5 6
2 6
7
1 1
2 2
2 0
2 3
4 1
6 0
4 3
输出
1
20
2
20
7
6
20
题解
请各位注意题目数据范围,看清楚了吗?
\(k_i\)小于\(3\)看见了吗?
图中无重边且每个点度数不超过 \(3\)这个条件看见了吗?
我反正没有看见第二个
然后我们就可以得出一个结论
信竞也不需要视力
结论就是这题暴力可以过啊!!!
那还想啥,\(bfs\)干就完了
#include<bits/stdc++.h>
using namespace std;
struct edge{
int v,ne;
}e[500010];
int cnt,n,m,q,h[150010],vis[150010];
void add(int u,int v){
e[++cnt].v = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int bfs(int st,int k){
int ans = st;
queue<pair<int,int> >q;
q.push(make_pair(st,0));
vis[st] = true;
while(q.size()){
int t = q.front().first,step = q.front().second;q.pop();
//printf("%d %d\n",t,step);
if(step==k) break;
for(int i = h[t];i;i = e[i].ne){
int v = e[i].v;
if(vis[v]) continue;
vis[v] = true;
q.push(make_pair(v,step+1));
ans+=v;
}
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
scanf("%d",&q);
for(int i = 1;i<=q;i++){
memset(vis,0,sizeof(vis));
int x,k;
scanf("%d%d",&x,&k);
printf("%d\n",bfs(x,k));
}
return 0;
}
C 糖果
题目描述
有\(n\)个人编号为\(1,2,…,n\)。第\(i\)个人讨厌第\(X_i\)个人。
现在你需要以一定顺序给他们分糖果。如果第\(X_i\)个人比第\(i\)个人先获得糖果,那么第\(i\)个人就会获得\(C_i\)的沮丧度。
请你随意选择一个顺序,最小的沮丧度的和是多少?
输入格式
第一行一个整数\(n\)。
接下来一行\(n\)个整数,表示\(X_1,X_2,…,X_n\) 。
接下来一行\(n\)个整数,表示\(C_1,C_2,…,C_n\)。
保证\(2≤n≤2×10^5\),\(1≤X_i≤n\),\(X_i \neq i\),\(1≤C_i≤10^9\) 。
输出格式
输出一行一个整数,表示答案。
样例 1
输入
3
2 3 2
1 10 100
输出
10
样例 2
输入
8
7 3 5 5 8 4 1 2
36 49 73 38 30 85 27 45
输出
57
题解
首先,我们可以将所有的数据建图,按照所有的\(X_i\)将它与\(i\)连起来
不难发现,我们的答案就是找出所有的环中最小的边,然后将其相加
因为所有点都只有一个出边,明显就是一棵树
但是树的根节点是由一个环组成的,所以明显是一棵基环树
不会真有人跟我一样不知道基环树是啥吧
然后我们就可以开始优雅的找环
代码中的\(dfs\)即为找环的步骤
\(vis\)数组就是对于遍历状况的记录
当\(vis[i] = 1\)时,就代表这个数已经被记录了,但还有其子节点没有遍历完
当\(vis[i] = 2\)时,就代表这个数是在环上的了,且已经子节点都被遍历完了
#include<bits/stdc++.h>
using namespace std;
int n,a[200010],c[200010],vis[200010];
vector<int>re;
void dfs(int u){
if(vis[u]==2) return ;
if(vis[u])re.push_back(c[u]);
++vis[u];
dfs(a[u]);
vis[u] = 2;
}
int main(){
scanf("%d",&n);
for(int i = 1;i<=n;i++) scanf("%d",&a[i]);
for(int i = 1;i<=n;i++) scanf("%d",&c[i]);
long long ans = 0;
for(int i = 1;i<=n;i++){
dfs(i);
if(re.size()){
int t = 0x3f3f3f3f;
for(int x = 0;x<re.size();x++) t = min(t,re[x]);
ans+=t;
re.clear();
}
}
printf("%lld",ans);
return 0;
}
D 最大最小
题目描述
给定一个长为\(n\)的整数序列\(A\)和两个整数\(X,Y\)。
请你求出有多少区间\((L,R)\),满足
- \(1≤L≤R≤n\)。
- \(A_L,A_{L+1},…,A_R\)中的最大值为\(X\) ,最小值为\(Y\) 。
输入格式
第一行三个整数\(n,x,y\) 。
第二行\(n\)个整数\(A_1,A_2,…,A_n\)。
保证\(1≤n,A_i≤2×10^5\),\(1≤Y≤X≤2×10^5\)。
输出格式
输出一行一个整数,表示答案。
样例 1
输入
4 3 1
1 2 3 1
输出
4
解释
合法的区间为\((L,R)=(1,3),(1,4),(2,4),(3,4)\)。
题解
所有的数都只有四种可能性,等于\(X\),等于\(Y\),大于\(Y\)小于\(X\),大于\(X\)且小于\(X\)
我们这时候可以开三个标记来记录
第一个从当前位置向左的最后一个连续的第四类数
第二个记录等于\(X\)的位置
第三个记录等于\(Y\)的位置
然后在当前位置每往右移时,记录一次答案
每次将答案加上后两个标记中较小的一个并减去第一个标记
代码量非常的少但是思路确实不怎么好想
#include<bits/stdc++.h>
using namespace std;
int n,x,y,a[1000010];
int main(){
scanf("%d%d%d",&n,&x,&y);
for(int i = 1;i<=n;i++)
scanf("%d",&a[i]);
long long res = 0;
int lx = 0,ln = 0,lf = 0;
for(int i = 1;i<=n;i++){
if(a[i]==x) lx = i;
if(a[i]==y) ln = i;
if(a[i]>x||a[i]<y) lf = lx = ln = i;
res+=(min(lx,ln)-lf);
}
printf("%lld",res);
return 0;
}
E 生成树
题目描述
给定一张\(n\)个点\(m\)条边的简单连通无向图。
请你求出两个根为\(1\)的生成树\(T_1,T_2\),满足:
- \(T_1\)必须满足对于图中的任意一条不在树中的边 \(u,v\),\(u,v\)中的一个点必须是另一个点在树中的祖先。
- \(T_2\)必须满足对于图中的任意一条不在树中的边 \(u,v\),\(u,v\)两点不互为祖先子孙关系,即不存在一个点是另一个点在树中的祖先。
数据保证这样的两棵树存在。
输入格式
第一行两个整数 n,m。
接下来 m行,每行两个整数 u,v。
保证\(2 \le n \le 2\times 10^5\),\(n-1 \le m \le \min\{\frac{n(n-1)}{2},2\times 10^5\}\),\(1 \le u,v \le n\)。
输出格式
输出\(2n-2\)行。
前\(n-1\)行每行两个整数表示\(T_1\)中的边。
接下来\(n-1\)行每行两个整数表示\(T_2\)中的边。
你可以以任何顺序输出这些边,也可以以任何顺序输出边的两个顶点。
样例 1
输入
6 8
5 1
4 3
1 4
3 5
1 2
2 6
1 6
4 2
输出
1 4
4 3
5 3
4 2
6 2
1 5
5 3
1 4
2 1
1 6
题解
看此题解前请做好心理准备
破防?只是常态罢了
首先请读题自己想一想,别往那些高端的东西思考
想到了吗?如果没有那就继续看吧
想到了的大佬请出门右转
第一棵树只有返祖边而没有横叉边,很明显是一棵\(dfs\)树
你问我为什么,看就完了
我们需要知道关于\(dfs\)的前置知识
不知道的出门左转,知道的自己反思一下为啥想不到
我们来手动模拟一下\(dfs\)的过程,
先从根节点选一条边,然后一条路走到黑,再回溯到上一个父节点
再从这个节点选一条,没走过的边继续走下去,那么,如果是横叉边会怎么处理呢?
\(dfs\)一定会不重不漏的走完这个点的所有出边,那么横叉边的另一个端点自然会变成这个节点的子节点
因为这时候我们并没有将横叉边的另一个节点所在的子树进行遍历,
自然就会把那个节点归为此节点的子节点了,而且会将那个子树也遍历一遍,注意这是无向图
所以那个子树于根节点所连的边就是一条返祖边了
所以有可能某些节点与根节点相连但是被提前遍历了,那就会出现返祖边
想通没有?
那再看看第二棵是什么树?
都到这份上了还想不出来那就可以\(AFO\)了,初中生当我没说
有了前面的思路可以再类比过来,那不就是\(bfs\)树吗
\(bfs\)一定会在头一时间将所有出边走完
那返祖边的另一端点不就直接变成子节点了吗,所以返祖边会变成树边
那横叉边呢?
很明显\(bfs\)不会从这里出发,
因为横叉边若是连着小于等于自己的深度的节点,那么那个节点一定比你先遍历到
若是连着深度比你大的节点,那就会变为你的子节点
所以\(bfs\)会留下部分横叉边但不会留下返祖边
那代码就很简单了
#include<bits/stdc++.h>
using namespace std;
struct node{
int u,v,ne;
}e[500010];
int n,m,cnt,h[200010],vis[200010];
void add(int u,int v){
e[++cnt].u = u;
e[cnt].v = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
void dfs(int x){
vis[x] = true;
for(int i = h[x];i;i = e[i].ne){
int v = e[i].v;
if(!vis[v]){
printf("%d %d\n",e[i].u,v);
dfs(v);
}
}
}
void bfs(int x){
queue<int>q;
q.push(x);
vis[x] = true;
while(q.size()){
int t = q.front();q.pop();
for(int i = h[t];i;i = e[i].ne){
int v = e[i].v;
if(!vis[v]){
printf("%d %d\n",e[i].u,v);
q.push(v);
vis[v] = true;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1);
memset(vis,false,sizeof(vis));
bfs(1);
return 0;
}
有蠢货存边没开二倍空间我不说是谁
F 矩形 GCD
题目描述
给定两个长为 n的数组\(A_1,A_2,\ldots,A_n\)和\(B_1,B_2,\ldots,B_n\)。
通过这两个数组,我们构造一个\(n \times n\)的矩阵,满足其中第$ i\(行第\) j\(个位置中的数为\) A_i+B_j$。
现在我们进行 \(q\)次询问,每次询问给定四个整数 \(h_1,h_2,w_1,w_2\),保证\(1 \le h_1 \le h_2 \le n,1 \le w_1 \le w_2 \le n\),请你求出左上角为$ (h_1,w_1)\(,右下角为\) (h_2,w_2) \(所形成的矩形中所有数的\) \gcd$值。
输入格式
第一行两个数$ n,q$。
第二行一行\(n\)个整数,表示$ A_1,A_2,\ldots,A_n$。
第三行一行\(n\)个整数,表示$ B_1,B_2,\ldots,B_n$。
接下来$ q$行,每行四个整数,表示 \(h_1,h_2,w_1,w_2\)。
保证 $1\le n,q \le 2\times 10^5,1 \le A_i,B_i \le 10^9,1 \le h_1 \le h_2 \le n,1 \le w_1 \le w_2 \le n $。
输出格式
输出\(q\)行,每行一个整数,表示答案。
样例 1
输入
3 5
3 5 2
8 1 3
1 2 2 3
1 3 1 3
1 1 1 1
2 2 2 2
3 3 1 1
输出
2
1
11
6
10
题解
这题暴力是肯定不能直接开莽的
建议去复习一下\(\gcd\)的性质,可以发现如下一个:
\(\gcd(a,b) = \gcd(a,a-b)\)
这下就好办了,
每一个位置上都是\(A_i + B_j\)那么我们思考在一行上的\(\gcd\)应该是多少
很明显\(i\)是一个定值,那么这一行就只需要求\(\gcd(A_i+B_j,A_i+B_{j+1},A_i+B_{j+2},\cdots)\)
我们再用上那个性质,就可以简化为\(\gcd(A_i+B_j,B_j-B_{j+1},B_{j+1}-B_{j+2},\cdots)\)
对于一列来说也是一样的
等等,再看看这个形式,想起某一个东西没有?
没错就是差分
我们只需要将行列分别计算一个差分数组,
分别对行和列进行询问,每次询问就可以做到\(O(n)\)的时间复杂度
但是呢,对于区间询问这种东西,有没有想起某知名数据结构
哈哈,就是线段树,所以这题就成了这几道中码量最大的了
#include<bits/stdc++.h>
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
struct node{
int l,r,dat;
}sga[800010],sgb[800010];
int n,q,a[200010],b[200010],ca[200010],cb[200010];
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
void build(int p,int l,int r){
sga[p].l = sgb[p].l = l;
sga[p].r = sgb[p].r = r;
if(l==r){
sga[p].dat = abs(ca[l]);
sgb[p].dat = abs(cb[l]);
return ;
}
int mid = (l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
sga[p].dat = gcd(sga[lc].dat,sga[rc].dat);
sgb[p].dat = gcd(sgb[lc].dat,sgb[rc].dat);
}
int querya(int p,int l,int r){
if(sga[p].l>=l&&sga[p].r<=r)
return sga[p].dat;
int mid = (sga[p].l+sga[p].r)>>1,ans = 0;
if(l<=mid){
if(!ans) ans = querya(lc,l,r);
else ans = gcd(ans,querya(lc,l,r));
}
if(r>mid){
if(!ans) ans = querya(rc,l,r);
else ans = gcd(ans,querya(rc,l,r));
}
return ans;
}
int queryb(int p,int l,int r){
if(sgb[p].l>=l&&sgb[p].r<=r)
return sgb[p].dat;
int mid = (sgb[p].l+sgb[p].r)>>1,ans = 0;
if(l<=mid){
if(!ans) ans = queryb(lc,l,r);
else ans = gcd(ans,queryb(lc,l,r));
}
if(r>mid){
if(!ans) ans = queryb(rc,l,r);
else ans = gcd(ans,queryb(rc,l,r));
}
return ans;
}
int main(){
scanf("%d%d",&n,&q);
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
ca[i] = a[i]-a[i-1];
}
for(int i = 1;i<=n;i++){
scanf("%d",&b[i]);
cb[i] = b[i]-b[i-1];
}
build(1,1,n);
for(int i = 1;i<=q;i++){
int h1,h2,w1,w2;
scanf("%d%d%d%d",&h1,&h2,&w1,&w2);
printf("%d\n",gcd(gcd(a[h1]+b[w1],querya(1,h1+1,h2)),gcd(a[h1]+b[w1],queryb(1,w1+1,w2))));
}
return 0;
}
偷偷完结撒花
梦与现实间挣扎着,所求为何
你可以借走我的文章,但你借不走我的智慧 虽然我是傻逼本文来自博客园,作者:cztq,转载请注明原文链接:https://www.cnblogs.com/cztq/p/17152751.html

浙公网安备 33010602011771号