比赛传送门
作者: fn
目录
签到题
N题 Particle Arts / 粒子艺术
题目大意
𝑛
𝑛
n 个粒子,每个粒子有一个能量
𝑎
𝑖
𝑎_𝑖
ai 。两两随机碰撞,每碰撞一次两粒子的能量变为
𝑎
&
𝑏
,
𝑎
∣
𝑏
𝑎\&𝑏, 𝑎|𝑏
a&b,a∣b。
求所有粒子能量稳定后的方差。
𝑛
≤
1
0
5
,
0
≤
𝑎
𝑖
<
2
15
𝑛≤10^5,0≤𝑎_𝑖<2^{15}
n≤105,0≤ai<215
考察内容
贪心
分析
每次碰撞后的
𝑎
&
𝑏
,
𝑎
∣
𝑏
𝑎\&𝑏, 𝑎|𝑏
a&b,a∣b 和原先相比,小的更小而大的更大,所以每次碰撞后方差单调增加。
预处理每个二进制位的数量,把他们尽可能放到少的数字里面去,得到的结果即最终结果,此时方差最大。
求方差的时候开__int128就行。
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll b[N];
ll num[17];
__int128 gcd(__int128 a,__int128 b){
if(b==0)
return a;
else
return gcd(b,a%b);
}
void print(__int128 x){
if(!x){
cout<<"0";
}
string ret="";
while(x){
ret+=x%10+'0';
x/=10;
}
reverse(ret.begin(),ret.end());
cout<<ret;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
int p=0;
while(a[i]>0){
num[p]+=a[i]%2;
a[i]/=2;
p++;
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=15;j++){
if(num[j]>0){
num[j]--;
b[i] += (1<<j);
}
}
}
__int128 sum=0;
for(int i=1;i<=n;i++){
sum+=b[i];
}
__int128 d1=0,d2=n*n*n;
for(int i=1;i<=n;i++){
__int128 t1=b[i]*n-sum;
d1+=t1*t1;
}
__int128 g1=gcd(d1,d2);
d1/=g1; d2/=g1;
if(d1==0){
d2=1;
}
print(d1);
cout<<"/";
print(d2);
return 0;
}
基本题
K题 NIO’s Sword / NIO的剑
题目大意
玩家初始有一把攻击力为
0
0
0 的剑,需要依次击杀
𝑛
(
1
≤
n
≤
1
0
6
)
𝑛 (1\le n\le 10^{6})
n(1≤n≤106) 个敌人,仅当攻击力模
𝑛
𝑛
n 与
𝑖
𝑖
i 同余才能击杀第
𝑖
𝑖
i 个敌人。
玩家可以升级剑,每次升级相当于在当前攻击力后面添加一个数字,问最少需要几次升级。
考察内容
贪心,暴力
分析
1
≤
n
≤
1
0
6
1\le n\le 10^{6}
1≤n≤106,所以最多升级7次(在后面加7个数字)肯定可以杀掉一个敌人。对于每一个敌人,直接枚举升级的次数即可。
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll po[10];
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
po[0]=1;
for(int i=1;i<=7;i++){
po[i]=po[i-1]*10; // 预处理10的幂次
}
cin>>n;
ll ans=0;
ll cnt=0; // 当前武器的数值
for(int i=1;i<=n;i++){ // 枚举每个怪物
if(cnt%n==i%n)continue; // 不用升级就能击败
for(int j=1;j<=7;j++){ // 最多升级7次肯定可以找到方案打败怪物
cnt*=10;
cnt%=n;
if(po[j]>=n){
cnt=i%n;
ans+=j;
break;
}
// po[j]<n
if(cnt<=i%n && cnt+po[j]-1>=i%n){ // 可以击败
cnt=i%n;
ans+=j;
break;
}
else if(cnt<=i%n+n && cnt+po[j]-1>=i%n+n){ // 可以击败
cnt=i%n;
ans+=j;
break;
}
}
}
cout<<ans<<endl;
return 0;
}
/*
输入:
1000000
输出:
5877904
*/
D题 Jobs (Easy Version) / 工作(简单版)
题目大意
有
𝑛
𝑛
n 个公司,第
𝑖
𝑖
i 个公司有
𝑚
𝑖
𝑚_𝑖
mi 个工作,每个工作对三个能力分别有数值要求,必须三个能力都达标才能胜任这份工作。一个人只要能胜任一个公司的任意一份工作就可以去这个公司工作。
询问
𝑞
𝑞
q 次,每次询问随机给出一个人三个能力的数值,求这个人可以去多少个公司工作。
强制在线。
𝑛
≤
10
,
∑
𝑚
𝑖
≤
1
0
6
,
𝑞
≤
2
×
1
0
6
𝑛≤10,∑𝑚_𝑖≤10^6 , 𝑞≤2×10^6
n≤10,∑mi≤106,q≤2×106 ,能力的数值不超过400
考察内容
贪心,排序,暴力,复杂度优化
分析
解法不唯一。
对每一家单位的所有工作先按照三个能力的总和sum从小到大排序,然后暴力判断即可。
一个人的三个能力不到sum时,后面的工作肯定也无法胜任,可以直接break掉。
快读优化一下。
#include<bits/stdc++.h>
#include<random>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+5;
const int maxq=2e6+5;
const ll mod=998244353;
ll read(ll &n){
char ch=' '; ll q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
ll n,q,seed;
ll m[N];
ll po[maxq];
struct node{
ll q1,q2,q3;
ll sumq; // q1+q2+q3
}a[11][N];
ll solve(ll IQ,ll EQ,ll AQ){ // 计算能被多少家公司录用
ll sum0=IQ+EQ+AQ; // 三个商之和
ll num=0;
for(int i=1;i<=n;i++){ // 判断是否能被第i家公司录用
int F=0;
// 枚举m[i]份工作
for(int j=1;j<=m[i];j++){
if(sum0<a[i][j].sumq){ // 三个商之和不够,这家公司不行
break;
}
// sum0>=a[i][j].sumq时
if(IQ>=a[i][j].q1 && EQ>=a[i][j].q2 && AQ>=a[i][j].q3){
F=1; // 可以胜任
break;
}
}
if(F)num++;
}
return num;
}
bool cmp(node x,node y){
return x.sumq<y.sumq; // "三个和"更小的工作放前面
}
signed main(){ // wa..
read(n); read(q);
for(int i=1;i<=n;i++){
read(m[i]);
for(int j=1;j<=m[i];j++){
read(a[i][j].q1); read(a[i][j].q2); read(a[i][j].q3);
a[i][j].sumq=a[i][j].q1+a[i][j].q2+a[i][j].q3;
}
sort(a[i]+1,a[i]+m[i]+1,cmp); // 排序
}
read(seed); // 读入随机种子seed
std::mt19937 rng(seed);
std::uniform_int_distribution<> u(1,400);
ll seedmod=seed%mod; // 防止爆long long
po[0]=1;
for(int i=1;i<=q;i++){
po[i]=po[i-1]*seedmod%mod; // 预处理seed的幂次
}
ll ans=0;
int lastans=0;
for (int i=1;i<=q;i++){ // 询问q次
int IQ=(u(rng)^lastans)%400+1; // The IQ of the i-th friend
int EQ=(u(rng)^lastans)%400+1; // The EQ of the i-th friend
int AQ=(u(rng)^lastans)%400+1; // The AQ of the i-th friend
lastans=solve(IQ,EQ,AQ)%mod; // The answer to the i-th friend
ans+=lastans*po[q-i]%mod;
ans%=mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}
/*
3 5
3 2 1 2 400 400 400 1 1 2
3 1 3 2 2 3 1 2 3 3
2 2 3 1 3 2 1
998244353
*/
H题 Wall Builder II / 墙体建造师II
题目大意
给
n
(
n
≤
100
)
n (n \leq 100)
n(n≤100) 个
1
∗
1
1*1
1∗1 的矩形,
n
−
1
n-1
n−1 个
1
∗
2
1*2
1∗2 的矩形,
n
−
2
n-2
n−2 个
1
∗
3
1*3
1∗3 的矩形…,
1
1
1 个
1
∗
n
1*n
1∗n 的矩形,把这些矩形拼成一个大矩形,求大矩形的最小周长。
考察内容
贪心,构造
分析
n
n
n 已经给定了,所以总面积可以直接枚举计算出来。
容易发现,给了这么多
1
∗
n
1*n
1∗n 的小块的情况下,把这个总面积摆成任意一个
x
∗
y
x*y
x∗y 的大矩形,都是能用小块拼出来的。直接枚举总面积的因数,选最接近正方形的那个轮廓即可。
摆的时候贪心一下,一层层摆,每层优先用剩下来的小块里面,能放得下的最长的那个即可。
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=105;
ll n;
ll num[N]; // num[i]记录1*i的积木数量
int p=0;
struct node{
ll x1,y1,x2,y2;
}a[N*N];
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t; cin>>t;
while(t--){
p=0; // 初始化p
cin>>n; // 最小周长
ll sum=0; // 总面积, sum<=171700
for(int i=1;i<=n;i++){
sum+=i*(n+1-i);
}
for(int i=1;i<=n;i++){
num[i]=n+1-i;
}
ll len1=(n+1)*2; // 记录最小周长
ll h=sqrt(sum);
for(int i=h;i>=1;i--){ // 枚举sum的因数
if(sum%i==0){ // 可以整除
ll xall=sum/i;
ll yall=i;
for(int i=1;i<=yall;i++){
int j=0;
while(j<xall && p<=n*(n+1)/2){
for(int k=min(n,xall-j);k>=1;k--){ // 贪心,尽量用长的积木
if(num[k]==0)continue;
// num[k]>0时
num[k]--;
a[++p].x1=j;
a[p].y1=i-1;
a[p].x2=j+k;
a[p].y2=i;
j+=k;
break;
}
}
// j>=xall时, 接着看上一行
}
len1=(i+sum/i)*2; // 更新len1
break;
}
}
cout<<len1<<endl;
for(int i=1;i<=p;i++){
cout<<a[i].x1<<' '<<a[i].y1<<' '<<a[i].x2<<' '<<a[i].y2<<endl;
}
}
return 0;
}
/*
1
4
*/
进阶题
A题 Task Computing / 任务计算
题目大意
从
n
n
n 个任务中选
m
m
m 个
(
𝑎
1
,
𝑎
2
,
.
.
.
.
.
.
,
𝑎
𝑚
)
(𝑎_1,𝑎_2,......,𝑎_𝑚)
(a1,a2,......,am) 出来并任意排序,收益是
∑
𝑖
=
1
𝑚
𝑤
𝑎
𝑖
∏
𝑗
𝑖
−
1
𝑝
𝑎
𝑗
∑_{𝑖=1}^𝑚 {𝑤_{𝑎_𝑖 }}∏_𝑗^{𝑖−1}{𝑝_{𝑎_𝑗}}
∑i=1mwai∏ji−1paj ,求最大收益。
1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ min ( n , 20 ) 1\le n\le 10^5, 1\le m \le \min(n,20) 1≤n≤105,1≤m≤min(n,20)
考察内容
动态规划,排序
分析
先对整个数组,用类似 “国王游戏” 里面用到的方法进行一遍排序。
然后上动态规划。
状态:
f
[
i
]
f[i]
f[i] 表示选上了
i
i
i 个任务时的最大收益。
边界:
f
[
0
]
=
0
f[0]=0
f[0]=0 ,不选的时候没有收益。
转移:
f
[
j
]
∗
a
[
i
]
.
p
+
a
[
i
]
.
w
>
f
[
j
+
1
]
f[j]*a[i].p+a[i].w>f[j+1]
f[j]∗a[i].p+a[i].w>f[j+1] 时,
f
[
j
+
1
]
=
f
[
j
]
∗
a
[
i
]
.
p
+
a
[
i
]
.
w
f[j+1]=f[j]*a[i].p+a[i].w
f[j+1]=f[j]∗a[i].p+a[i].w
倒着进行转移,取消后效性。
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;
ll n,m;
struct node{
ll w,q;
}a[N];
bool cmp2(node x,node y){ // 类似"国王游戏"中的排序
return x.w*(10000-y.q)>y.w*(10000-x.q); //
}
double f[25];
int main(){ // "国王游戏" + dp
cin>>n>>m; // 选m个,m<=20
for(int i=1;i<=n;i++){
cin>>a[i].w;
}
for(int i=1;i<=n;i++){
cin>>a[i].q;
}
sort(a+1,a+n+1,cmp2); // 排序
f[0]=0;
for(int i=1;i<=m;i++){
f[i]=-1e9-10;
}
for(int i=n;i>0;i--){
for(int j=m-1;j>=0;j--){
f[j+1]=max(f[j+1], f[j]*a[i].q/10000+a[i].w);
}
}
printf("%.7f\n",f[m]);
return 0;
}
/*
输入:
3 2
1000000000 1000000000 999999999
8000 8000 12000
输出:
2199999999
*/
L题 Black Hole / 黑洞
题目大意
求边长为
𝑎
𝑎
a 的凸正
𝑛
𝑛
n 面体收缩
𝑘
𝑘
k 次后的面数和边长。
一次收缩定义为作该几何体所有面中心点的三维凸包*。
𝑇
𝑇
T 组询问。
1
≤
𝑛
≤
200
,
1
≤
𝑎
≤
1000
,
1
≤
𝑘
≤
20
,
1
≤
𝑇
≤
100
1≤𝑛≤200,1≤𝑎≤1000,1≤𝑘≤20,1≤𝑇≤100
1≤n≤200,1≤a≤1000,1≤k≤20,1≤T≤100
*三维凸包: 给定一堆三维空间中的点,包含它们的最小凸多面体称为这些点的三维凸包。
考察内容
数学知识,计算几何
分析
凸正多面体只有5种,面数为:4,6,8,12,20
收缩的定义下:
正四面体→正四面体,
正八面体→正六面体,
正六面体→正八面体,
正十二面体→正二十面体,
正二十面体→正十二面体。
用立体几何知识对五种情况求出两个面的连心线长度即可。
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=205;
double f[N],h;
int to[N];
int main(){ // 数学知识
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 预处理答案
to[4]=4,to[6]=8,to[8]=6,to[12]=20,to[20]=12;
f[4]=1.0/3;
f[6]=sqrt(0.5);
f[8]=sqrt(2.0/9);
h=sqrt(5);
f[12]=sqrt(2/(35-15*h));
f[20]=(1+h)/6;
int t; scanf("%d",&t);
int x,k;
double a;
while(t--){
scanf("%d%lf%d",&x,&a,&k);
if(!to[x]){ // 不属于5种情况之一
printf("impossible\n");
continue;
}
printf("possible ");
while(k--){
a*=f[x];
x=to[x];
}
printf("%d %.11lf\n",x,a);
}
return 0;
}