Codeforces 1080 (Div. 3,CF 2195) Overall Solution

本文中,使用 \(T\) 表示这个题的单个测试点数据组数,时间复杂度都是单组数据的。

A 埃拉托斯特尼 67 筛法

题意

给定一个长度为 \(n\) 的正整数序列 \(a\),判断是否能够从中选出一些数使得它们的乘积为 \(67\)
\(n\le5,a_i\le67,T<=10^4\)

解法 1

枚举 \(2^n\) 个子集判断是否乘积为 \(67\),时间复杂度 \(O(2^nn)\),可以通过。

解法 2

由于乘法的结合律,一个集合的乘积等于其任意两个不交的子集的乘积,时间复杂度 \(O(2^n)\),可以通过。

解法 3

因为 \(67\) 是质数,因此如果一些正整数的乘积是 \(67\),当且仅当其中包含 \(1\)\(67\) 和多个 \(1\)

只需要判断 \(a\) 中是否有 \(67\) 即可。时间复杂度 \(O(n)\),可以通过。

代码(解法 3)

#include<cstdio>
int n,a,ans;
void Main(){
    scanf("%d",&n),ans=0;
    while(n--)scanf("%d",&a),ans+=a==67;
    puts((ans)?"YES":"NO");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

B 堆化 1

题意

给定一个长度为 \(n\) 的排列 \(a\),每次可以交换 \(a_i\)\(a_{2i}\)(前提是下标合法),问能否将 \(a\) 升序排序。

\(\sum n\le 2\times10^5\)

解法

定理 对于一个排列 \(a\),只通过交换相邻两项一定能够将其升序排序。

证明考虑如果已经将前 \(i-1\) 位升序排序了,那么找到 \(a_j=i\),必有 \(j\ge i\),只需要将 \(a_j\) 不断向前交换至 \(a_i\),就能将前 \(i\) 位升序排序了。归纳可证得一定能够将整个排列升序排序。

在本体中,去除所有 \(2\) 的次幂后值相同的位置构成一个排列,并且可以相邻两项交换。而去除所有 \(2\) 的次幂后值不相同的位置不能够通过任何方式互相交换。

因此对于每个 \(i\),判断 \(i\over a_i\) 是否是 \(2\) 的整次幂(可以为负)即可。

时间复杂度 \(O(n)\),可以通过。

代码

#include<cstdio>
int n,a;
bool b[200005];
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a);
        if((i%a==0)&&b[i/a])continue;
        if((a%i==0)&&b[a/i])continue;
        puts("No");
        for(int j=i+1;j<=n;j++)scanf("%d",&a);
        return;
    }
    puts("Yes");
}
int main(){
    int t;
    scanf("%d",&t);
    for(int i=0;(1<<i)<=2e5;i++)b[1<<i]=1;
    while(t--)Main();
}

C 骰子滚动序列

题意

有一颗六面体骰子,每个面有 \(1\)\(6\) 的编号。一个值域为 \(1\)\(6\) 的正整数序列是一个骰子滚动序列当且仅当,编号为这个序列的相邻两个元素的面是相邻的。

给定一个长度为 \(n\),值域为 \(1\)\(6\) 的正整数序列 \(a\),至少要修改对少个位置的元素才能使得它变为一个骰子滚动序列。

\(\sum n\le3\times10^5\)

解法 1

使用动态规划,设 \(dp_{i,j}\) 表示考虑前 \(i\) 位,在第 \(i\) 位为 \(j\) 的前提下,至少修改多少次能够使得前 \(i\) 位是一个骰子滚动序列,转移为

\[dp_{i,j}=\min_{k\in\{1,2,3,4,5,6\},k\ne j,k+j\ne7}dp_{i-1,k}+[a_i\ne j] \]

时间复杂度 \(O(n)\),常数较大,可以通过。

解法 2

对于每个位置 \(i\) 建点。如果位置 \(i\)\(i+1\)\(a_i=a_{i+1}\) 或者 \(a_i+a_{i+1}=7\),那么将 \(i\)\(i+1\) 连边。

我们如果将修改一个点视作将其染色,那么对于一条边其两端点至少有一个需要被染色。证明答案下界,相当于要选择尽量少的点,使得每条边的两端点至少有一个被选。

