11.3模拟赛

t1

cf题面

你醒了,发现全世界 OI 能力下降 \(10^6\) 倍,只有今日联考受影响。于是你打开 statement.pdf,准备闪击联考。

联考大军向你袭来,作为八校最强的 OIer,你准备在若干位置发动“闪击”。

联考大军中,每一场联考 \(i\) 都有一个编号 \(p_i\)\(p\) 是一个排列。你能在一个位置发动“闪击”,当且仅当这场联考前的联考编号全部小于这场,后的联考编号全部大于这场。形式化地,\(x\) 能发动“闪击”仅当:

\[\forall y < x, p_y < p_x \wedge \forall z > x, p_z > p_x \]

同时,为了便于攻击,你可以利用 OIer 强大的力量,进行一个操作:选择两个不同的下标 \(x, y\),交换 \(p_x, p_y\)

时间紧迫,你要快速决定且只能进行恰好一次这样的操作。你的目标是最大化可以发动“闪击”的位置。输出最大的位置数量。

对于所有测试数据,保证 \(1 \leq T \leq 5 \times 10^4\)\(2 \leq \sum n \leq 3 \times 10^5\)\(p\) 是一个排列。

题解

考虑直接计算贡献,对于每个点找出那些交换会使其产生贡献,排除本来就是好的排列之后,发现交换一定更优,接下来分类讨论。

  1. \(a_i=i\)
    • 这个时候如果左边数都小于他,那么这个数是合法的,直接计入答案,因为我们肯定不会交换这个数两侧的,因为左侧数小于右侧一定不优,直接计入答案即可。
    • 否则这个数如果左右只有一对数不合法,交换这一对即可。
  2. \(a_i>i\)
  • 考虑能不能交换 \(a_i\)\(a_{a_i}\) 来让 \(a_i\) 有贡献,充分必要条件就是有 \(a_i-1\) 个小于 \(a_i\) 的数在 \([1,a_i]\) 之间。
  1. \(a_i<i\)
    • 考虑能不能交换 \(a_i\)\(a_{a_i}\) 来让 \(a_i\) 有贡献,充分必要条件就是 \(a_i\)\([1,a_i]\) 之间最大的数字。

找到每个交换的最大值就可以了。原理是这种交换的总数是 \(O(n)\) 的。

code:

#include<bits/stdc++.h>
#define fi first
#define se second
#define int long long
#define lbt(x) (x&(-x))
#define pii pair<int,int>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=5e5+10;
int n,m,k,T,a[N],p[N],mx[N],mxp[N],t[N],ans;
map<pii,int> g;
void upd(int i,int x){while(i<N){t[i]+=x;i+=lbt(i);}}
int sc(int i){int ans=0;while(i>0){ans+=t[i];i-=lbt(i);}return ans;}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	freopen("strike.in","r",stdin);
	freopen("strike.out","w",stdout);
    cin>>T;
    while(T--){
    	cin>>n;g.clear();ans=0;
    	rep(i,1,n){t[i]=0;
    		cin>>p[i];a[p[i]]=i;mx[i]=max(mx[i-1],p[i]);
		}rep(i,1,n) mxp[i]=max(mxp[i-1],a[i]);
		rep(i,1,n){
			upd(p[i],1);
			if(p[i]==i){
				if(mx[i]==i) ans++;
				if(sc(i)==i-1) g[{a[mx[i]],mxp[i]}]++;
			}else if(p[i]>i){
				if(mx[p[i]]==p[i]) g[{i,p[i]}]++; 
			}else if(mxp[p[i]-1]==p[i]-1) g[{p[i],i}]++;
		}int ma=-2;
		for(auto x:g) ma=max(ma,x.se);
		cout<<ans+ma<<'\n';
	}
	return 0;
}

t2

cf题面

你醒了,你发现你是信竞教练,准备为学生安排模拟赛。

这个赛季总共有 \(n\) 场联考,第 \(i\) 场联考的“特征”可以表示为一个整数 \(a_i\)

如果有连续两场联考满足 \(\gcd(a_i, a_{i+1}) = 1\),那么两场联考风格差别巨大,会让学生非常不满抨击教练。定义一个赛季模拟赛的“不满度”为 \(1\)\(n-1\) 中,\(\gcd(a_i, a_{i+1}) = 1\) 的下标 \(i\) 的数量。

为了避免差评,你决定对联考特征进行最多 \(k\) 次修改。每次修改可以选定一个下标 \(i\) 和任意整数 \(x\),将 \(a_i\) 改为 \(x\)

你想求出这个赛季最小的不满度。

对于所有测试数据,保证 \(T \leq 7\)\(1 \leq k \leq n \leq 10^5\)\(0 \leq a_i \leq 10^9\)

题解

贪心,肯定是把数字一个一个变成0。先把能救两个位置的拿下,然后再管1,因为每个1连续段,管到最后可以改最后一个1,一次救两个。1救完后,再管剩下的互质的。

code:

