Codeforces Round #551 (Div. 2) 题解

Codeforces Round #551 (Div. 2) 题解

A. Serval and Bus

有若干种公交车,第\(i\)种会从\(s_i\)时刻开始,每过\(d_i\)秒会出现一次。现在有一个人在\(t_i\)时刻到达车站,问它会碰到的第一辆车是哪一种。

傻逼题

#include<iostream>
#include<cstdio>
using namespace std;
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int ans=0,mxT=1e9,n,T;
int main()
{
	n=read();T=read();
	for(int i=1;i<=n;++i)
	{
		int x=read(),d=read();
		while(x<T)x+=d;
		if(mxT>x)mxT=x,ans=i;
	}
	cout<<ans<<endl;
	return 0;
}

B. Serval and Toy Bricks

给你三视图,还原一个可能的图形。

俯视图告诉了哪些位置有东西。
左视图正视图告诉了每一行/每一列的最大值,
然后随手构造一下就行了。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 120
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int h[MAX][MAX],a[MAX],b[MAX],n,m,H,c[MAX][MAX];
int main()
{
	n=read();m=read();H=read();
	for(int i=1;i<=m;++i)a[i]=read();
	for(int i=1;i<=n;++i)b[i]=read();
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)c[i][j]=read();
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
		{
			if(!c[j][i])continue;
			if(b[j]>=a[i])h[j][i]=max(h[j][i],a[i]);
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			if(!c[i][j])continue;
			if(a[j]>=b[i])h[i][j]=max(h[i][j],b[i]);
		}
	for(int i=1;i<=n;++i,puts(""))
		for(int j=1;j<=m;++j)printf("%d ",h[i][j]);
	return 0;
}

C. Serval and Parenthesis Sequence

给你一个带有通配符的括号序列,你要构造一个合法的括号序列,使得除了本身外的每一个前缀都是不合法的括号序列。

仔细想想就会发现第一个位置一定是左括号,最后一个一定是右括号,且两个括号一定匹配。
问题变成了第\(2\)个位置到第\(n-1\)个位置必须是一个合法的括号序列。
那么算一下需要多少个左括号,前面全部填左括号,剩下的填右括号,再扫一遍判断是否合法即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 300300
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}

char s[MAX];int n;
void WA(){puts(":(");exit(0);}
void Output()
{
	for(int i=1;i<=n;++i)putchar(s[i]);
	puts("");exit(0);
}
int main()
{
	n=read();scanf("%s",s+1);
	if(n&1)WA();
	if(s[1]==')')WA();
	if(s[n]=='(')WA();
	s[1]='(';s[n]=')';
	if(n==2)Output();
	int tt=0,q=0;
	for(int i=2;i<n;++i)
		if(s[i]=='(')tt+=1;
		else if(s[i]==')')tt-=1;
		else ++q;
	if(q<abs(tt))WA();
	int lf=(q-abs(tt))/2;if(tt<0)lf-=tt;
	int cnt=0;
	for(int i=2;i<n;++i)
		if(s[i]=='?')
		{
			++cnt;
			if(cnt<=lf)s[i]='(';
			else s[i]=')';
		}
	for(int i=2,t=0;i<n;++i)
	{
		if(s[i]=='(')t+=1;
		else t-=1;
		if(t<0)WA();
	}		
	Output();
	return 0;
}

D. Serval and Rooted Tree

给你一棵树,每个点有一个\(\min\)或者一个\(\max\),表示其点权是所有儿子的的点权的最大值或者最小值。假设一共有\(k\)个叶子节点,那么每个叶子节点的点权是\([1,k]\)中的一个数,并且每个叶子的点权必须不同。
求根节点的最大点权。

一个很简单的\(dp\)题,设\(f[i]\)表示当前根节点的点权在子树的所有叶子的权值中最大排名第几,
如果这个点是\(\max\),那么转移就是叶子个数减去某个子树\(v\)中的叶子个数加上\(f[v]\)
如果是\(\min\),那么转移就是\(1+\sum f[v]-1\)
复杂度\(O(n)\)

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAX 300300
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
vector<int> E[MAX];
int f[MAX],sz[MAX],a[MAX],n;
void dfs(int u)
{
	if(!E[u].size()){f[u]=sz[u]=1;return;}
	int mx=0;
	for(int v:E[u])dfs(v),sz[u]+=sz[v];
	for(int v:E[u])
		if(a[u]==1)mx=max(mx,sz[u]-sz[v]+f[v]);
		else mx+=f[v]-1;
	if(a[u]==0)mx+=1;
	f[u]=mx;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=2;i<=n;++i)E[read()].push_back(i);
	dfs(1);printf("%d\n",f[1]);
	return 0;
}

E. Serval and Snake

交互题。
\(n*n\)的网格中有一条每条边都平行于\(x\)轴或者\(y\)轴,且不交不成环的折线(就是一条贪吃蛇),你每次可以询问一个矩阵,交互库会回答这个矩形的边界和折线的交点数量。
你需要在\(2n+log(n)\)次询问内找出这个折线的两个端点。

