【考试题解】多校A层冲刺NOIP2024模拟赛18

A. 选彩笔(rgb)

题目内容

N 支彩笔,每支彩笔有 Ri,Gi,Bi 三个属性。定义两只彩笔 i,j 的 Difference 值为 max(|RiRj,GiGj,BiBj|)。定义一套彩笔的 Colorfulness 值为选中彩笔中 Difference 的最大值。求取出 K 支笔组成一套的 Colorfulness 的最小值。Ri,Gi,Bi[0,255]K,N[2,105]

部分分

我不到啊。

正解

思路

观察到题面里鲜明的“最大值最小”,于是考虑二分答案。把每支笔都抽象为三维空间中的点,于是题目变为找一个正方体,包含至少 K 个点,求正方体最短边长。首先,有一个点在正方体其中一个顶点上的方案肯定不劣。于是check里枚举每一个点,分别把它作为正方体的八个顶点试一试。然后找一个正方体里有多少个点就是经典问题了。由于我们是实时询问,所以 O(nlog2n) 的三维偏序不是很行得通。注意到至于很小,所以直接三维前缀和秒掉。复杂度 O(w3+nlogw)。注意:如果只钦定它为三个值都取到 max 的顶点,可以过掉所有的大小样例,但是会挂在下面的数据:

输入:

2 2
1 2 2
2 1 1

输出:

1

锅了的会输出2

