2023.8.7测试

暑假NOIP模拟赛-2 保龄记

当然也搬了很多原题

T1 转圈圈

一个长度为 n01 串,初始时只有 s 位置上是 1。一次操作可以使一个长度为 k 的字串翻转。对于每个位置 i,你要求出最少需要几次操作使得这个位置为 1,否则输出 1

同时,串中存在 m 个禁止位置,这些位置永远都不能为 1

1n105

为了卡常最后时刻手写队列直接写成栈,喜提 0

手玩可注意到一个点可以往一段区间内奇偶性相同的点连边权为 1 的边,直接连边再 bfs 复杂度 O(n2)。于是选择线段树优化建图,时间复杂度 O(nlogn+logn)

#include<bits/stdc++.h>
#define mp make_pair
using namespace std;

const int N=100010,M=1000010;

int n,m,k,s,a[N],f[M],id[N],cnt; 
bool v[N],vis[M];
vector < pair<int,int> > g[M];
deque <int> q;

struct SegmentTree
{
	int idd[4*N],pos[N];
	
	void build(int p,int l,int r)
	{
		if(l==r)
		{
			idd[p]=pos[l]=++cnt;
			return;
		}
		
		idd[p]=++cnt;
		int mid=(l+r)>>1;
		build(p*2,l,mid);  g[idd[p]].push_back(mp(idd[p*2],0));
		build(p*2+1,mid+1,r);  g[idd[p]].push_back(mp(idd[p*2+1],0));
	}
	
	void add(int p,int l,int r,int ql,int qr,int x)
	{
		if(ql<=l && qr>=r)
		{
			g[x].push_back(mp(idd[p],1));
			return;
		}
		
		int mid=(l+r)>>1;
		if(ql<=mid)
			add(p*2,l,mid,ql,qr,x);
		if(qr>mid)
			add(p*2+1,mid+1,r,ql,qr,x);
	}
}tree[2];

void bfs()  //重点注意01bfs的写法
{
	f[id[s]]=0;  q.push_front(id[s]);
	while(q.size())
	{
		int x=q.front();  q.pop_front();
		if(vis[x])	
			continue;
		vis[x]=1;
		for(int i=0; i<g[x].size(); i++)
		{
			int y=g[x][i].first,z=g[x][i].second;
			if(f[y]==-1 || f[x]+z<f[y])
			{
				f[y]=f[x]+z;
				if(z==0)
					q.push_front(y);
				else
					q.push_back(y);
			}
		}
	}
}

int main()
{
	memset(f,-1,sizeof(f));
	
	scanf("%d%d%d%d",&n,&k,&m,&s);
	for(int i=1; i<=m; i++)
	{
		int x;
		scanf("%d",&x);
		v[x]=1;
	}
  
	tree[0].build(1,1,n);  tree[1].build(1,1,n);
	for(int i=1; i<=n; i++)
	{
		id[i]=tree[i&1].pos[i];
		if(v[i])
			continue;
		int l=(i>=k)? i-(k-1):k-i+1;
		int r=(i+k-1<=n)? i+(k-1):n-k+1+n-i;
		tree[(i&1)^((k&1)^1)].add(1,1,n,l,r,id[i]);	
	}
	
	bfs();
	
	for(int i=1; i<=n; i++)
		printf("%d ",v[i]? -1:f[id[i]]);
	
	return 0;
}

T2 括号匹配

(Uoj原题)

打部分分开了 deque 直接 MLE 喜提 0

注意到一个区间 [i,j] 合法当且仅当满足以下条件:

  • ji+1 是偶数

  • ) 当作 1,其余当作 1,区间的所有前缀和都 0,且总和等于 0