#include<bits/stdc++.h>
#define fi first
#define se second
#define ls (p<<1)
#define rs (p<<1|1)
#define pb push_back
#define mset multiset
#define int long long
#define lbt(x) (x&(-x))
#define pii pair<int,int>
#define umap unordered_map
#define m(a) memset(a,0,sizeof(a))
#define m127(a) memset(a,0x3f,sizeof(a))
#define m128(a) memset(a,-0x3f,sizeof(a))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=5e5+10;
int n,m,k,T,a[N],q,b[N];
vector<int> g;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	freopen("arrange.in","r",stdin);
	freopen("arrange.out","w",stdout);
    cin>>T;
	while(T--){
		cin>>n>>q;int sum=0,ans=0,tg=0;g.clear();
		rep(i,1,n) cin>>a[i];
		rep(i,1,n){
			if(a[i]!=1) tg=1;
		}rep(i,1,n-1){
			b[i]=__gcd(a[i],a[i+1]);if(b[i]==1) sum++;	
		}if(tg==1){
			rep(i,2,n-1){
				if(b[i-1]==1&&b[i]==1&&a[i-1]!=1&&a[i+1]!=1&&q>0){
					a[i]=0;q--;b[i-1]=a[i-1],b[i]=a[i+1],ans+=2;
				}
			}int l=0,r=n+1;
			while(a[l+1]==1) l++;l++;
			while(a[r-1]==1) r--;r--;
			rep(i,l,r){
				if(a[i]==1){
					int x=i;
					while(a[i+1]==1&&i<r) i++;
					if(i!=n&&x!=1) g.pb(i-x+1);
				}
			}
			sort(g.begin(),g.end());
			for(int v:g){
				if(v<=q) ans+=v+1,q-=v;
				else ans+=q,q=0;
			}cout<<max(0ll,sum-ans-q)<<'\n';
		}else{
			ans=min(q-1,n-1);
			cout<<sum-ans<<'\n';
		}
	}
	return 0;
}

t3

你醒了,你发现你成了构造 Master,秒了前两题来杀 T3。

\(T\) 次询问,每次给出正整数 \(k\),你要构造一个长度 \(\leq 60\)\(01\) 串,满足其本质不同子序列数量恰好为 \(k\)

对于所有测试数据,保证 \(1 \leq T \leq 10^5\)\(1 \leq k \leq 10^9\)

题解

然后考虑反向构造,那我枚举以 \(0\) 开头的串的个数,然后尝试顺次填数,假设我目标 \(0\) 开头串剩余 \(X\) 个,\(1\) 开头剩余 \(Y\) 个。

每次下一个位置如果填 \(0\),那我以 \(0\) 开头的串就会又产生 \(Y+1\) 个。

可以这样考虑,把前面的所有 \(0\) 选上,后面接上一个 \(1\) 开头的串,那有 \(Y\) 种,或者干脆什么都不接,那就又有 \(1\) 种,总的就是 \(Y+1\) 种,可以直接贪心地填上。

随机化 \(X,Y\) 就能过了。当然有一种有道理的做法,考虑上面的做法其实就是折损相减,所以按理来说有一些接近 \(\phi\) 作为比值的点,从这里开始枚举就好了。就是构造了一种 \(\frac{1-x}{x}=x\) 的东西让他能尽量减。

code:

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=5e5+10;
int n,m,k,T,a[N];
vector<int> g[N];
int work(int x,int y){
	if(x==y){
		if(x==0) return 0;
		else return INT_MAX;
	}if(x>y) swap(x,y);
	int k=y/(x+1);
	return k+work(x,y-k*(x+1));
}
void solve(int x,int y,bool b=0){
	if(x==y) return;
	if(x>y) swap(x,y),b^=1;
	cout<<b;solve(x,y-(x+1),b);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	freopen("construct.in","r",stdin);
	freopen("construct.out","w",stdout);
    cin>>T;
    while(T--){
    	cin>>n;int cnt=0;
    	rep(i,(int)n*0.61803398874989484,n){
    		if(work(i,n-i)<=60){
    			solve(i,n-i);cout<<'\n';goto loop;
			}
		}loop:;
	}
	return 0;
}

t4

题面

二操作先不管,因为是独立存在的。考虑一操作,直接考虑对里边叶子的贡献,维护几个叶子 $s z $, 子树里边每个节点有几个叶子的和 $( s s z = \sum s z ) $, 自己这个节点被加了多少并维护加法 $\operatorname { t a g } $, 节点的答案。直接维护答案和节点的值,quy的时候直接把被包含的区间的答案,和没被包含但是经过的节点的贡献算上。 三和一对应的线段树大概如此:

inline void push(uint u, Z x) {
    tag[u] += x;
    val[u] += x;
    sum[u] += ssz[u] * x;
}
inline void down(uint u) {
    if (!tag[u].val) return;
    push(u << 1, tag[u]);
    push(u << 1 | 1, tag[u]);
    tag[u] = 0;
}
inline void modify(const uint ql, const uint qr, const uint u, const uint x) {
    if (dfn[u] > qr || dfn[u] + siz[u] - 1 < ql) return;
    if (ql <= dfn[u] && dfn[u] + siz[u] - 1 <= qr) {
        return push(u, x);
    }
    down(u);
    modify(ql, qr, u << 1, x);
    modify(ql, qr, u << 1 | 1, x);
    val[u] += (ql <= dfn[u] && dfn[u] <= qr) * x;
    sum[u] = val[u] * Z(maxr[0][dfn[u]] - minl[0][dfn[u]] + 1) + sum[u << 1] + 
        sum[u << 1 | 1];
}
Z qres;
inline void query(const uint ql, const uint qr, uint u) {
    uint L = minl[0][dfn[u]], R = maxr[0][dfn[u]];
    if (L > qr || R < ql) return;
    if (ql <= L && R <= qr) return qres += sum[u], void();
    down(u);
    qres += Z(min(R, qr) - max(L, ql) + 1) * val[u];
    query(ql, qr, u << 1);
    query(ql, qr, u << 1 | 1);
}
posted @ 2025-11-03 18:25  NeeDna  阅读(21)  评论(0)    收藏  举报