2025/7/20模拟赛 (邓庭宇出题 )

2025/7/20模拟赛 \(\mathbf{} \begin{Bmatrix} \frac{{\Large TEST} }{{\color{Yellow}\Large Record} }\mathbf{} {No.11} \end{Bmatrix}\times{}\) NeeDna

看到比赛题编号是新的,感觉不对劲,原来是pku爷出的题啊

估分:140分 实际:130分

购物(shop)

题目描述

商店里有 \(n\) 个商品,\(i\) 号商品的标签如 \(w_i,d_i\),含义如下:

  • \(w_i\):购买 \(i\) 号商品需要支付 \(w_i\) 元,即 \(i\) 号商品的价格。

  • \(d_i\):一旦购买 \(i\) 号商品,你接下来购买的任何商品都将额外支付 \(d_i\) 元。

你现在只有 \(m\) 元的购物资金,所以你要制定一个最佳购物次序,使得买到的商品最多。

输出最多能买到的物品数量。

\(1\le n\le 3000,1\le m\le 10^{18},1\le w_i,d_i\le 10^9\)

正解:

我们首先发现一个最优方案下,你选择的所有商品的 \(d_i\) 值一定是从小到大排序的,所以我们先把商品按照 \(d_i\) 从小到大排序。

再看数据范围,我们估计是 \(O(n^2\log n)\) 或者 \(O(n^2)\) 的。发现有一个最大在题面里,我们考虑二分答案。

你会发现二分了之后我们就有了明确的天数,那么每个商品的贡献也就明显了,由于我们有\(O(n^2)\) 的时间给我们做计算,我们直接dp,\(f_{i,j}\),其中 \(i\) 表示第几位, \(j\) 表示选了几个数,很好转移。\(O(n^2\log n)\)

当然这道题也有 \(O(n^2)\) 做法。发现阻挠我们的实际上是不知道商品的 \(d_i\) 会被计算几次,我们倒着枚举其实就可以确定了,应为后面的数一定后选,所以时间是确定的(dp含义不变)。

\(O(n^2\log n)\) code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e3+10;
int n,m,vis[N],tim,dp[N][N];
struct node{int w,d;};
vector<node> g;
bool check(int mid){
   for(int i=0;i<=n;i++){
   	for(int j=1;j<=n;j++){
   		dp[i][j]=LONG_LONG_MAX;
   	}
   }
   for(int i=1;i<=n;i++){
   	for(int j=1;j<=min(mid,i);j++){
   		dp[i][j]=dp[i-1][j];
   		dp[i][j]=min(dp[i][j],dp[i-1][j-1]+g[i-1].d*(mid-j)+g[i-1].w);
   	}
   }	
   for(int i=mid;i<=n;i++){
   	if(dp[i][mid]<=m) return 1;
   }return 0;
}
bool cmp(node a,node b){
   return a.d<b.d;
}
signed main(){
   ios::sync_with_stdio(0);
   cin.tie(0),cout.tie(0);
   cin>>n>>m;
   for(int i=1,w,d;i<=n;i++){
   	cin>>w>>d;a[i]={w,d};
   	g.push_back({w,d});
   }
   sort(g.begin(),g.end(),cmp);	
   int l=0,r=n,ans;
   while(l<=r){
   	int mid=(l+r)>>1;
   	if(check(mid)) ans=mid,l=mid+1;
   	else r=mid-1;
   }
   cout<<ans;
   return 0;
}

\(O(n^2)\) code:

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=3005;
struct sd{ll w,d;}a[N];
ll f[N],m;
int n;
bool cmp(sd x,sd y){return x.d>y.d;}
int main(){
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++){scanf("%lld%lld",&a[i].w,&a[i].d);f[i]=2e18;}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
		for(ll j=n;j;j--)
			f[j]=min(f[j],f[j-1]+a[i].w+(j-1)*a[i].d);
	for(int i=n;i>=0;i--) if(f[i]<=m){printf("%d",i);return 0;}
}

序列(sequence)

题目描述

商店里有一个有 \(n\) 个商品的物品序列,你要从中挑选 \(k\) 个使得挑选的物品没有 \(m\) 连续的。即按编号从小到大排序后没有编号形如 \(i, i+1, \dots, i+m-1\) 的。求方案数。

输入格式

第一行包含三个整数 \(n\), \(m\), \(k\)

数据范围

  • 对于测试点 \(1\)\(n \leq 10\)
  • 对于测试点 \(2\)\(n \leq 300\)
  • 对于测试点 \(3-4\)\(n \leq 5000\)
  • 对于测试点 \(5-6\)\(k \leq 5000\)
  • 对于测试点 \(7-10\):无特殊限制