由于此时每个连通块的是一条链,因此一个大小为 \(s\) 的连通块,至少需要选择 \(\left\lfloor s\over 2\right\rfloor\) 个点。又由于对于任意两个面,一定存在第三个面与它们都相邻,因此这就是答案。

时间复杂度 \(O(n)\),可以通过。

代码(解法 1)

#include<bits/stdc++.h>
int n;
long long f[300005],a[300005];
__int128 of,I;
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&f[i]);
    for(int i=2;i<n;i++)a[i]=(f[i-1]+f[i+1]-2*f[i])/2;
    of=f[1];
    for(int i=2;i<n;i++)of-=I*(i-1)*a[i];
    a[n]=of/(n-1),of=f[n];
    for(int i=2;i<n;i++)of-=I*(n-i)*a[i];
    a[1]=of/(n-1);
    for(int i=1;i<=n;i++)printf("%lld ",a[i]);
    puts("");
}
int main(){
    int t;
    scanf("%d",&t),I=1;
    while(t--)Main();
}

D 绝对值影院

题意

给定一个关于 \(a_1,a_2,\cdots,a_n\)\(n\)\(1\) 次方程组,第 \(i\) 个方程为

\[A_i:\sum_{j=1}^na_i\ \cdot|i-x|=f_i \]

求解 \(a_1\)\(a_n\)\(2\le n,\sum n\le3\times10^5\)

解法 1

直接使用高斯消元求解,时间复杂度 \(O(n^3)\),不能通过。

解法 2

对于 \(i\)\(2\)\(n-1\),有

\[A_{i-1}+A_{i+1}-2A_i:2a_i=f_{i-1}+f_{i+1}-2f_i \]

可以解得 \(a_2,a_3,\cdots,a_{n-1}\)

然后注意到 \(A_1\)\(a_1\) 项的系数为 \(0\)\(a_n\) 项的系数不为 \(0\),可以解出 \(a_n\)\(A_n\)\(a_1\) 项的系数不为 \(0\),可以解出 \(a_1\)

时间复杂度 \(O(n)\),可以通过。

代码(解法 2)

#include<bits/stdc++.h>
int n;
long long f[300005],a[300005];
__int128 of,I;
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&f[i]);
    for(int i=2;i<n;i++)a[i]=(f[i-1]+f[i+1]-2*f[i])/2;
    of=f[1];
    for(int i=2;i<n;i++)of-=I*(i-1)*a[i];
    a[n]=of/(n-1),of=f[n];
    for(int i=2;i<n;i++)of-=I*(n-i)*a[i];
    a[1]=of/(n-1);
    for(int i=1;i<=n;i++)printf("%lld ",a[i]);
    puts("");
}
int main(){
    int t;
    scanf("%d",&t),I=1;
    while(t--)Main();
}

E 笨蛋优先搜索

题意

给定一棵编号为 \(1\sim n\)\(n\) 个点的二叉树(为了方便,不妨令 \(1\) 的父亲是 \(0\)),当笨蛋位于节点 \(x\) 时它会遍历完 \(x\) 的子树后再去到 \(x\) 的父亲。每一步移动的时间是 \(1\)

对于每个节点,询问从它出发到达 \(0\) 的时间。

\(\sum n\le 3\times10^5\)

解法

\(size_x\) 为以 \(x\) 为根,一个点开始遍历完后再去到父亲的时间为 \(t_x=2size_x-1\)

那么答案 \(ans_x\)

\[ans_x=ans_{p_x}+t_x \]

时间复杂度 \(O(n)\)

代码

#include<cstdio>
#include<vector>
#define N 300005
int n,lc[N],rc[N],fa[N],size[N],mod=1e9+7;
long long ans[N];
void dfs(int now){
    if(!now)return;
    dfs(lc[now]),dfs(rc[now]),size[now]=size[lc[now]]+size[rc[now]]+1;
}
void dgs(int now){
    if(!now)return;
    ans[now]=ans[fa[now]]+1+(size[now]-1<<1);
    dgs(lc[now]),dgs(rc[now]);
}
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&lc[i],&rc[i]),fa[lc[i]]=fa[rc[i]]=i;
    }
    dfs(1),dgs(1);
    for(int i=1;i<=n;i++)printf("%lld ",ans[i]%mod);
    puts("");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

F 二次不等式

题意

