训练赛3(2020 ICPC Asia Taipei-Hsinchu & Heilongjiang Programming Contest)
导语
第3次训练赛,选择了签到题、铜牌题、银牌题,根据队内安排与自己选择,选择值得参考的题目进行整理
涉及的知识点
数论、计算几何、思维、素数筛、搜索、二分
链接:ACM2019训练2
题目
A(Gym 102835A )
题目大意:给出一个定义,对于一个整数x,如果存在整数a、b满足 0 ≤ a ≤ b ≤ x 0\le a\le b\le x 0≤a≤b≤x使得 a × b = x a×b=x a×b=x并且 a b ≥ 0.5 \frac{a}{b}\ge0.5 ba≥0.5则称x满足该定义,现在给出N个数,判断这N个数是否都满足这一定义
思路:有一个结论,对于任意整数x,必存在一个因子大于等于 x \sqrt{x} x,并有其对应因子小于等于\sqrt{x},通过定义可以知道,如果想要满足 2 a ≥ b 2a\ge b 2a≥b, a 、 b a、b a、b需要尽可能接近,那么只需要找到以 x \sqrt{x} x为分界线最接近的一组因数,如果这一组都无法满足,此时a会更小,b会更大,必然无法满足
代码
#include <bits/stdc++.h>
using namespace std;
int N;
int main() {
scanf("%d",&N);
while(N--) {
int t,q,p;
scanf("%d",&t);
q=sqrt(t);
for(int i=q; i>=1; i--)
if(t%i==0) {
q=i;
break;
}
p=t/q;
if(2*q>=p)
cout <<"1\n";
else
cout <<"0\n";
}
return 0;
}
B(Gym 102835B)

题目大意:给出四个数字,所给数字从1到9,可以改变数字顺序,中间可以添加+、×、-,优先级×最高,数字间可以直接合并为一个数字,但至少有一个符号,求最终能生成多少个非负整数
思路:暴力枚举生成的所有数,枚举符号,用栈计算结果,set去重,注意去掉负数
代码
#include <bits/stdc++.h>
using namespace std;
int ans,data[5];
set<int>S;
vector<vector<int> >vec;//存储所有组合
typedef pair<int,int>pr;
int Getsum(deque<pr>Q) {//计算一组数字+排列的值
deque<pr>now;//临时变量
while(!Q.empty()) {
pr num=Q.front();
Q.pop_front();
if(num.first==1&&num.second==2) {//先把所有的乘号调出来
pr nex=Q.front();
Q.pop_front();
pr pre=now.back();
now.pop_back();
now.push_back({0,nex.second*pre.second});
} else
now.push_back(num);
}
Q=now;//算完乘的结果,now中依然保留了未算的加和减
now.clear();
while(!Q.empty()) {
pr num=Q.front();
Q.pop_front();
if(num.first==1) {//如果是符号
pr nex=Q.front();
Q.pop_front();
pr pre=now.back();
now.pop_back();
if(num.second==0)
now.push_back({0,nex.second+pre.second});
else if(num.second==1)
now.push_back({0,nex.second-pre.second});
} else
now.push_back(num);
}
return now.front().second;
}
void DFS(int id,int now,vector<int>v) {
if(now)
v.push_back(now);
if(id==5) {//代表数已经放置完毕
vec.push_back(v);//存入一个方案
return;
}
DFS(id+1,data[id],v);//单个放的组合
if(now)
v.pop_back();
now=now*10+data[id];//合并放的组合
DFS(id+1,now,v);
}
void ch(int id,vector<int>v,deque<pr>Q) {
Q.push_back({0,v[id]});//放入数字,必须先放入数字
if(id==v.size()-1) {//数字放完
int sum=Getsum(Q);//获取和
if(sum>=0)
S.insert(sum);
return ;
}
for(int i=0; i<3; i++) {//放符号,0,1,2对应+、×、-
Q.push_back({1,i});//放入符号
ch(id+1,v,Q);
Q.pop_back();
}
}
void cal(vector<int>v) {
if(v.size()==1)//去掉没有符号的方案
return;
deque<pr>Q;
ch(0,v,Q);//开始放符号
}
int main() {
scanf("%d%d%d%d",&data[1],&data[2],&data[3],&data[4]);
sort(data+1,data+5);//排序,便于处理
do {
vector<int>v;//存储方案
DFS(1,0,v);//遍历每个位置获得所有方案
} while(next_permutation(data+1,data+5));
int len=vec.size();//获得总方案数
for(int i=0; i<len; i++)
cal(vec[i]);//计算每个方案的结果并存储
printf("%d\n",S.size());
return 0;
}
E(Gym 102835H)

