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\) 位是一个骰子滚动序列,转移为
时间复杂度 \(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_1\) 至 \(a_n\),\(2\le n,\sum n\le3\times10^5\)。
解法 1
直接使用高斯消元求解,时间复杂度 \(O(n^3)\),不能通过。
解法 2
对于 \(i\) 从 \(2\) 到 \(n-1\),有
可以解得 \(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\) 为
时间复杂度 \(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\) 开头的最长合法子序列,那么有转移
答案为
代码
#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();
}

浙公网安备 33010602011771号