总和等于 0 很难处理,于是考虑转化成如下条件:

  • ) 当作 1,其余当作 1,区间的所有前缀和都 0

  • ( 当作 1,其余当作 1,区间的所有后缀和都 0

根据这些条件预处理出两个数组 L[i],R[i],分别表示以 i 为起点,向左/向右最远能到哪里,可以单调栈 O(n) 求出

那么现在再来考虑区间 [i,j] 的合法性,我们可以写成如下的式子:

  • ji+10(mod2)

  • iL[j]

  • jR[i]

转化一下变成在平面直角坐标系中,有多少个点 (L[j],j) 在点 (i,R[i]) 的左下方,这是一个二维数点的问题,可以用树状数组解决

但是数点要满足 j>i,这样很难操作。注意到当 j<iL[j]j<i,此时必定满足条件,于是我们可以先全部加到答案里在减去比 i 小的 j 的贡献

时间复杂度 O(nlogn)

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=1000010;

int n,s1[N],s2[N],L[N],R[N];
int sta[N],top,cnt0,cnt1;
struct node{int x,y;}tmp0[N],tmp1[N];
char s[N];
LL ans;

bool cmp(node a,node b)
{
	return a.x<b.x;
}

struct BIT
{
	int c[N];
	
	void add(int x,int y)
	{
		x++;
		for(x; x<=n+1; x+=(x&-x))
			c[x]+=y;
	}
	
	int ask(int x)
	{
		x++;
		int res=0;
		for(x; x; x-=(x&-x))
			res+=c[x];
		return res;
	}
}t[2];

void prework()
{
	s1[n+1]=-n;  sta[++top]=0;
	for(int i=1; i<=n+1; i++)
	{
		while(top && s1[i]<s1[sta[top]])
			R[sta[top]+1]=i-1,top--;
		sta[++top]=i;
	}
	
	top=0;  s2[0]=-n;  sta[++top]=n+1;
	for(int i=n; i>=0; i--)
	{
		while(top && s2[i]<s2[sta[top]])
			L[sta[top]-1]=i+1,top--;
		sta[++top]=i;
	} 
	
	for(int i=0; i<=n+1; i++)
	{
		if(s[i]=='(')
			L[i]=i;
		else if(s[i]==')')
			R[i]=i;
	}	
}

int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	
	for(int i=1; i<=n; i++)
		s1[i]=s1[i-1]+(s[i]==')'? -1:1);
	for(int i=n; i>=1; i--)
		s2[i]=s2[i+1]+(s[i]=='('? -1:1);
		
	prework(); 
	
	for(int i=2; i<=n; i+=2)
		tmp0[++cnt0]=(node){L[i],i};
	for(int i=1; i<=n; i+=2)
		tmp1[++cnt1]=(node){L[i],i};
		
	sort(tmp0+1,tmp0+1+cnt0,cmp);
	sort(tmp1+1,tmp1+1+cnt1,cmp);
	
	int p0=1,p1=1;
	for(int i=1; i<=n; i++)
	{
		if(i&1)
		{
			while(p0<=cnt0 && tmp0[p0].x<=i)
				t[0].add(tmp0[p0].y,1),p0++;
			ans+=1LL*(t[0].ask(R[i])-(i-1)/2);
		}
		else
		{
			while(p1<=cnt1 && tmp1[p1].x<=i)
				t[1].add(tmp1[p1].y,1),p1++;
			ans+=1LL*(t[1].ask(R[i])-i/2);
		}
	}
	
	printf("%lld",ans);

	return 0;
}

T3 崩原之战 1

其实就是 P8908 [USACO22DEC] Palindromes P

很有意思的题目

首先考虑暴力,将原串看成 01 串,显然对于区间 [l,r],如果有奇数个 0 和奇数个 1,那它的贡献就是 1

否则,只要其中一种字符两两配对,那另一种字符肯定也配对了。所以我们考虑少的那一种字符(节省时间),假设是 1。那显然,交换相邻相同的字符肯定不优。所以我们肯定是首尾配对,如果还剩下一个的话就放在正中间。然后手玩可以发现肯定存在某一种最优的方案,使得配对的两个字符有一个不移动。假设区间 [l,r] 里有 m1,位置分别是 a1m,那我们可以写出最小的操作次数:

[m1(mod2)]|l+r2am/2+1|+i=1m/2|ai+ami+1lr|

这样时间复杂度 O(n3)

前面的那坨可以 O(n2) 枚举解决掉,但后面那坨带绝对值的求和非常恶心,思考如何转化

注意到我们暴力枚举区间时会改变字符的配对情况,那如果反过来固定字符的配对情况再去枚举区间呢?

我们可以先枚举中间的一个或两个字符,每次再往外扩展一层,比如现在扩展到 aiaj 的配对,那我们就去枚举区间 l(ai1,ai],r[aj,aj+1),这样再中心点确定的情况下字符的配对情况也是确定的。

那如何快速计算字符配对对区间的贡献呢?

p=ai+aj,集合 P 为目前已经匹配的字符的所有 p 构成的集合。将那个绝对值式子分类讨论,分成 ai+ajl+rai+aj>l+r。对于前者,需要快速查询 P 内比 l+r 小的元素的个数和总和,分别记为 ts。那么贡献就是 t(l+r)s。对于后者,我们记 sum=piPpi,那么贡献就是 sums(|P|t)(l+r)

想要快速查询集合内比 x 小的元素总和和个数?BIT!!!

为什么这样枚举可以呢?因为在中心点不同的情况下,每个区间 [l,r] 只会被枚举一次,所以时间复杂度 O(n2logn)

于是,这道题就愉快地结束了

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=7510;

