ZR 2025 模拟赛集
NOIP 10 连测
Day 1
T1
题意:
大概就是说,给你 \(n\) 个数 \(a_1,a_2,a_3 \dots a_n\) ,你可以从中选出若干个数,问能使选出的数平均值为 \(A\) 的方案数。
Solution
根据平均数公式:
(选出 \(m\) 个数,并存在 \(b\) 数组中)
我们可以枚举 \(A\) 的倍数 \(k*A\),并预处理出来选 \(k\) 个数总和为 \(k*A\) 的方案。
显然是个简单 DP。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,A;
int b[103];
int a[103];
int ans;
int sum[103];
int dp[2503][51];
void work(){
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=sum[i];j>=a[i];j--){
for(int k=i;k>=1;k--){
dp[j][k]+=dp[j-a[i]][k-1];
}
}
}
}
signed main(){
cin>>n>>A;
sum[0]=0;
for(int i=1;i<=n;i++) cin>>a[i],sum[i]=a[i]+sum[i-1];
work();
for(int i=A;i<=n*A;i+=A){
ans+=dp[i][i/A];
}
cout<<ans;
}
T2
题意:
给你一张无向图,边有边权,经过一条边的代价为 \(\min(之前经过的边的边权最小值,与这条边的边权值)\)
求从 \(1\) 至 \(n\) 的最小代价。
Solution
首先贪心的想一下,容易想到肯定是先走边权小的边。
所以我们可以先 \(Floyd\) 处理出来两点间的最短路。
然后从最大的边开始枚举,表示以它为当前最小边权,更新 \(1~n\) 的最小代价。
\(Q:\) 为什么要从最大边枚举?
\(A:\) 因为枚举时是设当前枚举边为最小边权,所以之前的边需要比它大。
Code
#include<bits/stdc++.h>
using namespace std;
const int M=125001;
const int N=502;
int n,m;
struct node{
int u,v,w;
}z[M];
int dis[N][N];
bool cmp(node x,node y){
return x.w>y.w;
}
int dp[N];
signed main(){
cin>>n>>m;
memset(dis,0x3f3f3f3f,sizeof dis);
for(int i=1;i<=m;i++){
cin>>z[i].u>>z[i].v>>z[i].w;
dis[z[i].u][z[i].v]=1;
dis[z[i].v][z[i].u]=1;
}
for(int i=1;i<=n;i++) dis[i][i]=0;
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
memset(dp,0x3f3f3f3f,sizeof dp);
sort(z+1,z+1+m,cmp);
dp[1]=0;
for(int i=1;i<=m;i++){
int x=z[i].u,y=z[i].v,w=z[i].w;
if(dp[x]>dp[y]) swap(x,y);
for(int j=1;j<=n;j++){
dp[j]=min(dp[j],dp[x]+(dis[y][j]+1)*w);
}
}
cout<<dp[n];
}
Day 2
T1
题意:
这里有个数组 \(a\),\(a_1=1,a_2=2,a_3=3 \dots a_n=n\)
定义数组的价值为 \((a_1 \otimes 1)+(a_2 \otimes 2)+(a_3 \otimes 3)+\dots+(a_n \otimes n)\)
你可以交换 \(k\) 次,问最后数组价值最大是多少。
Solution
由于是 \(\otimes\) 所以尽量让 \(a_i\) 与 \(i\) 的二进制不同。
考虑贪心。
发现 \(x\) 和 \(2^m-x-1\) 匹配时最优。
Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int T,n,k;
int ans;
int PW[1003]={0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824};//30
void solve(int l,int r){
if(l==r||r<l||!k) return ;
for(int i=31;i>=1;i--){
if(PW[i]<=r){
int p=min((PW[i]-1),(r-PW[i]+1));
p=min(p,k);
ans+=(p*2*(PW[i+1]-1));
k-=p;
solve(l,PW[i]-1-min((PW[i]-1),(r-PW[i]+1)));
return ;
}
}
}
void work(){
ans=0;
cin>>n>>k;
solve(1,n);
cout<<ans<<endl;
}
signed main(){
cin>>T;
while(T--) work();
}
T2
题意
就是有个数组 \(a\) ,你可以将 \(a_i\) \(+1\) 或 \(-1\) ,代价为 \(1\)。
最后如果有 \(a_i\) = \(a_j+1\) ,那么代价再增加 \(1\)。
Solution
容易想到将同值的 \(a_i\) 一起操作。
枚举 \(min_a\) 至 \(max_a\),我们将 \(a_i\) 统一转移到差值为 \(1\) 的 \(a_j\) 上。
Code
#include<bits/stdc++.h>
using namespace std;
int n;
int a;
int ans;
int cnt[100003];
int maxx;
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a,cnt[a]++,maxx=max(maxx,a);
for(int i=1;i<=maxx;i++){
int k=min(cnt[i-1],cnt[i]);
ans+=k;
cnt[i-1]-=k;
cnt[i]-=k;
}
cout<<ans;
}
Day 3
T1
题意
给你一张有向图,边有边权,点有一定限制,只有在 \([b+kl,e+kl] ,k \in N\) ,求 \(1\) 至 \(n\) 的最短路。
Solution
单源最短路板子
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+20;
const int M=1e6+20;
const int inf=1e18;
int n,m;
struct point{
int b,e,l;
}p[N];
struct street{
int y,w;
};
int u,v2,w;
vector<street> v[N];
void init(){
cin>>n>>m;
for(int i=2;i<n;i++){
cin>>p[i].b>>p[i].e>>p[i].l;
}
for(int i=1;i<=m;i++){
cin>>u>>v2>>w;
v[u].push_back((street){v2,w});
}
}
bool vis[N];
int dis[N];
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > q;
int Len(int x,int t){
if(x==1||x==n) return 0;
int k=t%p[x].l;
if(k>=p[x].b&&k<=p[x].e) return 0;
else{
if(k<p[x].b) return p[x].b-k;
if(k>p[x].e) return p[x].l+p[x].b-k;
}
}
void djie(){
for(int i=1;i<=n;i++){
dis[i]=inf;
vis[i]=0;
}
dis[1]=0;
q.push({0,1});
while(!q.empty()){
int x=q.top().second;
// cout<<"x"<<x<<endl;
q.pop();
if(vis[x]) continue;
vis[x]=1;
for(auto _:v[x]){
int y=_.y;
int w=_.w;
//cout<<"y"<<y<<" "<<Len(y,dis[x]+w)<<endl;
if(dis[y]>dis[x]+w+Len(y,dis[x]+w)){
dis[y]=dis[x]+w+Len(y,dis[x]+w);
q.push({dis[y],y});
}
}
}
}
signed main(){
freopen("rgba.in","r",stdin);
freopen("rgba.out","w",stdout);
init();
djie();
//cout<<Len(3,100)<<"\n";
cout<<dis[n];
}
T2
题意:
有 \(2*n\) 个数,每个数小于等于 \(n\),求一个区间使得可划分为两个总和相等的集合。
Solution:
对于每个数,都赋一个权 \(1\) 或 \(-1\) ,这样可以将前缀和控制在 \([-n+1,n]\) 中,根据鸽巢原理,肯定有满足的。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[4000004];
int d;
int k;
int vis[4000004];
signed main(){
cin>>n;
n*=2;
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++){
cin>>k;
if(d>0){
d-=k;
}
else{
d+=k;
vis[i]=1;
}
if(d==0||a[d+n]){
cout<<a[d+n]+1<<" "<<i<<"\n";
for(int j=a[d+n]+1;j<=i;j++) cout<<vis[j];
return 0;
}
a[d+n]=i;
}
}
Day 4
T1
题意:
给你一个数组 \(a\) ,可任意选出 \(a_i\) 和 \(a_j\) 将 \(a_i - a_j\) 的绝对值加入 \(a\) 数组(如果之前有这个数就不加)问最后最多会有几个数。
Solution
容易发现,最后的 \(a\) 数组从小到大排后形式为:
\(a_1\) ,\(2*a_1\) , \(3*a_1\) , $4*a_1 $ \(\cdots\)
可以发现 \(a_1\) 其实就是给出的 \(a\) 数组的最大公约数。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+20;
int gcd(int x,int y){
if(!y) return x;
else return gcd(y,x%y);
}
int n;
int T;
int a[N];
void solve(){
cin>>n;
int maxx=-1;
for(int i=1;i<=n;i++) cin>>a[i],maxx=max(maxx,a[i]);
if(n==1){
cout<<"1\n";
return ;
}
int GCD=gcd(a[1],a[2]);
for(int i=2;i<n;i++) GCD=gcd(GCD,a[i+1]);
cout<<(maxx/GCD)<<"\n";
}
signed main(){
cin>>T;
while(T--) solve();
}
CSP 7 连测
Day 1
T1
题意:
有 \(n\) 个人,每个人有个值 \(a\)。
给出每个人认识的人。
对于第 \(i\) 个人,他若认识 \(m\) 个人,其中 \(k\) 个人的值比他大。
如果 \(k\ge \frac{m}{2}\) 那么这个人就会放弃。
问有多少人会放弃。
Solution
暴力即可
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int c,a,b;
int q[20000004];
int x;
int sum;
int ans;
int vis[20000004];
int cnt;
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>q[i];
for(int i=1;i<=n;i++){
cin>>c>>a>>b;
sum=0,cnt=0;
x=b;
for(int j=1;j<=c;j++){
if(vis[x]!=i&&x!=i) vis[x]=i,cnt++;
else{
x=(x*a+b)%n+1;
continue;
}
sum+=(q[x]>q[i]);
x=(x*a+b)%n+1;
}
ans+=(sum*2>=cnt&&(sum!=0));
}
cout<<ans;
}
T2
题意:
给你个数,在后面加数,问最小可得的质数
Solution:
由于 \(1000\) 个数中必有质数,所以暴力即可。
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int q;
const int inf=100000000;
int prime[10000005];
bool vis[100000003];
int tot;
ll ans[1000003];
void XX(int n){
memset(vis,1,sizeof vis);
vis[1]=0;
for(int i=2;i<=n;i++){
if(vis[i]){
prime[++tot]=i;
}
for(int j=1;j<=tot&&prime[j]*i<=n;j++){
vis[prime[j]*i]=0;
if(i%prime[j]==0) break;
}
}
}
inline bool check(ll x){
if(x<=inf) return vis[x];
for(int i=2;i*i<=x;i++){
if(x%i==0) return 0;
}
return 1;
}
void work(int x){
if(check(x)){
ans[x]=x;
return ;
}
bool flag=0;
for(int i=1;i<=9;i+=2){
ll y=x*10+i;
if(check(y)){
ans[x]=y;
return ;
}
}
for(int i=0;i<=9;i++){
for(int j=1;j<=9;j+=2){
ll z=x*100+i*10+j;
if(check(z)){
ans[x]=z;
return ;
}
}
}
for(int i=0;i<=9;i++){
for(int j=0;j<=9;j++){
for(int k=1;k<=9;k+=2){
ll p=x*1000+i*100+j*10+k;
if(check(p)){
ans[x]=p;
return ;
}
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
XX(inf);
for(int i=1;i<=1000000;i++){
work(i);
}
cin>>q;
while(q--){
int x;
cin>>x;
cout<<ans[x]<<"\n";
}
}
T3
题意:
有个 \(a\) 数组,\(a_1=1,a_2=2 \dots a_n=n\)
\(n\) 个人,每个人对应着 \(a\) 数组中的值。
有 \(m\) 个要求。
每个要求给 \(p\) 和 \(x\),表示下标大于 \(p\) 的人中,\(a\) 的值大于第 \(p\) 个人的人恰好有 \(x\) 个。
Solution
可以发现,对于一个要求,符合的数是个区间,而每次贪心取最大的即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10;
int T;
int n,m;
struct node{
int l,r,sum;
}z[4*N];
struct cjx{
int p,x;
}q[N];
int X[N];
void build(int l,int r,int x){
z[x].l=l;
z[x].r=r;
if(l==r){
z[x].sum=1;
return ;
}
int mid=l+r>>1;
build(l,mid,x<<1);
build(mid+1,r,x<<1|1);
z[x].sum=z[x<<1].sum+z[x<<1|1].sum;
}
int query(int k,int x){
if(z[x].l==z[x].r) return z[x].l;
if(k>z[x<<1].sum) return query(k-z[x<<1].sum,x<<1|1);
else return query(k,x<<1);
}
void del(int k,int x){
if(z[x].l==z[x].r){
z[x].sum=0;
return ;
}
int mid=z[x].l+z[x].r>>1;
if(k<=mid) del(k,x<<1);
else del(k,x<<1|1);
z[x].sum=z[x<<1].sum+z[x<<1|1].sum;
}
int ans[N];
signed main(){
int o=0;
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cin>>n>>m;
o++;
build(1,n,1);
bool flag=0;
for(int i=1;i<=n;i++) X[i]=-1;
for(int i=1;i<=m;i++){
cin>>q[i].p>>q[i].x;
X[q[i].p]=q[i].x;
if(n-q[i].p-q[i].x+1<=0){
flag=1;
}
}
if(flag){
cout<<"-1\n";
continue;
}
for(int i=1;i<=n;i++){
if(X[i]==-1){
if(n-i+1<=0){
flag=1;
break;
}
ans[i]=query(n-i+1,1);
}
else{
if(n-i-X[i]+1<=0){
flag=1;
break;
}
ans[i]=query(n-i-X[i]+1,1);
}
del(ans[i],1);
}
if(flag){
cout<<"-1\n";
continue;
}
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
cout<<"\n";
}
}
Day 2
T2
题意:
给你 \(n\) 个数,你需要将它们划分为 \(3\) 个集合,满足第 \(1\) 集合的最大值小于等于第 \(2\) 个集合的长度,第 \(2\) 个集合的最大值小于等于第 \(3\) 个集合的长度,第 \(3\) 个集合的最大值小于等于第 \(1\) 个集合的长度。
Solution:
贪心,但要注意把情况算全。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int T;
int n;
int col[N];
struct node{
int id,x;
}a[N];
bool cmp(node x,node y){
return x.x>y.x;
}
signed main(){
ios::sync_with_stdio(false);
//freopen("performance.in","r",stdin);
// freopen("performance.out","w",stdout);
cin>>T;
while(T--){
cin>>n;
//cout<<n<<endl;
for(int i=1;i<=n;i++) {
cin>>a[i].x;
a[i].id=i;
}
sort(a+1,a+1+n,cmp);
bool flag=0;
// for(int i=1;i<=n;i++) cout<<a[i]<<" ";
// cout<<endl;
for(int i=1;i<=n-2;i++){
int l=n-a[1].x+1,r=n;
if(l-i<=1) continue;
// cout<<i<<" "<<(l-1-(i+1)+1)<<" "<<n-l+1<<" "<<a[i+1]<<" "<<a[l]<<endl;
// cout<<i<<" "<<l<<" "<<r<<endl;
//cout<<(a[l]<=n-r)<<" I i " <<i<<" a[l] "<<a[l]<<" r "<<r<<" n-r "<<n-r<<" r-l+1 "<<r-l+1<<" a[i] "<<a[i]<<" l "<<l<<" a[r+1] "<<a[r+1]<<endl;
// cout<<(a[l]>=(l-1-(i+1)+1))<<" "<<((a[i+1]>=i))<<"\n";
if(a[l].x<=(l-1-(i+1)+1)&&(a[i+1].x<=i)){
// cout<<a[l]<<" "<<r<<" "<<n-r<<" "<<r-l+1<<" "<<a[i]<<" "<<l<<" "<<a[r+1]<<endl;
flag=1;
cout<<"YES\n";
for(int j=1;j<=i;j++) col[a[j].id]=1;
for(int j=i+1;j<=l-1;j++) col[a[j].id]=3;
for(int j=l;j<=r;j++) col[a[j].id]=2;
for(int j=1;j<=n;j++) cout<<col[j]<<" ";
cout<<"\n";
break;
}
l=i+1,r=l+a[1].x-1;
if(r>=n||l>=n) continue;
if(a[r+1].x<=i&&a[l].x<=(n-(r+1)+1)){
flag=1;
cout<<"YES\n";
for(int j=1;j<=i;j++) col[a[j].id]=1;
for(int j=l;j<=r;j++) col[a[j].id]=2;
for(int j=r+1;j<=n;j++) col[a[j].id]=3;
for(int j=1;j<=n;j++) cout<<col[j]<<" ";
cout<<"\n";
break;
}
}
if(flag) continue;
cout<<"NO\n";
}
}
T4
题意:
有一个 \(n\) 个点的树,每个点有点权。有 \(q\) 次询问,每次给定一个 \(x\),问是否存在两个祖孙关系的节点 \(u\) , \(v\) 的路径上点权和为 \(x\)。
Soltuion:
bitset
Code:
#include<bits/stdc++.h>
using namespace std;
int n,q;
int f;
vector<int> v[1000003];
int a[2000004];
bitset<100050> Q,b[41];
int x[1000004];
bool vis[1000003];
int rt;
struct node{
int son,maxson,fa;
}z[1000004];
void dfs1(int x,int fa){
z[x].fa=fa;
z[x].maxson=0;
z[x].son=1;
for(auto y:v[x]){
if(y!=fa){
dfs1(y,x);
z[x].son+=z[y].son;
if(z[y].son>z[z[x].maxson].son){
z[x].maxson=y;
z[z[x].maxson].son=z[y].son;
}
}
}
}
void dfs2(int x,int now){
b[now].set(0,1);
b[now]<<=a[x];
Q^=(Q&b[now]);
for(auto y:v[x]){
if(y!=z[x].fa&&y!=z[x].maxson){
b[now+1]=b[now];
dfs2(y,now+1);
}
}
if(z[x].maxson) dfs2(z[x].maxson,now);
}
signed main(){
cin>>n>>q;
int rt;
for(int i=1;i<=n;i++){
cin>>f;
vis[i]=1;
if(!f) rt=i;
else v[f].push_back(i);
}
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=q;i++){
cin>>x[i];
Q.set(x[i],1);
}
dfs1(rt,0);
dfs2(rt,0);
for(int i=1;i<=q;i++){
if(Q[x[i]]==1) cout<<"NO\n";
else cout<<"YES\n";
}
}
Day 3
T1
唐题
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,m;
int K;
const int mod=1e9+7;
int ans;
int mp[10];
int xh[4]={0,1,4,2};
void work(int x){
if(x==1||x==2||x==4||!m){
a=x;
return ;
}
ans=(ans+x)%mod;
if(x%2==0) --m,work(x/2);
else --m,work(3*x+1);
}
signed main(){
cin>>a>>m;
work(a);
int k=m/3;
ans=(ans+(k*7)%mod)%mod;
m%=3;
mp[1]=1,mp[4]=2,mp[2]=3;
if(m){
int x=mp[a];
while(m){
ans=(ans+xh[x])%mod;
if(x==3) x=1;
x++,m--;
}
}
cout<<ans;
}