对于所有测试点:

  • \(2 \leq n \leq 10^{18}\)
  • \(2 \leq m \leq k \leq 10^6\)
  • \(k \leq n\)

提示

答案需要对 \(998244353\) 取模。

题外话:

虽然是t2但是是紫感觉

dty首先锐评了我们0人写出 \(k \leq 5000\) 的情况,他说这个贼简单,插板容斥统计ans。

设计dp,设 \(f[i][j]\) 表示考虑前 \(i\) 个商品,已经选择了 \(j\) 个商品,且不违反连续 \(m\) 个限制的方案数。转移时需要枚举最后一个连续段的长度。然后有个枚举转移,用前缀和优化。然后把容斥答案乘起来就可以了。

他还和我们聊了容斥的最本质概念,以及对我们的嘱咐和提醒,感觉他非常负责和passion。

正解:

先想 \(k \leq 5000\) 为什么不能直接转移过来,因为前面的dp是 \(O(n^2)\) 的,dp的用处是计算题目中后半部分,即 \(k\) 个中的物品没有 \(m\) 连续的 \(ans\)

如何优化,正难则反,我们计算有 \(m\) 个连续的数的方案数。也可以这么想,题目中的限制其实是一些交集,不符合常规的容斥(并集),所以我们反过来计算有 \(k\) 物品有 \(m\) 连续的 \(ans\)

根据容斥的基本性质,我们有如下结论:

\[\left| \bigcup_{i=1}^k A_i \right| = \sum_{i=1}^k |A_i| - \sum_{1 \leq i < j \leq k} |A_i \cap A_j| + \sum_{1 \leq i < j < l \leq k} |A_i \cap A_j \cap A_l| - \cdots + (-1)^{k+1} \left| \bigcap_{i=1}^k A_i \right| \]

其中元素 \(|A_i|\) 在本题中表示至少有 \(1\)\(m\) 连续的商品被选择的方案数。

所以我们枚举子集即可,因为我们每个元素是等价的,所以我们不需要去真的枚举子集,而是去枚举元素,再乘上组合数即可。

那如何判断这样的区间,也就是找到这样的元素呢,一种容易想到但是错误的做法是把 \(m\) 个连续的选择当成一个元素,这样错误的原因是,如果有一个比 \(m\) 长的连续选择可能,那么我们会把这个元素重复计算多次。

所以正确的元素是 \(m\) 个连续的选择加上一个不选择的商品(或者边界)。长度为 \(m+1\),这样我们就可以插板法了。

最后统计答案时也要计算两种可能。还有一个细节,因为 \(n\) 太大,没办法预处理阶乘,我们发现阶乘中的元素相差不大,都是\(n\to n-k\) 范围内的数相乘,我们预处理这个连乘即可。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const int N=2e6+10,mod=998244353;
ll n,m,k;
ll inv[N],mul[N],inm[N];
ll quickpow(ll a,int p){
    ll res=1;
    while(p){
        if(p&1) (res*=a)%=mod;
        (a*=a)%=mod;
        p>>=1;
    }
    return res;
}
ll c(ll n_,ll m){
    if(n==n_) return mul[m-1]*inv[m]%mod;
    return mul[n-n_+m-1]*inm[n-n_-1]%mod*inv[m]%mod;//注意阶乘的处理
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;
    ll jie=1;
    for(ll i=2;i<=k;i++) (jie*=i)%=mod;
    inv[k]=quickpow(jie,mod-2);
    for(ll i=k-1;~i;i--) inv[i]=(i+1)*inv[i+1]%mod;

    mul[0]=n%mod;
    inm[0]=quickpow(n%mod,mod-2);
    for(ll i=1;i<=k+k/m+1;i++){
        mul[i]=(n-i)%mod*mul[i-1]%mod;
        inm[i]=quickpow(mul[i],mod-2);
    }
    ll ans=0,tot=0;
    for(ll i=1;i<=k/m&&i*m+i<=n;i++){
        tot=c(n-m*i,i)*c(n-m*i-i,k-m*i)%mod;//两种可能 1
        if(i%2==1) (ans+=tot)%=mod;
        else ans=(ans-tot+mod)%mod;//容斥
    }
    for(ll i=1;i<=k/m&&i*m+i-1<=n;i++){
        tot=c(n-m*i,i-1)*c(n-m*i-i+1,k-m*i)%mod;//两种可能 2
        if(i%2==1) (ans+=tot)%=mod;
        else ans=(ans-tot+mod)%mod;//容斥
    }
    ans=(c(n,k)-ans+mod)%mod;
    cout<<ans;
	return 0;
}