int n,a[N],cnt;
LL ans;
char s[N];

struct BIT
{
	LL c[2*N];
	
	void add(int x,int y)
	{
		for(x; x<=2*n; x+=(x&-x))
			c[x]+=(LL)y;
	}
	
	LL ask(int x)
	{
		LL res=0;
		for(x; x; x-=(x&-x))
			res+=c[x]; 
		return res;
	}
	
	void clear()
	{
		for(int i=1; i<=2*n; i++)
			c[i]=0;
	}
}t1,t2;

int main()
{
	scanf("%s",s+1);  n=strlen(s+1);
	
	int cntg=0; 
	for(int i=1; i<=n; i++)
		cntg+=(s[i]=='G');
	if(cntg>n/2)  //将出现次数少的字符作为操作的对象
		for(int i=1; i<=n; i++)
			s[i]=(s[i]=='G'? 'H':'G');
	
	for(int l=1; l<=n; l++)
	{
		cnt=0;
		for(int r=l; r<=n; r++)
		{
			if(s[r]=='G')
				a[++cnt]=r;
			if((cnt&1) && (r-l+1)%2==0)  //0和1都出现奇数次,无解,贡献-1
				ans--;
			else if(cnt&1)  //提前计算式子的前半部分
				ans+=abs((l+r)/2-a[cnt/2+1]);
		}
	}
	
	cnt=0;
	for(int i=1; i<=n; i++)
		if(s[i]=='G')
			a[++cnt]=i;
	a[cnt+1]=n+1;
	
	for(int i=1; i<=cnt; i++)  //枚举一个中间的一个字符
	{
		t1.clear();  t2.clear();
		LL sum=0;
		
		for(int j=1; i-j>=1 && i+j<=cnt; j++)
		{
			int tmp=a[i-j]+a[i+j];
			sum+=(LL)tmp;
			t1.add(tmp,1);  t2.add(tmp,tmp);
			
			for(int l=a[i-j-1]+1; l<=a[i-j]; l++)
			{
				for(int r=a[i+j]; r<=a[i+j+1]-1; r++)
				{
					if((r-l+1)%2==0)
						continue;
					LL t=t1.ask(l+r),s=t2.ask(l+r);
					ans+=1LL*(t*(l+r)-s+(sum-s)-(j-t)*(l+r));
				}
			}
		}
	} 
	
	for(int i=1; i<cnt; i++)  //枚举中间的两个字符
	{
		t1.clear();  t2.clear();
		LL sum=0;
		
		for(int j=0; i-j>=1 && i+j+1<=cnt; j++)
		{
			int tmp=a[i-j]+a[i+j+1];
			sum+=(LL)tmp;
			t1.add(tmp,1);  t2.add(tmp,tmp);
			
			for(int l=a[i-j-1]+1; l<=a[i-j]; l++)
			{
				for(int r=a[i+j+1]; r<=a[i+j+2]-1; r++)
				{
					LL t=t1.ask(l+r),s=t2.ask(l+r);
					ans+=1LL*(t*(l+r)-s+(sum-s)-(j+1-t)*(l+r));
				}
			}
		}
	}
	
	printf("%lld",ans);

	return 0;
}

T4 抽卡 1

其实就是P9379 [THUPC 2023 决赛] 老虎坤

最后时刻想冲部分分没冲出来(是自己想简单了)。做完这题后对期望有了更深入的认识

copy一下别人的题解

设一个位置集合 A 表示每一位的取值情况,A 的每一位是 {0,1,?} 的一种,所以 A 可以用三进制表示。称 A 合法,当且仅当 A 可以唯一确定目标串

对于操作次数的期望,一个经典套路是计算到达某个合法状态的概率,乘上停留在这里的期望时间,再求和。(注意状态每一位是 {0,1},表示是否知道该位的数字,是二进制)

考虑预处理停留在状态 S 的期望时间 tS。设 PS 表示停留在 S 的概率,那么 PS=iS(1pi)。根据经典结论 tS=i=0(PS)i=11PS(其实我是第一次知道,考完后Shui_Dream跟我分析了下才明白)

那么从 PS 转移到 PT 的系数就是 iS,iTpiiT(1pi)tS=iS,iTpiiT(1pi)1PS,预处理 g1,S=iSpig2,S=inotS(1pi) 可以快速求出。枚举超集 DP 时间复杂度 O(3l)

对于目标串 si,设 Qi 表示 si 的合法位置集合,那么答案就等于 AQiPAtA。发现对于一个 A,它要么唯一确定一个字符串,要么不能,而 A 的每一位都是 {0,1,?} 中的一个,因此 A 的个数是 O(3l) 的,因此 |Qi|3l。于是我们考虑容斥,ans=APAtAAQiPAtA