给定 \(n\) 个二次函数 \(f_1,f_2,\cdots,f_n\)。对于 \(i\)\(1\)\(n\),求集合 \(F=\{f_1,f_2,\cdots,f_n\}\) 的最大的子集 \(G\) 使得 \(G\) 中的二次函数两两不交,并且 \(f_i\in G\)

\(\sum n^2\le3000^2\)

解法

对于任意两个不交的二次函数 \(f\)\(g\),如果 \(f(0)<g(0)\),那么对于任意 \(x\) 都有 \(f(x)<g(x)\),我们称 \(f<g,g>f\)

不难发现此处定义的 \(<\) 具有传递性,因此若 \(f<g,g<h\)\(f<h\)\(f\)\(h\) 无交。

于是将所有二次函数按 \(f(0)\) 排序,原问题等价于选择一个最长的包含 \(f_i\) 的子序列,使得序列中相邻的函数无交。

使用 DP,记 \(l_i\) 为以 \(i\) 结尾的最长合法子序列,\(r_i\) 为以 \(i\) 开头的最长合法子序列,那么有转移

\[\begin{aligned} l_i&=1+\max_{j=1}^{i-1}[f_i\bot f_j]l_j\\ r_i&=1+\max_{i+1}^n[f_i\bot f_j]l_j \end{aligned}\]

答案为

\[ans_i=l_i+r_i-1 \]

代码

#include<cstdio>
#include<vector>
#include<algorithm>
#define N 3005
struct node{
    int a,b,c,id;
    bool operator<(const node z)const{return c<z.c;}
}a[N];
bool pd(node x,node y){
    int a=x.a-y.a,b=x.b-y.b,c=x.c-y.c;
    if(a==0&&b==0&&c==0)return 0;
    if(a==0&&b==0)return 1;
    if(a==0)return 0;
    return 1ll*b*b-4ll*a*c<0;
}
int f[N],n,g[N],ans[N];
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),f[i]=g[i]=1,a[i].id=i;
    std::sort(a+1,a+n+1);
    for(int i=1;i<=n;i++)for(int j=1;j<i;j++)if(pd(a[i],a[j]))f[i]=std::max(f[i],f[j]+1);
    for(int i=n;i;i--)for(int j=i+1;j<=n;j++)if(pd(a[i],a[j]))g[i]=std::max(g[i],g[j]+1);
    // for(int i=1;i<=n;i++)printf("!%d %d %d\n",a[i].id,f[i],g[i]);
    for(int i=1;i<=n;i++)ans[a[i].id]=f[i]+g[i]-1;
    for(int i=1;i<=n;i++)printf("%d ",ans[i]);
    puts("");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

G 笨蛋优先搜索和查询

题意

在 F 题的基础上,改为 \(q\) 次询问,每次询问从节点 \(x\) 出发,走 \(k\) 步后达到何处(保证 \(k\) 不超过达到 \(0\) 的时间)。

\(\sum n,\sum q\le5\times10^5\)

解法 1

预处理出欧拉序。对于一次询问我们找到 \(x\) 的祖先中最浅的一个 \(p\) 使得 \(ans_x-ans_p<k\),然后答案为从 \(p\) 开始走 \(k-(ans_x-ans_p)\) 步欧拉序到达的位置。

使用二分/倍增,时间复杂度 \(O((n+q)\log(n+q))\),可以通过。

解法 2

考虑解法 1 的瓶颈在于二分实现类似于 lower_bound 的过程。

考虑将询问按 \(k-ans_x\) 离线排序,然后走指针。

用并查集维护 \(n\) 个指针,每次移动时要合并子树中的并查集根。

总移动量 \(O(n)\),故时间复杂度 \(O(n+q)\)

注意到排序的过程中值域是 \(O(n^2)\) 的,故使用基数排序的复杂度就是 \(O(n)\)

代码(解法 1)