题目大意:
思路:
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,fa[1212121],siz[1212121],from,to,minn,id;
//点、边、父节点
int head[1212121],cnt=1;//链式前向星,链表头
bool vis[1212121];
ll sum;
struct Edge {
int to,next,w;//连接点、链表下一项、值
} edge[1212121];
struct node {
int x,y,w;
} in[1212121];//边节点
void addedge(int u,int v,int w) {
edge[++cnt].to=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt;
}
bool cmp(node a,node b) {
return a.w<b.w;
}
int Find(int x) {
if(fa[x]==x)
return x;
return fa[x]=Find(fa[x]);
}
bool Union(int x,int y) {
int _x=Find(x),_y=Find(y);
if(_x!=_y) {
fa[_x]=_y;
return 1;
}
return 0;
}
bool check(int t,bool flag) {//检查能否构成生成树,t是最小边编号
int num=0;
for(int i=1; i<=n; i++)//初始化
fa[i]=i;
for(int i=t; i<=m; i++) {
if(Union(Find(in[i].x),Find(in[i].y))) {//如果集合相异
if(flag) {//如果flag为真值,代表t为解,构造树
addedge(in[i].x,in[i].y,in[i].w);
addedge(in[i].y,in[i].x,in[i].w);
}
num++;//记录连了几条边
}
if(num>=n-1)
return 1;
}
return 0;
}
void cal(int u,int fa) {
siz[u]=1;
for(int i=head[u]; i; i=edge[i].next) {
int v=edge[i].to;
if(vis[i]||v==fa)//如果是与父节点的边或者已访问过的边跳过
continue;
cal(v,u);//计算连通块的节点数
siz[u]+=siz[v];//累加节点数
if(edge[i].w<minn) {//记录选取出来的最小边
minn=edge[i].w;//记录权重
id=i;//记录最小边
from=u,to=v;//同上
}
}
}
ll dfs(int u) {
bool f=0;
for(int i=head[u]; i; i=edge[i].next) {
if(vis[i])
continue;
f=1;
break;
}
if(!f)//代表每条边都已遍历
return 0;
minn=INT_MAX,id=0;
cal(u,-1);//计算以u为根节点的左右子树规模和再加本身,并获得这个连通块的最小边
vis[id^1]=vis[id]=1;//加边时双向加边,所以一条边对应两个连续编号
int t1=from,t2=to;//该边的左端点和右端点
return 1ll*(siz[u]-siz[to])*siz[to]*minn+dfs(t1)+dfs(t2);
/*siz[u]记录的是连通块规模,siz[u]-siz[to]为左连通,
siz[to]为右连通,权重为左×右×值,siz的对象为点,但是计算需要的对象为边*/
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
scanf("%d%d%d",&in[i].x,&in[i].y,&in[i].w);
sort(in+1,in+1+m,cmp);//按照边权排序
int l=1,r=m,ans=1;
while(l<=r) {//二分枚举最小边,检查剩下的边能否构成生成树
int mid=(l+r)>>1;
if(check(mid,0))
ans=mid,l=mid+1;
else
r=mid-1;
}
check(ans,1);//统计答案
printf("%lld\n",dfs(1));//此时树已构造好
return 0;
}
G(Gym 102803A )
题目大意:计算给定函数围成的面积
思路:将给定y<0的两个函数进行积分可以得到面积大小各自为 2 a b 2ab 2ab,所以面积总和为 2 a b + 9 π 2ab+9\pi 2ab+9π
代码
#include <bits/stdc++.h>
#include <cmath>
using namespace std;
int T;
double a,b,pi=3.1415926535;
int main() {
scanf("%d",&T);
while(T--) {
scanf("%lf%lf",&a,&b);
printf("%.8f\n",pi*a*a+4*a*b);
}
return 0;
}
J(Gym 102803G )
题目大意:给出一个数,两个人轮流取该数的真因数(不为1也不为该数),例如一开始的数为N,a取出N的真因数M,此时N被替换为M,再由b取,最后不能取者为赢,如何操作能让a赢,并让a第一次取的数字最大,赢不了输出-1,一开始就赢输出0,否则输出第一次取的数
思路:如果一开始的数为质数,a赢,如果一开始的数为两个质数的乘积,a无法赢,因为a取任何一个质数,取到的都为质数,b都无法操作,其他情况,就需要构造a取过后a取到的值为两个质数的乘积的情况,也就是取得初始值的最大质因数以及次大质因数的乘积,以这个策略操作即可
代码
#include <bits/stdc++.h>
using namespace std;
int T,n,prime[12121],len;
bool isprime[121212];
int GetMax(int x) {
if(x==1)
return 1;
for(int i=2; i<=x/i; i++)
if(x%i==0)
return max(GetMax(x/i),GetMax(i));//返回最小质因数的最大值
return x;
}
int main() {
memset(isprime,1,sizeof(isprime));
isprime[0]=isprime[1]=0;
for(int i=2; i<=1e5+10; i++) {
if(isprime[i])
prime[++len]=i;
for(int j=1; j<=len&&i*prime[j]<=1e5+10; j++) {
isprime[i*prime[j]]=0;
if(i%prime[j]==0)
break;
}
}//筛出前1e5+10内的素数
scanf("%d",&T);
while(T--) {
int a=0,b=0;
scanf("%d",&n);
if(isprime[n]||n==1)//如果是本身是质数或者是1
printf("0\n");
else {
a=GetMax(n);//获得最大质因数
b=GetMax(n/a);//获得第二大质因数
printf("%d\n",a*b==n?-1:a*b);
}
}
return 0;
}
K(Gym 102803L)
题目大意:在平面直角坐标系中,从原点(0,0)开始,按照上、右、下、左 的顺序进行bfs,每个位置的值从0依次递增。
定义两种操作:
1 id : 输出平面中值为id的坐标(相对于上一次操作的坐标)
2 x y : 输出此坐标(相对于原点(0,0))的值
思路:见参考文献与代码
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#define LL long long
#include<cstring>
#include<algorithm>
#define INF ox7fffffff
using namespace std;
LL nowx,nowy;
LL find(LL x) {
if(x<=0)
return 0;
return 2*(1+x)*x;
}//返回这一层的最大值
int check(LL id) {//二分查找当前id所属的层数
LL l=0,r=1e9;
while(l<r) {
LL mid=(l+r)>>1;
if(find(mid)>=id)
r=mid;
else
l=mid+1;
}
return l;
}
void work1(LL id) {
LL n=check(id);//获得层数
id-=find(n-1);//去掉上一层的个数,获得该层的相对偏移量
LL x,y;
if(id==0)//只有id为0的时候才能得到此值
x=0,y=0;
else if(id==1)//刚好是最上层
x=0,y=n;
else if(id<=2*n-1) {//第一二象限
LL h=id/2;
LL r=id%2;
if(!r) {
x=h;
y=n-h;
} else {
x=-h;
y=n-h;
}
} else if(id<=3*n) {//第三象限
id-=2*n;
y=-id;
x=n-id;
} else if(id<=4*n) {//第四象限
id-=3*n;
x=-id;
y=-(n-id);
}
printf("%lld %lld\n",x-nowx,y-nowy);//输出相对偏移量
nowx=x;
nowy=y;
}
void work2(LL x,LL y) {
LL n=abs(x)+abs(y);//获得层数
LL id;
id=find(n-1);//获当前层之前的个数和
if(y>0) {//一四象限
if(x>0)
id+=2*abs(x);
else
id+=2*abs(x)+1;
} else {//二三象限
if(x>=0)
id+=2*n+abs(y);
else
id+=3*n+abs(x);
}
if(n==0)
id=0;
printf("%lld\n",id);
nowx=x,nowy=y;
}
int main() {
int t;
cin>>t;
while(t--) {
int a;
cin>>a;
if(a==1) {//如果是求坐标
LL id;
cin>>id;
work1(id);
} else {//如果是求id
LL x,y;
cin>>x>>y;
work2(x,y);
}
}
return 0;
}
本文精选2020ICPC亚洲区域赛中的经典算法题目,包括数论、计算几何等,详细介绍了各题目的背景、思路及代码实现,适合算法学习者参考。

被折叠的 条评论
为什么被折叠?



