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