6.7考试总结(NOIP模拟5)

前言

昨天说好不考试来着,昨晚就晚睡颓了一会,今天遭报应了,也没好好考,考得挺烂的就不多说了。

T1 string

题目描述

给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]~s[r]升序排序;如果 x=0,将 s[l]~s[r]降序排序。你需要求出最终序列。

输入格式

第一行两个整数 n,m。

第二行一个字符串 s。

接下来 m 行每行三个整数 l,r,x。

输出格式

一行一个字符串表示答案。

样例

样例输入

5 2
cabcd
1 3 1
3 5 0

样例输出

abdcc

数据范围与提示

对于 40%的数据,n,m<=1000。

对于 100%的数据,n,m<=100000。

解题思路

比赛上第一想法就是打一发sort,直接暴力,然后完美TLE40pts,这一部分分也是所有人都拿到了,没什么意义。。

正解是线段树,主流打法有两种:

  1. 开24棵线段树,分别对26种字母进行维护。
  2. 只开一棵线段树,对于区间维护求值。

我当然是选择码量较小并且快的第二种了QAQ,但是聪明睿智的@WindZR选择了第一种打法(code),虽然他多加了好几个inline快了2s才过

对于每一个线段树区间,如果这个区间都为一个字母,存储。否则,就为0.

在更改的时候,先求一下本区间中各个字母的多少,然后分别对于线段树中的每个字母更改,直接存延迟标记到数值上就好了。

最后统一push_down一下,输出就好了。代码实现可能有一点考验码力。。

code



#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e5+10;
int tre[N<<2];
int n,m,len[27];
char s[N];
inline void push_down(int x)
{
	if(tre[x])
		tre[ls]=tre[rs]=tre[x];
	tre[x]=0;
}
inline void push_up(int x)
{
	tre[x]=tre[ls]*(tre[ls]==tre[rs]);
}
inline void build(int x,int l,int r)
{
//	cout<<x<<' '<<l<<' '<<r<<endl;
	if(l==r)
	{
		tre[x]=s[l]-'a'+1;
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(x);
}
inline void query(int x,int l,int r,int L,int R)
{
//	cout<<x<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<endl;
	if(L>R||r<L||l>R)
		return ;
	if(L<=l&&r<=R&&tre[x])
	{
		len[tre[x]-1]+=r-l+1;
		return ;
	}
	push_down(x);
	int mid=(l+r)>>1;
	if(L<=mid)
		query(ls,l,mid,L,R);
	if(R>mid)
		query(rs,mid+1,r,L,R);
//	push_up(x);
}
inline void update(int x,int l,int r,int L,int R,int dat)
{
//	cout<<x<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<' '<<dat<<endl;
	if(L>R||r<L||l>R)
		return ;
	if(L<=l&&r<=R||tre[x]==dat)
	{
		tre[x]=dat;
		return ;
	}
	push_down(x);
	int mid=(l+r)>>1;
	if(L<=mid)
		update(ls,l,mid,L,R,dat);
	if(R>mid)
		update(rs,mid+1,r,L,R,dat);
	push_up(x);
}
inline void Push_down(int x,int l,int r)
{
	if(tre[x])
	{
		for(int i=l;i<=r;i++)
			s[i]=tre[x]+'a'-1;
		return ;
	}
	int mid=(l+r)>>1;
	Push_down(ls,l,mid);
	Push_down(rs,mid+1,r);
}
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	build(1,1,n);
	for(int i=1,x,y,vis;i<=m;i++)
	{
		memset(len,0,sizeof(len));
		scanf("%d%d%d",&x,&y,&vis);
		query(1,1,n,x,y);
		/*for(int j=0;j<26;j++)
			cout<<len[j]<<' ';
		cout<<endl;*/
		if(vis)
		{
			int temp=x;
			for(int j=0;j<26;j++)
			{
				if(len[j])
					update(1,1,n,temp,temp+len[j]-1,j+1);
				temp+=len[j];
			}
		}
		else
		{
			int temp=x;
			for(int j=25;j>=0;j--)
			{
				if(len[j])
					update(1,1,n,temp,temp+len[j]-1,j+1);
				temp+=len[j];
			}
		}
	}
	Push_down(1,1,n);
	for(int i=1;i<=n;i++)
		printf("%c",s[i]);
	return 0;
}