代码

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b,u[100001],v[100001],w[100001],col[262][262][262],pre[262][262][262];
il bool check(int x)
{
for(ri i=1;i<=a;i++)
{
ri x1=max(1,u[i]-x),y1=max(1,v[i]-x),z1=max(1,w[i]-x);
ri x2=u[i],y2=v[i],z2=w[i];
ri rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=w[i],z2=min(256,w[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=max(1,w[i]-x),z2=w[i];
y1=v[i],y2=min(256,v[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=w[i],z2=min(256,w[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=max(1,w[i]-x),z2=w[i];
y1=max(1,v[i]-x),y2=v[i];
x1=u[i],x2=min(256,u[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=w[i],z2=min(256,w[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=max(1,w[i]-x),z2=w[i];
y1=v[i],y2=min(256,v[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
z1=w[i],z2=min(256,w[i]+x);
rn=pre[x2][y2][z2];
rn-=pre[x1-1][y2][z2]+pre[x2][y1-1][z2]+pre[x2][y2][z1-1];
rn+=pre[x1-1][y1-1][z2]+pre[x1-1][y2][z1-1]+pre[x2][y1-1][z1-1];
rn-=pre[x1-1][y1-1][z1-1];
if(rn>=b)
{
return true;
}
}
return false;
}
int main()
{
freopen("rgb.in","r",stdin);
freopen("rgb.out","w",stdout);
scanf("%d%d",&a,&b);
for(ri i=1;i<=a;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
u[i]++;
v[i]++;
w[i]++;
col[u[i]][v[i]][w[i]]++;
}
for(ri i=1;i<=256;i++)
{
for(ri j=1;j<=256;j++)
{
for(ri k=1;k<=256;k++)
{
ri rn=col[i][j][k];
rn+=pre[i-1][j][k]+pre[i][j-1][k]+pre[i][j][k-1];
rn-=pre[i-1][j-1][k]+pre[i-1][j][k-1]+pre[i][j-1][k-1];
rn+=pre[i-1][j-1][k-1];
pre[i][j][k]=rn;
}
}
}
ri m=0,n=255;
while(n!=m)
{
ri l=(m+n)>>1;
if(check(l))
{
n=l;
}
else
{
m=l+1;
}
}
printf("%d",m);
return 0;
}

B. 兵蚁排序(sort)

题目内容

给你两个序列 A,B,保证 A,B 中任意数出现的次数相同。构造方案把 A 转化为 B,无解输出-1。多测,T 组测试数据,T10,n1000,Ai,Bi[1,n]

部分分

75pts

暴搜,枚举每次的交换位置,使用哈希来进行去重+剪枝,然后跑的飞快。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b,c[1001],d[1001],ans;
const int base=11491;
vector<int>vec;
deque<pair<int,int>>que;
map<pair<unsigned long long,int>,int>mp;
unsigned long long mdd;
il unsigned long long _hash(const vector<int> &x)
{
register unsigned long long rn=0;
for(ri i:x)
{
rn=rn*base+i;
}
return rn;
}
bool dfs(int x)
{
if(x>ans)
{
return false;
}
register unsigned long long re=_hash(vec);
if(re==mdd)
{
puts("0");
printf("%d\n",x-1);
while(!que.empty())
{
printf("%d %d\n",que.front().first,que.front().second);
que.pop_front();
}
return true;
}
if(mp[{re,a}]&&mp[{re,a}]<=x)
{
return false;
}
mp[{re,a}]=x;
vector<int>now(vec);
for(ri i=0;i<vec.size()-1;i++)
{
for(ri j=i+1;j<vec.size();j++)
{
sort(vec.begin()+i,vec.begin()+j+1);
que.push_back({i+1,j+1});
if(dfs(x+1))
{
return true;
}
que.pop_back();
vec=now;
}
}
return false;
}
int main()
{
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
scanf("%d",&a);
while(a--)
{
scanf("%d",&b);
vec.clear();
for(ri i=1;i<=b;i++)
{
scanf("%d",&c[i]);
vec.push_back(c[i]);
}
mdd=0;
for(ri i=1;i<=b;i++)
{
scanf("%d",&d[i]);
mdd=mdd*base+d[i];
}
ans=b*b;
if(!dfs(1))
{
puts("-1");
}
}
return 0;
}

正解

思路

由于题目保证 A,B 中任意数出现的次数相同,所以我们对于 A 中任意数,可以找到其对应 B 中的位置。然后根据这个东西进行冒泡排序,使映射位置靠前的往前走,如果这么走不合法,那么就是无解。具体地,如果存在位置 i 使得它后面有数权值比它大,而映射位置比它小,那么无解;否则必能通过我们的冒泡排序找到可行解。由于是冒泡排序,所以复杂度 O(n2)

代码

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b,c[1001],d[1001],ans,to[1001];
queue<int>que[1001];
queue<pair<int,int>>ask;
il bool bubble()
{
for(ri i=1;i<b;i++)
{
for(ri j=1;j<b;j++)
{
if(to[j]>to[j+1])
{
if(c[j]<c[j+1])
{
while(!ask.empty())
{
ask.pop();
}
return false;
}
else
{
swap(c[j],c[j+1]);
swap(to[j],to[j+1]);
ask.push({j,j+1});
}
}
}
}
return true;
}
int main()
{
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
scanf("%d",&a);
while(a--)
{
scanf("%d",&b);
for(ri i=1;i<=b;i++)
{
scanf("%d",&c[i]);
que[c[i]].push(i);
}
for(ri i=1;i<=b;i++)
{
scanf("%d",&d[i]);
to[que[d[i]].front()]=i;
que[d[i]].pop();
}
ans=bubble();
if(ans)
{
puts("0");
printf("%d\n",ask.size());
while(!ask.empty())
{
printf("%d %d\n",ask.front().first,ask.front().second);
ask.pop();
}
}
else
{
puts("-1");
}
}
return 0;
}

C. 人口局DBA(dba)

题目内容

给你一个 m 进制数 n,长度为 L 且首位保证不为 0,求所有 <n 的 满足 S(x)=S(n)m 进制数 x 的个数,答案对 109+7 取模。S(n)nm 进制下各位数字之和。m,L[1,2000]

部分分

60pts

数位DP,但是赛时打锅了。

正解

思路

推式子。设 H(l,s) 表示长度为 l 总和为 s 的方案数。这里最困难的地方在于要保证任意数位的数要 <m。于是考虑容斥,发现这玩意儿推二项式反演好像很有前途,于是在 l,s 固定的情况下,设 F(x) 表示恰好有 i 个位置不满足 <m 条件的方案数,G(x) 表示至少有 i 个位置不满足 <m 条件的方案数,列出二项式反演的式子:

G(x)=i=xl(ix)F(i)

F(x)=i=xl(1)ix(ix)G(i)

我们先看 G(x) 的求解。先忽略限制,插板法可得方案数为 (s+l1l1)。这里可以这么理解:原来有 m 个球,现在加进去 n1 个球,然后再随便选 n1 个球染色,将原序列分为 n 部分。选上 x 个位置 m,就先让这 x 个位置值为 m,然后剩下的数,总和为 sxm 随便分给所有位置。方案数就是 (sxm+l1l1)。注意二项式反演的一式,我们这里 G(x) 并不是钦定的方案数,而是考虑了被选中位置的方案数。所以 G(x)=(lx)(sxm+l1l1)

然后反演求 F(x)

F(x)=i=xl(1)ix(ix)G(i)=i=xl(1)ix(ix)(li)(sim+l1l1)

H(l,s) 即为对应 l,s 下的 F(0) 值。

H(l,s)=F(0)=i=0l(1)i(i0)(li)(sim+l1l1)=i=0l(1)i(li)(sim+l1l1)

类比数位DP,设第 i 位所贡献的答案为 ansi,则 ansi=i=0c[i]1H(l1,si),然后推式子:

ansk=i=0c[k]1H(l1,si)=i=0c[k]1j=0l1(1)j(l1j)(sijm+l2l2)=j=0l1(1)j(l1j)i=0c[k]1(sijm+l2l2)

现在重点在于简化后面的那个 项。于是充分发扬人类智慧,把这个玩意儿放杨辉三角上,发现这个东西是类似 C(a+bi,b) 的东西,也就是杨辉三角上同一列里连续的一段区间。

image

我们要求的是红色的 13,现在加进来一个 41+4=5,2+5=6,3+6=7,于是 74 就是我们想要求得答案。然后写成式子:

ansk=j=0l1(1)j(l1j)(sjm+l1l1)(sc[k]jm+l1l1)

然后直接求就可以了。注意特判组合数为 0 的情况。

代码

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b,c[2002];
const int mod=1e9+7;
long long jc[4000004],ny[4000004],sm,ans;
il long long qpow(long long x,long long y)
{
register long long rn=1;
while(y)
{
if(y&1)
{
rn=(rn*x)%mod;
}
x=(x*x)%mod;
y>>=1;
}
return rn;
}
il long long C(int x,int y)
{
if(x<y)
{
return 0;
}
return (((jc[x]*ny[x-y])%mod)*ny[y])%mod;
}
int main()
{
freopen("dba.in","r",stdin);
freopen("dba.out","w",stdout);
scanf("%d%d",&a,&b);
for(ri i=b;i>=1;i--)
{
scanf("%d",&c[i]);
sm+=c[i];
}
jc[0]=ny[0]=1;
for(ri i=1;i<=a*b;i++)
{
jc[i]=(jc[i-1]*i)%mod;
ny[i]=qpow(jc[i],mod-2);
}
for(ri i=b;i>=1;i--)
{
register long long rn=0;
for(ri j=0;j<i;j++)
{
ri op=(j&1)?-1:1;
register long long k=(C(i-1,j)*((C(sm-a*j+i-1,i-1)-C(sm-c[i]-a*j+i-1,i-1)+mod)%mod))%mod;
rn=(rn+op*k)%mod;
rn=(rn+mod)%mod;
}
ans=(ans+rn)%mod;
sm-=c[i];
}
printf("%lld",ans);
return 0;
}

D. 银行的源起(banking)

不会。

posted @   一位很会的教授er~  阅读(41)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示