CSP-S 模拟赛十二总结

∞ \infty 年没打过比赛了,周末打了一场,来写一下总结。

T1

题面(有原题不截图了)。

典型的二分答案类问题(带限制求最值),现在问题就是怎么写 check 函数。

然后 check 里面的那个贪心我是真服了,大致就是说我们把原数列从大到小排序,然后把前几个数作为每个组别的开端,接下来的数要在满足题目条件的情况下尽可能的被塞进去,那我们选择这个数能被塞进去的最小的那个组别就行,因为比这个数更大的组别能选的范围更广,选它们自然更不优,而更小的你又塞不下去。然后这个过程可以做到 O ( n ) O(n) O(n)。于是总时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

当然更极端一点就是:假设我们要分成 x x x 组,我们可以直接规定第 i i i 个数必须被分到 i   m o d   x i\bmod x imodx 组里去,如果塞不进去就直接跳过。正确性我不会证明,反正就这样吧。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
	inline int read()
	{
		int z=0,f=1;
		char c=getchar();
		if(c==EOF)
		{
			exit(0);
		}
		while(c<'0'||c>'9')
		{
			if(c==EOF)
			{
				exit(0);
			}
			if(c=='-')
			{
				f=-1;
			}
			c=getchar();
		}
		while(c>='0'&&c<='9')
		{
			z=z*10+c-'0';
			c=getchar();
		}
		return z*f;
	}
	inline void write(int x)
	{
		if(x<0)
		{
			putchar('-');
			x=-x;
		}
		static int top=0,stk[106];
		while(x)
		{
			stk[++top]=x%10;
			x/=10;
		}
		if(!top)
		{
			stk[++top]=0;
		}
		while(top)
		{
			putchar(char(stk[top--]+'0'));
		}
	}
	inline void write(string s)
	{
		for(auto i:s)
		{
			putchar(i);
		}
	}
}
using namespace fastio;
int n,k,rr,a[1000006],mn[1000006],num[1000006];
bool check(int x)
{
	if(x==0)
	{
		return true;
	}
	for(int i=1;i<=x;i++)
	{
		mn[i]=1e18;
		num[i]=0;
	}
	for(int i=1;i<=n;i++)
	{
		if(mn[i%x==0?x:i%x]-a[i]>=rr)
		{
			mn[i%x==0?x:i%x]=a[i];
			num[i%x==0?x:i%x]++;
		}
	}
	for(int i=1;i<=x;i++)
	{
		if(num[i]<k)
		{
			return false;
		}
	}
	return true;
}
signed main()
{
	n=read(),k=read(),rr=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
	}
	sort(a+1,a+n+1,greater<int>());
	int l=0,r=n/k,mid,ans;
	while(l<=r)
	{
		mid=l+r>>1;
		if(check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	write(ans);
	return 0;
}

另一种方法的代码洛谷题解区里有,而且那种方法似乎更科学一点,正确性可证明,建议大家还是看另外一种做法。

T2

题面

水题,考试的时候没考虑完整被坑了。

首先我们看下面的部分分,我们会发现 k = 3 k=3 k=3 给的分最多,而其余几种情况给的分都很少。说明其他几种应该很好做,我们先考虑其余几种情况。

  1. k = 0 k=0 k=0:此时答案肯定是 YES,随便排都行。
  2. k = 1 k=1 k=1:此时我们把建筑高度从小到大排序,然后从上往下、从左往右依次建建筑,这样就能保证每栋楼至少都有一个朝左的方向。所以答案为 YES
  3. k = 2 k=2 k=2:此时还是按照 k = 1 k=1 k=1 的情况建立,容易发现每个建筑都至少有朝上和朝左两个方向。所以答案为 YES
  4. k = 4 k=4 k=4:此时所有建筑高度一定要全部一样,不然肯定会有建筑缺少至少一个方向。这个判断一下就行。

现在回到 k = 3 k=3 k=3 的情况。我们要思考一种一定合法的构造方法,从而判断是否成立。

我们会发现:对于四个角上的建筑,它们天生就有两个方向,我们只需要再找一个方向就行。因此在角上放高度最小的建筑肯定更好。那么因为所有建筑的高度都大于等于它,所以我们可以直接忽略这个最低的建筑,那么就可以以同样的方式继续构造。可以我们现在要角上的建筑多出一个方向,那就只能在它的附近,甚至是它这一排再安排跟它同样高度的建筑。这样这一排所有的建筑正好都能满足条件。

因此我们现在的问题就变成了:对所有建筑按高度从小到大排序后,每次可以选择一整行或一整列全填充这一种建筑,问能否把整张图全部填充完毕。很容易发现这里我直接默认了这两个条件之间是充要关系,至于为什么也是能证明的。简单证明一下。

首先证明充分性,也就是说如果我按照上面的方法构造出来了,那么原图一定有至少一种构造方法。这一看不就是一句废话吗。再证明必要性。假设按照我的方法构造不出来,那么最小的建筑肯定就不是放在最边上,因此我们容易想到如果最小的建筑放在中间,那么就必须强行构造出来三个方向,保证这三个方向上的建筑高度都是最小高度。也就是长这样:

标红的那一块是高度最小的建筑,然后那三条绿色的柱子就是我们随便选的三个方向。

假设红色方块上面的建筑都比它高,那么下面那个绿色矩形上的所有建筑都还要再找两个方向,也就是左右方,于是最后你会发现下面那一整块就全变成最小高度的建筑了,这和我们之前的构造方法并没有什么区别。

如果说红色方块有四个方向,也就是这样:

那么对于绿色矩形上的建筑,每个建筑需要再找至少一个方向,如果只找一个方向的话就会变回上面的那种情况,所以每个建筑都必须再找两个方向,那再考虑这两个方向上的建筑,又要再重复上面的过程,随后整张图就全都是最小高度的建筑了,这和我们之前的构造方法没什么区别。

因此必要性也是正确的。

所以说如果原图有构造方法,那么就必定有我给出的那种构造方法。我们可以直接用来判定。

然后我们可以直接使用 dfs 解决这个问题,为了防止时间复杂度飞起,我们需要用记忆化搜索。

当然你也可以优化一下:我们很容易发现你每次相当于要删掉一个 L 型的东西,于是我们可以设你删的 L 型的东西的两个宽,列方程,然后每次只需要解方程就行,而且这样会去除掉很多一定错误的情况,相当于一个剪枝了。

代码(没加剪枝):

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
	inline int read()
	{
		int z=0,f=1;
		char c=getchar();
		if(c==EOF)
		{
			exit(0);
		}
		while(c<'0'||c>'9')
		{
			if(c==EOF)
			{
				exit(0);
			}
			if(c=='-')
			{
				f=-1;
			}
			c=getchar();
		}
		while(c>='0'&&c<='9')
		{
			z=z*10+c-'0';
			c=getchar();
		}
		return z*f;
	}
	inline void write(int x)
	{
		if(x<0)
		{
			putchar('-');
			x=-x;
		}
		static int top=0,stk[106];
		while(x)
		{
			stk[++top]=x%10;
			x/=10;
		}
		if(!top)
		{
			stk[++top]=0;
		}
		while(top)
		{
			putchar(char(stk[top--]+'0'));
		}
	}
	inline void write(string s)
	{
		for(auto i:s)
		{
			putchar(i);
		}
	}
}
using namespace fastio;
struct build{
	int x,y;
}a[10006];
int n,m,kk,f[1006][1006];
bool dfs(int x,int y,int pos,int s)
{
	if(pos==m&&s==0)
	{
		return true;
	}
	if(f[x][y]!=-1)
	{
		return f[x][y];
	}
	if(!s)
	{
		pos++;
		s=a[pos].y;
	}
	f[x][y]=0;
	if(s>=x)
	{
		f[x][y]|=dfs(x,y-1,pos,s-x);
	}
	if(s>=y)
	{
		f[x][y]|=dfs(x-1,y,pos,s-y);
	}
	return f[x][y];
}
signed main()
{
	memset(f,-1,sizeof(f));
	n=read(),kk=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		a[i].x=read(),a[i].y=read();
	}
	if(kk<=2)
	{
		write("YES");
		return 0;
	}
	if(kk==4)
	{
		if(m>1)
		{
			write("NO");
		}
		else
		{
			write("YES");
		}
		return 0;
	}
	sort(a+1,a+m+1,[&](build x,build y)
	{
		return x.x<y.x;
	});
	if(dfs(n,n,1,a[1].y))
	{
		write("YES");
	}
	else
	{
		write("NO");
	}
	return 0;
}