T2 matrix

题面描述

求出满足以下条件的 n*m 的 01 矩阵个数:

(1)第 i 行第 1~li 列恰好有 1 个 1。 (li+1到ri-1不能放1)

(2)第 i 行第 ri~m 列恰好有 1 个 1。

(3)每列至多有 1 个 1。

输入格式

第一行两个整数 n,m。接下来 n 行每行 2 个整数 li,ri。

输出格式

一行一个整数表示答案。对 998244353 取模

样例

样例输入

2 6
2 4
5 6

样例输出

12

数据范围与提示

对于 20%的数据,n,m<=12。

对于 40%的数据,n,m<=50。

对于 70%的数据,n,m<=300。

对于 100%的数据,n,m<=3000,1<=li<ri<=m。

解题思路

说实话看这题第一眼,我想的是排列组合,后来想了想是个\(n^2\)的dp,但是我想的是枚举坐标,所以整不出来了,在然后就直接搜呗(逃。

这个题正解特别妙,\(f[i][j]\) ,i表示第几列,j表示第i列后面1的个数,最重要的一点是这个题是逆推的。由\((0,0)\)开始,且该位置方案数为1。然后就开始愉快地实现了

第一部分

显然可以从i-1列转移过来,状态有两种:放与不放,也就是j-1或者 j,利用r总数进行更新。

第二部分

枚举前一行到当前行的可行的左侧数量,再进行枚举左侧放的棋子数然后再乘上一个方案数就好了。

  • 优化:l和r边界的总数用前缀和优化。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,M=4e3+10,mod=998244353;
int n,m,sl[N],sr[N],f[M][M];
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld",&n,&m);
	for(int i=1,l,r;i<=n;i++)
	{
		scanf("%lld%lld",&l,&r);
		sl[l]++;
		sr[r]++;
	}
	for(int i=1;i<=m;i++)
	{
		sl[i]+=sl[i-1];
		sr[i]+=sr[i-1];
//		cout<<sl[i]<<' '<<sr[i]<<endl;
	}
	f[0][0]=1;
	for(int i=1;i<=m;i++)
	{
		f[i][0]=f[i-1][0];
		for(int j=1;j<=i;j++)
			f[i][j]=(f[i-1][j]+f[i-1][j-1]*(sr[i]-j+1)%mod)%mod;
		for(int j=sl[i-1];j<sl[i];j++)
			for(int k=0;k<=i;k++)
				f[i][k]=f[i][k]*(i-j-k)%mod;
	}
	printf("%lld",f[m][n]);
	return 0;
}
```# T2 matrix
## 题面描述
求出满足以下条件的 n*m 的 01 矩阵个数:

(1)第 i 行第 1~li 列恰好有 1 个 1。 (li+1到ri-1不能放1)

(2)第 i 行第 ri~m 列恰好有 1 个 1。

(3)每列至多有 1 个 1。
## 输入格式
第一行两个整数 n,m。接下来 n 行每行 2 个整数 li,ri。
## 输出格式
一行一个整数表示答案。对 998244353 取模
## 样例
### 样例输入
```cpp
2 6
2 4
5 6

样例输出

12

数据范围与提示

对于 20%的数据,n,m<=12。

对于 40%的数据,n,m<=50。

对于 70%的数据,n,m<=300。

对于 100%的数据,n,m<=3000,1<=li<ri<=m。

解题思路