#include<cstdio>
#include<vector>
#define N 300005
int n,q,lc[N],rc[N],fa[N],size[N],mod=1e9+7;
long long ans[N];
int anss[N],pp[N][20];
int in[N],ccnt,euler[N<<1];
void dfs(int now){
    if(!now)return;
    euler[++ccnt]=now;in[now]=ccnt;
    pp[now][0]=fa[now];
    for(int i=1;i<20;i++)pp[now][i]=pp[pp[now][i-1]][i-1];
    dfs(lc[now]),dfs(rc[now]),size[now]=size[lc[now]]+size[rc[now]]+1;
    euler[++ccnt]=fa[now];
}
void dgs(int now){
    if(!now)return;
    anss[now]=1+(size[now]-1<<1);
    ans[now]=ans[fa[now]]+anss[now];
    ccnt=0;
    dgs(lc[now]),dgs(rc[now]);

}
void Main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&lc[i],&rc[i]),fa[lc[i]]=fa[rc[i]]=i;
    }
    dfs(1),dgs(1);
    while(q--){
        int v,k;
        scanf("%d%d",&v,&k);
        for(int i=19;~i;i--){
            int cur=pp[v][i];
            if(ans[v]-ans[cur]<k)k-=ans[v]-ans[cur],v=cur;
        }
        // printf("(%d %d)",v,k);
        printf("%d ",euler[k+in[v]]);
    }
    puts("");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

H Codeforces 启发式比赛 001

题意

给定 \(n\),在 \(3n\times 3n\) 个点构成的单位矩阵中构造尽可能多的三角形,使得三角形两两不交,并且面积都为 \(1\over2\)

\(\sum n^2\le166\)

解法 by AAhaoxuan

对于任意 \(2\times 3\) 的子矩阵,可以构造 \(2\) 个满足条件的三角形。进而对于任意 \(a\times b\)\(a\)\(3\) 的倍数,\(b\)\(2\) 的倍数)的子矩阵,可以构造所有格点都被包含的三角形。

对于任意 \(5\times 9\) 的子矩阵,可以构造所有格点都被包含的三角形。

以上两类的具体构造可以见下方代码。

情况 1(\(n=1\)

此时答案一定为 \(2\),具体构造方案见代码。

情况 2(\(n\) 为偶数)

此时 \(3n\) 既是 \(2\) 的倍数,又是 \(3\) 的倍数,使用 \(2\times3\) 的子矩阵构造。

情况 3(\(n\) 为大于 \(1\) 的奇数)

此时将其划分成 \(5\times 9,(3n-5)\times 9,3n\times(3n-9)\) 三个部分。

由于 \(3n-5,3n-9\) 都是偶数,因此 \((3n-5)\times 9,3n\times(3n-9)\) 两个部分都可以使用 \(2\times3\) 的子矩阵构造。

代码

#include<cstdio>
#include<vector>
int n;
void Main(){
    scanf("%d",&n);
    if(n==1){
        printf("2\n1 1 1 2 2 1\n2 3 3 2 3 3\n");
        return;
    }
    printf("%d\n",3*n*n);
    if(n%2==0){
        for(int i=1;i<=n;i++)for(int j=1;j<=n*3/2;j++){
            int x=3*i-2,y=2*j-1;
            printf("%d %d %d %d %d %d\n%d %d %d %d %d %d\n",x,y,x,y+1,x+1,y,x+1,y+1,x+2,y,x+2,y+1);
        }
        return;
    }
    printf("1 1 1 2 2 1\n");
    printf("1 3 2 2 2 3\n");
    printf("1 4 1 5 2 4\n");
    printf("2 5 3 4 3 5\n");
    printf("3 1 3 2 4 1\n");
    printf("3 3 4 4 4 5\n");
    printf("4 2 5 1 5 2\n");
    printf("4 3 5 3 5 4\n");
    printf("5 5 6 4 6 5\n");
    printf("6 1 6 2 7 1\n");
    printf("6 3 7 4 7 5\n");
    printf("7 2 7 3 8 1\n");
    printf("8 2 8 3 9 1\n");
    printf("8 4 9 2 9 3\n");
    printf("8 5 9 4 9 5\n");
    for(int i=1;i<=3;i++)for(int j=1;j<=(3*n-5)/2;j++){
        int x=3*i-2,y=2*j+4;
        printf("%d %d %d %d %d %d\n%d %d %d %d %d %d\n",x,y,x,y+1,x+1,y,x+1,y+1,x+2,y,x+2,y+1);
    }
    for(int i=1;i<=(3*n-9)/2;i++)for(int j=1;j<=n;j++){
        int x=2*i+8,y=3*j-2;
        printf("%d %d %d %d %d %d\n%d %d %d %d %d %d\n",x,y,x,y+1,x+1,y,x,y+2,x+1,y+1,x+1,y+2);
    }
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}
posted @ 2026-02-21 12:36  AAhaoxuan  阅读(19)  评论(0)    收藏  举报