代码(加了剪枝):

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
	inline int read()
	{
		int z=0,f=1;
		char c=getchar();
		if(c==EOF)
		{
			exit(0);
		}
		while(c<'0'||c>'9')
		{
			if(c==EOF)
			{
				exit(0);
			}
			if(c=='-')
			{
				f=-1;
			}
			c=getchar();
		}
		while(c>='0'&&c<='9')
		{
			z=z*10+c-'0';
			c=getchar();
		}
		return z*f;
	}
	inline void write(int x)
	{
		if(x<0)
		{
			putchar('-');
			x=-x;
		}
		static int top=0,stk[106];
		while(x)
		{
			stk[++top]=x%10;
			x/=10;
		}
		if(!top)
		{
			stk[++top]=0;
		}
		while(top)
		{
			putchar(char(stk[top--]+'0'));
		}
	}
	inline void write(string s)
	{
		for(auto i:s)
		{
			putchar(i);
		}
	}
}
using namespace fastio;
struct build{
	int x,y;
}a[10006];
int n,m,kk,f[1006][1006];
bool dfs(int x,int y,int pos)
{
	if(pos==m+1)
	{
		return true;
	}
	if(f[x][y]!=-1)
	{
		return f[x][y];
	}
	f[x][y]=0;
	for(int j=0;j<=n&&a[pos].y-x*j>=0;j++)
	{
		if(y-j==0)
		{
			if(a[pos].y%x==0)
			{
				f[x][y]|=dfs(0,0,pos+1);
			}
		}
		else
		{
			if((a[pos].y-x*j)%(y-j)==0)
			{
				f[x][y]|=dfs(x-(a[pos].y-x*j)/(y-j),y-j,pos+1);
			}
		}
	}
	return f[x][y];
}
signed main()
{
	memset(f,-1,sizeof(f));
	n=read(),kk=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		a[i].x=read(),a[i].y=read();
	}
	if(kk<=2)
	{
		write("YES");
		return 0;
	}
	if(kk==4)
	{
		if(m>1)
		{
			write("NO");
		}
		else
		{
			write("YES");
		}
		return 0;
	}
	sort(a+1,a+m+1,[&](build x,build y)
	{
		return x.x<y.x;
	});
	if(dfs(n,n,1))
	{
		write("YES");
	}
	else
	{
		write("NO");
	}
	return 0;
}