说实话看这题第一眼,我想的是排列组合,后来想了想是个\(n^2\)的dp,但是我想的是枚举坐标,所以整不出来了,在然后就直接搜呗(逃。

这个题正解特别妙,\(f[i][j]\) ,i表示第几列,j表示第i列后面1的个数,最重要的一点是这个题是逆推的。由\((0,0)\)开始,且该位置方案数为1。然后就开始愉快地实现了

第一部分

显然可以从i-1列转移过来,状态有两种:放与不放,也就是j-1或者 j,利用r总数进行更新。

第二部分

枚举前一行到当前行的可行的左侧数量,再进行枚举左侧放的棋子数然后再乘上一个方案数就好了。

  • 优化:l和r边界的总数用前缀和优化。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,M=4e3+10,mod=998244353;
int n,m,sl[N],sr[N],f[M][M];
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld",&n,&m);
	for(int i=1,l,r;i<=n;i++)
	{
		scanf("%lld%lld",&l,&r);
		sl[l]++;
		sr[r]++;
	}
	for(int i=1;i<=m;i++)
	{
		sl[i]+=sl[i-1];
		sr[i]+=sr[i-1];
//		cout<<sl[i]<<' '<<sr[i]<<endl;
	}
	f[0][0]=1;
	for(int i=1;i<=m;i++)
	{
		f[i][0]=f[i-1][0];
		for(int j=1;j<=i;j++)
			f[i][j]=(f[i-1][j]+f[i-1][j-1]*(sr[i]-j+1)%mod)%mod;
		for(int j=sl[i-1];j<sl[i];j++)
			for(int k=0;k<=i;k++)
				f[i][k]=f[i][k]*(i-j-k)%mod;
	}
	printf("%lld",f[m][n]);
	return 0;
}

T3 big

题目描述

你需要在\([0,2)\)中选一个整数 x,接着把 x 依次异或 m 个整数a1~am。

在你选出 x 后,你的对手需要选择恰好一个时刻(刚选完数时、异或一些数后或是最后),将 x 变\((\lfloor\dfrac{2x}{2^n}\rfloor+2x) \bmod 2^n\) 你想使 x 最后尽量大,而你的对手会使 x 最后尽量小。

你需要求出 x 最后的最大值,以及得到最大值的初值数量。

输入格式

第一行两个整数 n,m。第二行 m 个整数 a1~am

输出格式

第一行输出一个整数,表示 x 最后的最大值。

第二行输出一个整数,表示得到最大值的初值数量。

第一个数正确得 6 分,两个数都正确再得 4 分

样例

样例输入

2 3
1 2 3

样例输出

1
2

解题思路

考试的时候,我一开始都没打算打这个题(还是我太菜了),联想到了博弈论这一类奇奇怪怪的东西。。。后来仔细看了看题目中操作的式子,想到了循环节,也想到了分位运算,但是就是没有想到用Tire树 [流泪].jpg

\((\lfloor\dfrac{2x}{2^n}\rfloor+2x) \bmod 2^n\),这个式子一看就不是瞎整的,有二进制的感觉,\(\lfloor\dfrac{2x}{2^n}\rfloor\) 就是\((x>>(n-1))\)(取第n位)与\(x<<1\)的加和再按位与上\(1<<n\),扣下前n位,总的来看就类似与把110101变成了101011。

我们再求一个一个前缀和后缀,后缀用于维护u对手操作完没变的点,前缀维护操作过的点,不难发现,对于i之前的所有的都操作与将i之前的异或再操作结果是一样的。
可以手动模拟一下。

然后就是构建Tire树,从根开始往下遍历每一位,处理情况有3种

  1. 子节点0和1都有:此时如果我选择0,那么对手就会选择1,同样的,如果我选择1,那么对手就会选择0,因此这个点对于答案是没有贡献的,只需要继承上来就好了。
  2. 只有0节点:只可以走这一路,对答案有贡献\(1<<res\)
  3. 只有1节点:与只有0节点的情况大同小异,贡献也是\(1<<res\)
  • 优化:遍历时可以返回一个pair同时储存两个答案。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,s[N],suf[N],he[N];