拍照(photo)

题目背景

一切的相遇都有分别之时。

那天,她即将离开 \(SSOI\)

临别之际,他想给她拍一张照片。

题目描述

他们来到的地方背景由大楼和云朵构成:

  • 大楼在一个一维数轴上,每个位置有一个高度
  • 每朵云彩有一个颜色值 \(c\)
  • 照相机可以拍下一个矩形
  • 矩形的好看程度 \(x\) 定义为:
    • 包含云彩的不同颜色数量
    • 但如果矩形包含大楼,则 \(x=0\)
  • 照相设备限制:所选矩形的高度最多为 \(h\)

输入格式

第一行:两个整数 \(n\), \(h\)(数轴长度,高度限制)
第二行:\(n\) 个整数表示大楼的高度
第三行:整数 \(k\)(云朵数量)
接下来 \(k\) 行:每行三个整数 \(x,y,z\)(云朵坐标和颜色)

输出格式

一个整数表示拍照的最大好看度

提示

  • \(m\) 为云朵 \(y\) 坐标的最大值
  • \(h \geq m\) 表示没有高度限制
  • 大楼高度和云朵坐标都是正整数

数据范围

测试点 特殊限制
1 \(n,k,m \leq 100\)
2-3 \(n,k \leq 1000\)
4-6 \(n,k \leq 2 \times 10^5\), \(m \leq h\)
7-8 \(n,k \leq 2 \times 10^5\)
9-10 无限制

通用范围:

  • \(n,k,c_i \leq 5 \times 10^5\)
  • \(h,m \leq 2 \times 10^9\)

题外话:

dty说这道题是为了让我们打破对这种题就想到笛卡尔树的惯性思维。

但是我们连笛卡尔树都没学过。

这道题目前在我的能力范围之外,我到 8 月再练数据结构。

题解:

考虑钦定最下端的\(y\)

每一个固定的\(y\)都会被中间的很多大楼隔开形成很多个联通块。

联通块向上延伸\(h\)就是一种可能的答案。

于是对于每个\(y\)处的\(x\)连通块都有一个确定的矩形。

考虑随着\(y\)的增大,这些矩形在什么时候会改变:

  1. 对于联通块的变化,会出现\(O(n)\)次合并。
  2. 对于云朵而言,会出现\(O(n)\)次加入,和\(O(n)\)次删除。

因此变化次数是\(O(n)\)的。

我们以上的操作都可以用线段树合并维护。