T3

题面

首先我们很容易想到把字符串从 1 1 1 开始编码,然后我们就能得出它每次洗牌后的位置的规则:

p o s → { p o s × 2 − 1 p o s ≤ n 2 p o s × 2 − n p o s > n 2 pos\to\begin{cases}pos\times2-1&pos\le\frac{n}{2}\\pos\times2-n&pos\gt\frac{n}{2}\end{cases} pos{pos×21pos×2npos2npos>2n

其中 n n n 指牌数,也就是题目中给的 64 k 64^k 64k

然后我们稍加研究不难发现:每张牌最多只会跳 6 ⋅ k 6\cdot k 6k 次就会回到原来的位置。又因为 k k k 很小,所以每次询问我们是可以暴力跳的。

于是我们直接用 long long 算出当前的位置在哪,然后暴力跳下一个位置,回到原位置了就停下,看看要跳多少步才能跳到终点。这样能获得 40pts。

然后我们再用一下高精度,实现好一点时间复杂度就是 O ( m k 2 ) O(mk^2) O(mk2),能过 70pts(我常数太大被卡了,还要压位才能拿到 70pts)。

现在我们考虑正解,当然你也可以尝试压位做到 O ( m k 2 w ) O(\frac{mk^2}{w}) O(wmk2) 看看能不能飞过去,反正作者的高精度常数有点大,飞不过去(而且哪个出题人搬题的时候把时限卡到了 500ms 啊!!!)。

这里我们需要转换一下编号:我们最初是 1-index,现在我们换成 0-index,也就是从 0 开始,那么我们的规则就变成了这样:

p o s → { p o s × 2 p o s < n 2 p o s × 2 − n + 1 p o s ≥ n 2 pos\to\begin{cases}pos\times2&pos\lt\frac{n}{2}\\pos\times2-n+1&pos\ge\frac{n}{2}\end{cases} pos{pos×2pos×2n+1pos<2npos2n

然后我们把编号放在二进制下看,那么第一种操作就是在后面补了一个 0 0 0,第二种操作就是先补了一个 0 0 0,然后把最高位的那个 1 1 1 去掉,再把末尾那个 0 0 0 变成 1 1 1。我们不难发现:这两种操作都相当于把二进制循环移位了一下,也就是把头放到了尾部。

因此我们可以先把编号的二进制编码保存下来,然后在后面再拼上这个编码,于是我们的问题就变成了找到一段区间 [ i , i + 6 ⋅ k − 1 ] [i,i+6\cdot k-1] [i,i+6k1] 要和目标二进制编码一样。问这个 i i i 是多少。比较两个字符串是否相同这种问题明显交给哈希做啊。于是时间复杂度: O ( m k ) O(mk) O(mk)

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
	inline int read()
	{
		int z=0,f=1;
		char c=getchar();
		if(c==EOF)
		{
			exit(0);
		}
		while(c<'0'||c>'9')
		{
			if(c==EOF)
			{
				exit(0);
			}
			if(c=='-')
			{
				f=-1;
			}
			c=getchar();
		}
		while(c>='0'&&c<='9')
		{
			z=z*10+c-'0';
			c=getchar();
		}
		return z*f;
	}
	inline void write(int x)
	{
		if(x<0)
		{
			putchar('-');
			x=-x;
		}
		static int top=0,stk[106];
		while(x)
		{
			stk[++top]=x%10;
			x/=10;
		}
		if(!top)
		{
			stk[++top]=0;
		}
		while(top)
		{
			putchar(char(stk[top--]+'0'));
		}
	}
	inline void write(string s)
	{
		for(auto i:s)
		{
			putchar(i);
		}
	}
}
using namespace fastio;
int B=3;
int k,m;
unsigned int pw,h1[12006],h2[6006];
int F(char c)
{
	if(c=='#')
	{
		return 0;
	}
	if(c=='$')
	{
		return 1;
	}
	if(c>='0'&&c<='9')
	{
		return c-'0'+2;
	}
	if(c>='A'&&c<='Z')
	{
		return c-'A'+12;
	}
	if(c>='a'&&c<='z')
	{
		return c-'a'+38;
	}
}
string change(string s)
{
	string ss="";
	for(int i=0;i<k;i++)
	{
		int id=F(s[i]);
		for(int j=5;j>=0;j--)
		{
			ss+=to_string((id>>j)&1);
		}
	}
	return ss;
}
signed main()
{
	k=read(),m=read();
	int len=k*6;
	pw=1;
	for(int i=1;i<=len;i++)
	{
		pw=pw*B;
	}
	while(m--)
	{
		string s1,s2;
		cin>>s1>>s2;
		s1=change(s1);
		s2=change(s2);
		s1=s1+s1;
		s1=" "+s1,s2=" "+s2;
		for(int i=1;i<=len*2;i++)
		{
			h1[i]=h1[i-1]*B+(s1[i]-'0'+1);
		}
		for(int i=1;i<=len;i++)
		{
			h2[i]=h2[i-1]*B+(s2[i]-'0'+1);
		}
		int ans=0;
		bool fl=false;
		for(int i=0;i<=len;i++)
		{
			if(h1[i+len]-h1[i]*pw==h2[len])
			{
				fl=true;
				break;
			}
			ans++;
		}
		if(!fl)
		{
			write(-1);
		}
		else
		{
			write(ans);
		}
		putchar('\n');
	}
	return 0;
}

T4

题面(原题是 CF 的,但是找不到了):

题面很短,但是概期 DP 的题一般都不好做。

首先我们考虑我们最终要求的式子的组合意义是什么。很容易想到 ∏ a i \prod a_i ai 的最基本的组合意义就是指第 i i i 个盒子里面有 a i a_i ai 个球,从每个盒子里面选出一个球的总方案数有多少种。而每次操作实际上就像是往第 i i i 及后面的所有箱子里面都扔了 v v v 个球。

因此我们可以设 f i , j f_{i,j} fi,j 表示前 i i i 个箱子,有 j j j 次操作放进去的球被取了出来,于是我们可以转移:

如果当前盒子取出来的球是之前某一次操作放进去的球,那么总的方案数就是 f i + 1 , j = f i , j × ( a i + 1 + j × v ) f_{i+1,j}=f_{i,j}\times(a_{i+1}+j\times v) fi+1,j=fi,j×(ai+1+j×v)

如果当前盒子取出来的球是某一个之前没有被取出来过的操作,那么可以得到 f i + 1 , j + 1 = f i , j × ( m − j ) × v × ( i + 1 ) f_{i+1,j+1}=f_{i,j}\times(m-j)\times v\times(i+1) fi+1,j+1=fi,j×(mj)×v×(i+1),其中 ( m − j ) × v (m-j)\times v (mj)×v 表示取出来的球是哪个, i + 1 i+1 i+1 表示我是从前面哪个箱子的操作中取出来的。

于是我们可以得到答案就是 ∑ i = 0 n f n , i n i \sum_{i=0}^n\frac{f_{n,i}}{n^i} i=0nnifn,i

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
namespace fastio
{
	inline int read()
	{
		int z=0,f=1;
		char c=getchar();
		if(c==EOF)
		{
			exit(0);
		}
		while(c<'0'||c>'9')
		{
			if(c==EOF)
			{
				exit(0);
			}
			if(c=='-')
			{
				f=-1;
			}
			c=getchar();
		}
		while(c>='0'&&c<='9')
		{
			z=z*10+c-'0';
			c=getchar();
		}
		return z*f;
	}
	inline void write(int x)
	{
		if(x<0)
		{
			putchar('-');
			x=-x;
		}
		static int top=0,stk[106];
		while(x)
		{
			stk[++top]=x%10;
			x/=10;
		}
		if(!top)
		{
			stk[++top]=0;
		}
		while(top)
		{
			putchar(char(stk[top--]+'0'));
		}
	}
	inline void write(string s)
	{
		for(auto i:s)
		{
			putchar(i);
		}
	}
}
using namespace fastio;
int n,m,v,a[5006],f[5006][5006];
const int mod=1e9+7;
int qpow(int x,int y)
{
	int z=1;
	while(y)
	{
		if(y&1)
		{
			z=z*x%mod;
		}
		x=x*x%mod;
		y>>=1;
	}
	return z;
}
signed main()
{
	n=read(),m=read(),v=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
	}
	f[0][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			f[i+1][j]=(f[i+1][j]+f[i][j]*a[i+1]%mod+f[i][j]*j%mod*v%mod)%mod;
			f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*(m-j)%mod*v%mod*(i+1)%mod)%mod;
		}
	}
	int ans=0;
	for(int i=0;i<=n;i++)
	{
		ans=(ans+f[n][i]*qpow(qpow(n,i),mod-2)%mod)%mod;
	}
	write(ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值