int cnt=1,tre[N*40][2];
int work(int x)
{
//	return 2*x/(1<<n)+2*x%(1<<n);
	return ((x>>(n-1))+(x<<1))&((1<<n)-1);
}
inline void Tire_Add(int x)
{
	int now=1;
	for(int i=n-1;i>=0;i--)
	{
		bool p=x&(1<<i);
		if(!tre[now][p])
			tre[now][p]=++cnt;
		now=tre[now][p];
//		cout<<now<<' ';
	}
}
pair<int,int> dfs(int now,int res)
{
	if(res==-1)
		return make_pair(0,1);
	if(tre[now][0]&&tre[now][1])
	{
		pair<int,int> x=dfs(tre[now][0],res-1),y=dfs(tre[now][1],res-1);
		if(x.first==y.first)
			return make_pair(x.first,x.second+y.second);
		if(x.first>y.first)
			return x;
		return y;
	}
	if(tre[now][0])
	{
		pair<int,int> x=dfs(tre[now][0],res-1);
		x.first+=(1<<res);
		return x;
	}
	if(tre[now][1])
	{
		pair<int,int> x=dfs(tre[now][1],res-1);
		x.first+=(1<<res);
		return x;
	}
}
#undef int
int main()
{
	#define int long long
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%lld",&s[i]);
	for(int i=m;i>=1;i--)
	{
		suf[i]=suf[i+1]^s[i];
//		cout<<suf[i]<<' ';
	}
	for(int i=1;i<=m;i++)
	{
		he[i]=work(s[i])^he[i-1];
//		cout<<he[i]<<' ';
	}
	for(int i=0;i<=m;i++)
		Tire_Add(he[i]^suf[i+1]);
	pair<int,int> ans=dfs(1,n-1);
	printf("%lld\n%lld",ans.first,ans.second);
	return 0;
}

T4 P2403 [SDOI2010]所驼门王的宝藏

解题思路

算是这次比赛里比较好想的一个题了 (虽然我打的裸暴力)

正解的话就是Tarjan缩点+DAG

主流打法有两种:

  1. 建假点,利用假点运算
  2. sort排序,暴力建边 (我当然是喜欢暴力的了)

对于自动排序这一类的问题,hash和map都可以做,(我当然是选择暴力的map了)题面里的所谓各种门,无非是连接各个点的一条边,显然,对于一系列的点如果我们可以到达其中的一个就一定可以全部到达,这不就是强联通分量吗。

因此我们对于每一种门分别查找建出边来,

建边

横天门与纵寰门

这两种门是大同小异的,这里举横天门的例子,先将所有的宝藏室排个序(第一关键字是横坐标,第二关键字是操作,第三关键字是总纵坐标)。

  • 优化:操作的关键字,先判y的比先判x的快了一半(先判x也没事 ~毕竟洛谷数据水嘛)。

任意门

直接暴力枚举各个点建出边来就好了,分别扫一下八个方向是否有点就好了。

其他操作