现在问题在于如何快速求出一个字符串 s 的合法位置集合。设 ptA 表示 A 能唯一确定的字符串编号,如果不唯一则为 1,谁都无法确定就为 0

先考虑暴力,对于任意的 A,选一位 0 或者 1 把它变成 ?,看它是否能确定其他的字符串,这样复杂度是 O(3ll)

但如果反过来,我们选一些 ?,将它填上 0 或者 1,看看两次的结果是否不同。若相同或有一个是 0,就可以转移:令 ptA 等于 ptA,否则就为 1。稍微思考一下就能发现我们只要填一个 ? 就可以了,于是我们可以预处理出每个 A 最靠左的 ?,这样时间复杂度就是 O(3l)

那么总的时间复杂度就是 O(T3l)

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int M=20,N=40010,MAXN=15000010;
const int MOD=998244353;

int T,n,l,c[M],a[N],ans[N];
int p,pw[M],mi[MAXN],val[MAXN],pt[MAXN];  //mi表示最左边的?,val表示?位置的集合 
int g1[N],g2[N],f[MAXN];

void init()
{
	memset(f,0,sizeof(f));
	memset(pt,0,sizeof(pt));
	memset(val,0,sizeof(val));
}

int ksm(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1)
			res=1LL*res*x%MOD;
		x=1LL*x*x%MOD;
		y>>=1;
	}
	return res;
}

void prework()
{
	pw[0]=1;
	for(int i=1; i<=15; i++)
		pw[i]=pw[i-1]*3;
		
	mi[0]=0;
	for(int i=0; i<15; i++)  
	{
		for(int j=pw[i]-1; j>=0; j--)  //预处理每个位置集合最左边的? 
		{
			mi[j*3+0]=mi[j]+1;
			mi[j*3+1]=mi[j]+1;
			mi[j*3+2]=0;
		}
	}
}

void calc()  //计算每个位置集合是否能唯一确定一个字符串 
{
	for(int i=0; i<=pw[l]-1; i++) 
	{
		if(mi[i]>=l)  //大于等于l说明没有? 
			continue;
		int pre=i-pw[mi[i]];  //?位置填1
		val[i]=val[pre]^(1<<mi[i]); 
		pt[i]=pt[pre];
		pre=i-pw[mi[i]]*2;  //?位置填0 
		if(!pt[i])
			pt[i]=pt[pre];
		else if(pt[i]!=pt[pre] && pt[pre]!=0)
			pt[i]=-1;
	}
}

int main()
{
	p=ksm(10000,MOD-2); 
	prework();
	
	scanf("%d",&T);
	while(T--)
	{
		init();
		
		scanf("%d%d",&l,&n);
		for(int i=0; i<l; i++)
			scanf("%d",&c[i]);
		for(int i=1; i<=n; i++)
		{
			char x[N];
			scanf("%s",x);
			a[i]=0;
			for(int j=0; j<l; j++)
				if(x[j]=='1')
					a[i]+=pw[j];
			pt[a[i]]=i;
		} 
			
		if(n==0)
		{
			printf("0\n");
			return 0; 
		}
		
		calc();
		
		for(int i=0; i<=(1<<l)-1; i++)  //预处理g1,g2 
		{
			g1[i]=g2[i]=1;
			for(int j=0; j<l; j++)
			{
				if(i&(1<<j))
				{
					g1[i]=1LL*g1[i]*c[j]%MOD*p%MOD;
					g2[i]=1LL*g2[i]*(10000-c[j])%MOD*p%MOD;
				}
			}
		}
		
		f[0]=1;
		int sum=0,S=(1<<l)-1;
		for(int i=0; i<=S; i++)
		{
			f[i]=1LL*f[i]*ksm((1-g2[S^i]+MOD)%MOD,MOD-2)%MOD;  //先乘上期望时间 
			for(int j=i; j<=S; j=(j+1)|i)  //枚举超集 
			{
				if(i==j)
					continue;
				f[j]=(f[j]+1LL*f[i]*g1[j^i]%MOD*g2[S^j]%MOD)%MOD;
			}
			sum=1LL*(sum+f[i])%MOD;
		}
		
		for(int i=1; i<=n; i++)
			ans[i]=sum;
		for(int i=0; i<=pw[l]-1; i++)  //容斥 
			if(pt[i]>0)
				ans[pt[i]]=(ans[pt[i]]-f[S^val[i]]+MOD)%MOD;
		
		for(int i=1; i<=n; i++)
			printf("%d\n",ans[i]);
	}

	return 0;
}
posted @   xishanmeigao  阅读(24)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示