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;
}