接下来就可以套Tarjan的板子缩点 了,顺便储存一下各个点属于那一个强联通分量,处理入度为拓扑排序做准备,然后再进行dfs拓扑排序,求出最大值就ok了。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,t,ans,du[N],f[N];
int tot,head[N],ver[N*100],nxt[N*100],fro[N*100];
int cnt,tim,dfn[N],low[N],bel[N],sum[N];
int top,sta[N];
bool bosta[N];
map<int,bool> vis[N];
map<int,int> s[N];
map<int,bool>::iterator it;
int sx[9]={0,-1,-1,-1,0,0,1,1,1};
int sy[9]={0,-1,0,1,-1,1,-1,0,1};
struct Ques
{
	int x,y,opt,id;
}q[N];
inline bool comp1(Ques x,Ques y)
{
	if(x.x!=y.x)
		return x.x<y.x;
	if(y.opt==1)
		return false;
	if(x.opt==1)
		return true;
	return x.y<y.y;
}
inline bool comp2(Ques x,Ques y)
{
	if(x.y!=y.y)
		return x.y<y.y;
	if(y.opt==2)
		return false;
	if(x.opt==2)
		return true;
	return x.x<y.x;
}
inline void add_edge(int x,int y)
{
	ver[++tot]=y;
	fro[tot]=x;
	nxt[tot]=head[x];
	head[x]=tot;
}
inline void Tarjan(int x)
{
	dfn[x]=low[x]=++tim;
	sta[++top]=x;
	bosta[x]=true;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(!dfn[to])
		{
			Tarjan(to);
			low[x]=min(low[x],low[to]);
		}
		else if(!bel[to])
			low[x]=min(low[x],low[to]);
	}
	if(dfn[x]==low[x])
	{
		cnt++;
		int temp=sta[top--];
		bel[temp]=cnt;
		sum[cnt]++;
		bosta[temp]=false;
		while(temp!=x)
		{
			temp=sta[top--];
			bel[temp]=cnt;
			sum[cnt]++;
			bosta[temp]=false;
		}
	}
}
inline void dfs(int x,int fa)
{
	if(f[x]>sum[x])
		return ;
	f[x]=sum[x];
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa)
			continue;
		dfs(to,x);
		f[x]=max(f[x],f[to]+sum[x]);
	}
}
inline int read()
{
	char ch=getchar();
	int x=0;
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
int main()
{
//	scanf("%d%d%d",&t,&n,&m);
    #define int register int
	t=read();
	n=read();
	m=read();
	for(int i=1;i<=t;i++)
	{
//		scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].opt);
		q[i].x=read();
		q[i].y=read();
		q[i].opt=read();
		q[i].id=i;
		s[q[i].x][q[i].y]=i;
	}
	#define head wzrdsb
	int head=1,tail=1;
	sort(q+1,q+t+1,comp1);
	for(int i=1;i<=t;i++)
	{
		if(q[i].x!=q[i+1].x)
		{
			if(head!=tail)
				add_edge(q[tail].id,q[head].id);
			tail=head=i+1;
		}
		else
		{
			if(q[tail].opt==1)
				add_edge(q[tail].id,q[i+1].id);
			if(q[i+1].opt==1)
				tail=i+1;
			if(q[head].opt!=1)
				tail=head=i+1;
		}
	}
	head=1;
	tail=1;
	sort(q+1,q+t+1,comp2);
	for(int i=1;i<=t;i++)
	{
		if(q[i].y!=q[i+1].y)
		{
			if(head!=tail)
				add_edge(q[tail].id,q[head].id);
			tail=head=i+1;
		}
		else
		{
			if(q[tail].opt==2)
				add_edge(q[tail].id,q[i+1].id);
			if(q[i+1].opt==2)
				tail=i+1;
			if(q[head].opt!=2)
				tail=head=i+1;
		}
	}
	#undef head
	for(int i=1;i<=t;i++)
		if(q[i].opt==3)
		{
			for(int j=1;j<=8;j++)
				if(s[q[i].x+sx[j]].count(q[i].y+sy[j]))
					add_edge(q[i].id,s[q[i].x+sx[j]][q[i].y+sy[j]]);
		}
	for(int i=1;i<=t;i++)
		if(!dfn[i])
			Tarjan(i);
	for(int i=1;i<=tot;i++)
	{
		int to=ver[i];
		if(bel[fro[i]]!=bel[to])
			vis[bel[fro[i]]][bel[to]]=true;
	}
	memset(head,0,sizeof(head));
	tot=0;
	for(int i=1;i<N;i++)
		for(it=vis[i].begin();it!=vis[i].end();it++)
		{
			add_edge(i,it->first); 
			du[it->first]++;
		}
	for(int i=1;i<=cnt;i++)
		if(!du[i])
		{ 
		    dfs(i,0);
		    ans=max(ans,f[i]);
		}
	printf("%d",ans);
	return 0;
}
上一篇:[CF1149C]Tree Generator™


下一篇:LeetCode: 598 Range Addition II(easy)