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\)。
根据容斥的基本性质,我们有如下结论:
其中元素 \(|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\)的增大,这些矩形在什么时候会改变:
- 对于联通块的变化,会出现\(O(n)\)次合并。
 - 对于云朵而言,会出现\(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\),最终答案可以表示为:
设所有数的和为\(sum\),平方和为\(tot\),则可以进一步化简为:
这样,每个\(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的倾力出题和讲解,祝福他。

                
            
        
浙公网安备 33010602011771号