发现如果询问的矩形中包含了恰好一个端点,那么返回值就是奇数,否则是偶数。
那么先询问每一行和每一列,确定两个点在哪一行哪一列。
如果不在同一行或者同一列,额外询问一次就可以确定答案。
否则在同一行或者同一列,额外二分一下答案就可以了。

#include<iostream>
#include<cstdio>
using namespace std;
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n;
int Query(int x1,int y1,int x2,int y2)
{
	printf("? %d %d %d %d\n",x1,y1,x2,y2);
	fflush(stdout);
	return read();
}
void Answer(int x1,int y1,int x2,int y2)
{
	printf("! %d %d %d %d\n",x1,y1,x2,y2);
	fflush(stdout);
}
int lx[20],t1,ly[20],t2;
int main()
{
	n=read();
	for(int i=1;i<=n;++i)if(Query(i,1,i,n)&1)lx[++t1]=i;
	for(int i=1;i<=n;++i)if(Query(1,i,n,i)&1)ly[++t2]=i;
	if(t1==2&&t2==2)
	{
		if(Query(lx[1],ly[1],lx[1],ly[1])&1)
			Answer(lx[1],ly[1],lx[2],ly[2]);
		else Answer(lx[1],ly[2],lx[2],ly[1]);
	}
	else if(t1==2)
	{
		int l=1,r=n,ret=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(Query(lx[1],1,lx[1],mid)&1)ret=mid,r=mid-1;
			else l=mid+1;
		}
		Answer(lx[1],ret,lx[2],ret);
	}
	else
	{
		int l=1,r=n,ret=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(Query(1,ly[1],mid,ly[1])&1)ret=mid,r=mid-1;
			else l=mid+1;
		}
		Answer(ret,ly[1],ret,ly[2]);
	}
	return 0;
}

F. Serval and Bonus Problem

\([0,l]\)的数轴上,随机\(n\)条线段(端点是实数),求被超过\(k\)条线段覆盖的区间的长度和的期望。

看ChineseRound的题解是真的舒服

首先既然是实数,那么长度是\(l\)和长度是\(1\)没有什么区别。
而合法区间的总长度和随机一个点\(P\),使得它在合法区间上的概率也是一样的。
那么\(n\)个区间一共有\(2n\)个端点,再加上\(P\)点,一共会产生\(2n+1\)个点,这些点也是可以随机产生的。我们现在要算的就是\(P\)点在合法区间上的概率。
于是现在的问题就是给你\(2n+1\)个点,怎么选择\(P\)点以及\(n\)条直线求\(P\)被至少\(k\)条覆盖的概率。
我们设\(f[i][j][0/1]\)表示前\(i\)个点中,还有\(j\)个点没有找到匹配,是否已经选定了\(P\)点。
考虑转移:

  • 这个点作为右端点:\(f[i][j][k]*j\rightarrow f[i+1][j-1][k]\)
  • 这个点作为左端点:\(f[i][j][k]\rightarrow f[i+1][j+1][k]\)
  • 这个点作为\(P\)点:\(f[i][j][0]\rightarrow f[i+1][j][1]\)

我们强制选择\(P\)点的时候\(j\ge K\),这样子算出来的就是至少被覆盖\(K\)次的方案数。
但是这样算出来的东西显然是算多的,因为我们这样子等价于强行把线段进行了。
考虑计算总方案,我们这样子来一种对应方法,即对于一个排列,前\(2n\)个点相邻的两个配对作为一条线段,最后一个点作为\(P\)点,但是这样子算重了,所以要除掉\(n!*2^n\)
所以答案就是\(\displaystyle \frac{f[2n+1][0][1]*L*n!*2^n}{(2n+1)!}\)

#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 998244353
#define MAX 4040
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int fpow(int a,int b){int s=1;while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}return s;}
int n,N,K,L,f[MAX][MAX][2],ans,inv[MAX];
int main()
{
	cin>>n>>K>>L;N=n+n+1;
	f[0][0][0]=1;
	for(int i=1;i<=N;++i)
		for(int j=0;j<i;++j)
		{
			add(f[i][j+1][0],f[i-1][j][0]);
			add(f[i][j+1][1],f[i-1][j][1]);
			if(j)add(f[i][j-1][0],1ll*f[i-1][j][0]*j%MOD);
			if(j)add(f[i][j-1][1],1ll*f[i-1][j][1]*j%MOD);
			if(j>=K)add(f[i][j][1],f[i-1][j][0]);
		}
	ans=1ll*L*f[N][0][1]%MOD;
	for(int i=1;i<=n;++i)ans=2ll*ans*i%MOD;
	inv[0]=inv[1]=1;for(int i=2;i<=N;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD,ans=1ll*ans*inv[i]%MOD;
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-04-14 20:32  小蒟蒻yyb  阅读(626)  评论(5编辑  收藏  举报