总复杂度\(O(n \log n)\),常数稍大。

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define N 500010
ll n,h,k;
ll a[N],x[N],y[N],c[N];
ll dui[N<<1],cnn=0;
ll lc[N*20],rc[N*20];
inline void lis()
{
	sort(dui+1,dui+cnn+1);
	cnn=unique(dui+1,dui+cnn+1)-dui-1;
	return ;
}
ll fa[N];
inline ll getf(ll x)
{
	if(fa[x]==x)return x;
	return fa[x]=getf(fa[x]);
}
ll cn=0;
ll val[N*20],cnt[N*20];
inline void pushup(ll x)
{
	val[x]=val[lc[x]]+val[rc[x]];
	return ;
}
inline void update(ll &o,ll l,ll r,ll x,ll k)
{
	if(!o)o=++cn;
	if(l==r)
	{
		//val[o]=0;
		cnt[o]+=k;
		if(cnt[o])val[o]=1;
		else val[o]=0;
		return ;
	}
	ll mid=(l+r)>>1;
	if(mid>=x)update(lc[o],l,mid,x,k);
	if(mid<x)update(rc[o],mid+1,r,x,k);
	pushup(o);
	return ;
}
inline ll merge(ll x,ll y,ll l,ll r)
{
	if(!x||!y)return x+y;
	if(l==r)
	{
		cnt[x]+=cnt[y];
		if(cnt[x])val[x]=1;
		return x;
	}
	ll mid=(l+r)>>1;
	lc[x]=merge(lc[x],lc[y],l,mid);
	rc[x]=merge(rc[x],rc[y],mid+1,r);
	pushup(x);
	return x;
}
inline ll read()
{
	ll x=0;
	char ch;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
ll rt[N];
bool vis[N];
ll p[N],p1[N];
inline bool cmp(ll x,ll y){return a[x]<a[y];}
inline bool cmp1(ll x,ll p){return y[x]<y[p];}
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>h;ll ss=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dui[++cnn]=a[i]+1;
	}
	cin>>k;
	for(int i=1;i<=k;i++)
	{
		cin>>x[i]>>y[i]>>c[i];
		dui[++cnn]=y[i];ss=max(ss,c[i]);
	}
	lis();
	for(int i=1;i<=n;i++)
	{
		a[i]=lower_bound(dui+1,dui+cnn+1,a[i]+1)-dui;
	}
	for(int i=1;i<=k;i++)
	{
		y[i]=lower_bound(dui+1,dui+cnn+1,y[i])-dui;
	}
	for(int i=1;i<=n;i++)p[i]=i;
	for(int i=1;i<=k;i++)p1[i]=i;
	sort(p+1,p+n+1,cmp);sort(p1+1,p1+k+1,cmp1);
	for(int i=1;i<=n;i++)fa[i]=i;
	ll pre=1;
	ll an=0;
	ll nw1=1,nww=1,nw2=1;
	for(int i=1;i<=cnn;i++)
	{
		while(nw1<=k&&dui[y[p1[nw1]]]<=dui[i]+h)
		{
			ll p=p1[nw1];
			ll tem=getf(x[p]); 
			update(rt[tem],1,ss,c[p],-1);
			ll now=val[rt[tem]];
			an=max(an,now);nw1++;
		}
		while(nww<=n&&a[p[nww]]<=i)
		{
			ll g=p[nww];vis[g]=1;nww++;
			bool kz1=(g!=n&&vis[g+1]),kz2=(g!=1&&vis[g-1]);
			if(kz1&&kz2){
				ll f=getf(g-1),u=getf(g+1);
				fa[g]=u; 
				fa[f]=u;
				rt[u]=merge(rt[u],rt[f],1,ss);
				rt[u]=merge(rt[u],rt[g],1,ss);
				ll now=val[rt[u]];
				an=max(an,now);
				continue;
			}
			if(kz1)
			{
				ll f=getf(g+1);
				fa[g]=f;
				rt[f]=merge(rt[f],rt[g],1,ss);
				an=max(an,val[rt[f]]);
				continue;
			}
			if(kz2)
			{
				ll f=getf(g-1);
				fa[g]=f;
				rt[f]=merge(rt[f],rt[g],1,ss);
				an=max(an,val[rt[f]]);
				continue;
			}
		}
		while(nw2<=k&&dui[y[p1[nw2]]]<=dui[i])
		{	
			ll r=p1[nw2];
			ll u=getf(x[r]);
			update(rt[u],1,ss,c[r],1);
			ll now=val[rt[u]];
			an=max(an,now);nw2++;
		}
	}
	cout<<an<<'\n';
	return 0; 
}

游戏(game)

题目背景

\(L\)正在玩一个神奇的游戏。

题目描述

这个游戏的规则是,有\(2 \times n\)张写有数字的纸牌,她要选\(n\)张纸牌使得她选的牌的方差减去剩下的\(n\)张牌的方差尽量小。

现在有一个纸牌序列\(a_1 \cdots a_n\)以及\(q\)组询问,每组询问给你一个\(l, r\),问你对于\(a_l \cdots a_r\)玩这个游戏方差的差值。保证\(r-l+1 \equiv 0 \pmod{2}\)

输入格式

第一行输入一个\(n\)表示牌的数量。
接下来一行包含\(n\)个数\(a_1, a_2 \cdots \cdots, a_n\)
第三行一个数\(q\)表示询问数量。
接下来\(q\)行每行一个询问\(l, r\)

输出格式

输出\(q\)行,每行一个数表示答案,为了避免浮点数,答案乘上\((r-l+1)^4\)

数据范围

对于测试点\(1-2\)\(n, q \leq 10\)
对于测试点\(3-4\)\(a_i \leq 2\)
对于测试点\(5-8\)\(n, q \leq 2000\)
对于测试点\(9-12\)\(l = 1\)
对于测试点\(13-20\):没有特殊限制

对于全部的测试点:\(n, q \leq 10^5\)\(1 \leq a_i \leq 10^9\)

题解:

解题思路

假设选出来的两组数分别为\(a_i\)\(b_i\),最终答案可以表示为:

\[ans = \frac{n \times \sum(a_i^2 - b_i^2) + (\sum b_i)^2 - (\sum a_i)^2}{n^2} \]

设所有数的和为\(sum\),平方和为\(tot\),则可以进一步化简为:

\[ans = \frac{n \times (2 \times \sum a_i^2 - tot) + sum^2 - 2 \times sum \times \sum a_i}{n^2} \]

这样,每个\(a_i\)对答案的贡献是独立的,可以按照\(p_i = n \times a_i^2 - sum \times a_i\)排序。

时间复杂度为\(O(nq \log n)\)

进一步优化可以通过主席树实现\(O(n \log n + q \log^2 n)\)的复杂度。

代码实现

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lll __int128
const ll N=1e5+10;
ll n,q;ll a[N],rt[N];
ll sum[N];lll s2[N];
lll v2[N*31];ll lc[N*31],rc[N*31],gs[N*31],vs[N*31];ll cn=0;
inline void pushup(ll x){v2[x]=v2[lc[x]]+v2[rc[x]];vs[x]=vs[lc[x]]+vs[rc[x]];gs[x]=gs[lc[x]]+gs[rc[x]];return ;}
inline ll nw(ll x){++cn;lc[cn]=lc[x];rc[cn]=rc[x];vs[cn]=vs[x];v2[cn]=v2[x];gs[cn]=gs[x];return cn;} 
inline void update(ll &o,ll l,ll r,ll x){
	o=nw(o);if(l==r){gs[o]++;vs[o]+=x;v2[o]+=x*x;return ;}
	ll mid=(l+r)>>1;
	if(mid>=x)update(lc[o],l,mid,x);
	else update(rc[o],mid+1,r,x);
	pushup(o);return ;
}
ll gg=0,gvs=0;
lll gv2=0;
inline void ask(ll o,ll l,ll r,ll x,ll y){
	if(!o)return ;if(x<=l&&r<=y){gg+=gs[o];gv2+=v2[o];gvs+=vs[o];return ;}
	ll mid=(l+r)>>1;
	if(mid>=x)ask(lc[o],l,mid,x,y);
	if(mid<y)ask(rc[o],mid+1,r,x,y);
	return ;
}
ll mx=0;
inline ll askg(ll o,ll p,ll l,ll r,ll x,ll y){
	if(!o)return 0;if(x<=l&&r<=y)return gs[o]-gs[p];
	ll mid=(l+r)>>1,an=0;
	if(mid>=x)an+=askg(lc[o],lc[p],l,mid,x,y);
	if(mid<y)an+=askg(rc[o],rc[p],mid+1,r,x,y);	
	return an;
}
ll xu[N],cnt=0;
inline void write(lll x){
	if(!x){putchar('0');return ;}
	if(x<0)putchar('-'),x=-x;
	cnt=0;while(x)xu[++cnt]=x%10,x/=10;
	for(int i=cnt;i>=1;i--)putchar(xu[i]+'0');
	return ;
}
string s="game";
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n;for(int i=1;i<=n;i++)cin>>a[i],sum[i]=sum[i-1]+a[i],s2[i]=s2[i-1]+a[i]*a[i],mx=max(mx,a[i]);
	for(int i=1;i<=n;i++){
		rt[i]=rt[i-1];update(rt[i],1,mx,a[i]);
	}cin>>q;for(int i=1;i<=q;i++){
		ll x,y;cin>>x>>y;ll n=(y-x+1)/2;ll s=sum[y]-sum[x-1];lll sf=s2[y]-s2[x-1];
		assert((y-x)&1);
		lll ans=(lll)s*s-sf*n;
		ll L=1,R=mx+mx,an,mid;
		ll tem=s/(2*n),kz=0;if((tem+1)*2*n-s<s-tem*2*n)kz=1;
		while(L<=R){
			mid=(L+R)>>1;ll u=mid/2;
			ll l=tem-u+1,r=tem+u;if(mid&1){if(kz)r++;else l--;}
			l=max(l,1ll);r=min(r,mx);
			if(askg(rt[y],rt[x-1],1,mx,l,r)>=n)an=mid,R=mid-1;
			else L=mid+1;
		}ll u=an/2;ll l=tem-u+1,r=tem+u;if(an&1){if(kz)r++;else l--;}
		l=max(l,1ll);r=min(r,mx);
		gg=gvs=gv2=0;ask(rt[y],1,mx,l,r);ll g=gg,h=gvs;lll f=gv2;
		gg=gvs=gv2=0;ask(rt[x-1],1,mx,l,r);g-=gg;h-=gvs;f-=gv2;
		if(g>n){
			ll res=g-n,l1=tem-l,r1=r-(tem+1),p=0;
			if(l1==r1){if(kz)p=l;else p=r;}
			else if(l1>r1)p=l;else p=r;
			h-=p*res;f-=(lll)p*p*res;
		}ans+=f*n*2-(lll)s*h*2;
		write(ans);putchar('\n');
	}return 0;
}

最后感谢dty的倾力出题和讲解,祝福他。

posted @ 2025-07-20 21:02  NeeDna  阅读(24)  评论(0)    收藏  举报