[题解] Atcoder Regular Contest ARC 150 A B C D 题解

点我看题

求点赞/kel/kk

A - Continuous 1

对于每一个长度为k的区间,它合法当且仅当输入序列中所有出现的1都在这个区间内,所有出现的0都在这个区间外。用前缀和判断一下即可。注意题目要求的是合法区间只有一个,而不是存在就行。

时间复杂度\(O(n)\)

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

using namespace std;

int t,n,k,sum1[300010],sum0[300010];
string s;

int main()
{
  ios::sync_with_stdio(false);
  cin>>t;
  rep(tn,t)
  {
    cin>>n>>k>>s;
    LL tot0=0,tot1=0;
    rep(i,n)
    {
      sum1[i+1]=sum1[i]+(s[i]=='1' ? 1:0);
      sum0[i+1]=sum0[i]+(s[i]=='0' ? 1:0);
      if(s[i]=='1') ++tot1;else if(s[i]=='0') ++tot0;
    }
    int cc=0;
    rep(i,n-k+1)
    {
      int c1=sum1[i+k]-sum1[i],c0=sum0[i+k]-sum0[i];
      if(c1==tot1&&c0==0) ++cc;
    }
    cout<<(cc==1 ? "Yes":"No")<<endl;
  }
	return 0;
}

B - Make Divisible

\(B+Y=k(A+X)\)。假设我们已经确定了X,那么我们应该找到最小的整数k使得\(k(A+X) \ge B\)。因为这样可以让Y最小。当X比较小的时候,比如\(\le \sqrt{1e9}\)时,这样的X是非常少的,直接枚举即可;当\(X>\sqrt{1e9}\)时,k一定\(\le \sqrt{1e9}\),那么我们干脆枚举k,此时为了让\(k(A+X) \ge B\),X会有一个下界,容易发现X取这个下界时是最优的,因为这样可以让Y最小。(注意题目要求X和Y都是非负数)

时间复杂度\(O(t\sqrt{1e9})\)

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

using namespace std;

LL t,a,b,B=100000;

int main()
{
  cin>>t;
  rep(tn,t)
  {
    cin>>a>>b;
    LL ans=1e18;
    for(LL x=0;x<=B;++x) ans=min(ans,(b+a+x-1)/(a+x)*(a+x)-b+x);
    for(LL k=1;k<=B;++k)
    {
      LL lim=b-k*a;
      if(lim<=0) lim=0;
      else lim=(lim+k-1)/k;
      ans=min(ans,k*a+k*lim-b+lim);
    }
    cout<<ans<<endl;
  }
	return 0;
}

C - Path and Subsequence

注意A和B数组中是会有重复的元素的。。。我一开始被这个坑了,还在写找点双什么的

题目中的合法条件等价于:图中的每个节点都有一个值,为\(A_i\),任意一条1~n的路径都会依次经过值为\(B_1 \cdots B_k\)的点。我们只要找有没有不合法的路径就行了。任意一条路径,它一定会去尽量早地匹配B中的元素(从前往后遍历路径,碰到B中的下一个元素就匹配),以尽量使自己合法。令\(dist_i\)表示从1走到i的所有路径,在尽量早地匹配B中元素的情况下,最少匹配了B中的几个元素,容易发现这样贪心是正确的。用Dijkstra的方式转移,最后看\(dist_n\)是否等于\(|B|\)即可。题目中要求路径是简单路径,但是没有关系,把有环的路径中的环去掉变成简单路径,肯定更容易不合法,所以Dijkstra是不会经过环的。

时间复杂度\(O(nlogn)\),n、m同阶所以都用n表示了。

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

using namespace std;

int n,m,k,a[100010],b[100010],dist[100010];
vector <int> g[100010];
multiset <pii> q;

int main()
{
  cin>>n>>m>>k;
  int x,y;
  rep(i,m)
  {
    scanf("%d%d",&x,&y);
    g[x].pb(y);g[y].pb(x);
  }
  repn(i,n) scanf("%d",&a[i]);
  rep(i,k) scanf("%d",&b[i]);
  rep(i,n+3) dist[i]=1e9;dist[1]=(b[0]==a[1] ? 1:0);q.insert(mpr(dist[1],1));
  while(!q.empty())
  {
    pii f=*q.begin();q.erase(q.begin());
    rep(i,g[f.se].size())
    {
      int val=f.fi+(f.fi==k ? 0:(b[f.fi]==a[g[f.se][i]] ? 1:0));
      if(dist[g[f.se][i]]>val)
      {
        dist[g[f.se][i]]=val;
        q.insert(mpr(val,g[f.se][i]));
      }
    }
  }
  puts(dist[n]==k ? "Yes":"No");
	return 0;
}

D - Removing Gacha

注意题目中有四种定义:黑点,白点,好点,坏点。

令从第一次达到有i个黑点的情况,进步到第一次有i+1个黑点,期望需要的步数为\(x_i\),要求的答案就是\(\sum_{i=0}^{n-1}x_i\)(不懂的感性理解一下;证明其实还是挺显然的)。令\(prob_{k,m}\)表示第一次达到k个黑点时,有m个好点的概率(\(m\le k\))。在有k个黑点和m个好点时,我们会在\(n-m\)个坏点中随机选点染黑,坏点中有\(n-k\)个白点,砸中一个就是胜利,成功晋级到k+1个黑点;每次期望砸到\(\frac{n-k}{n-m}\)个白点,所以期望砸\(\frac{n-m}{n-k}\)次。所以:

\[\begin{align} x_k&=\sum_m (prob_{k,m}\cdot \frac{n-m}{n-k})\\ &=\sum_m prob_{k,m}\cdot \frac{n}{n-k}-\frac{\sum_m (prob_{k,m}\cdot m)}{n-k}\\ &=1\cdot \frac{n}{n-k}-\frac{\sum_m (prob_{k,m}\cdot m)}{n-k} \end{align} \]

所以我们只要对每个k求出\(\sum_m (prob_{k,m}\cdot m)\)就行了,发现这个东西就是第一次有k个黑点时期望好点个数。在我们选点染黑时,是从所有坏点中均匀随机,染到坏点中的黑点是没用的,必须染到白点才能让黑点个数+1。每个白点被选到的概率也是相同的,所以我们的染色过程是:每次从所有白点中随机选一个点染黑。这个过程非常对称,所以有k个黑点时,每个点是黑色的概率相同。此时的期望好点的个数是每个点是好点的概率之和。一个深度为d(根节点深度为1)的点是好点的概率是从它到根的d个节点都是黑点的概率。显然这个概率是\(\frac{\binom{n-d}{k-d}}{\binom{n}{k}}=\frac{(n-d)!k!}{(k-d)!n!}\)。这就已经得到一个卷积的形式了,一遍NTT即可。时间复杂度\(O(nlogn)\)

有O(n)的神奇做法,代码只有一行,但我不会。

点击查看代码
#include <bits/stdc++.h>
#include <atcoder/all>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

using namespace std;
using mint=atcoder::modint998244353;

const LL MOD=998244353;

LL qpow(LL x,LL a)
{
	LL res=x,ret=1;
	while(a>0)
	{
		if((a&1)==1) ret=ret*res%MOD;
		a>>=1;
		res=res*res%MOD;
	}
	return ret;
}

LL n,dcnt[200010],fac[200010],inv[200010];
vector <LL> g[200010];

void dfs(int pos,int d)
{
  ++dcnt[d];
  rep(i,g[pos].size()) dfs(g[pos][i],d+1);
}

pii calc(LL val)
{
  repn(i,40) repn(j,40) if((LL)i*qpow(j,MOD-2)%MOD==val) return mpr(i,j);
}

int main()
{
  fac[0]=1;repn(i,200005) fac[i]=fac[i-1]*i%MOD;
  rep(i,200003) inv[i]=qpow(fac[i],MOD-2);
  cin>>n;
  int x;
  for(int i=2;i<=n;++i)
  {
    scanf("%d",&x);
    g[x].pb(i);
  }
  dfs(1,1);
  vector <mint> A(n+1,0),B(n+1,0),C;
  repn(i,n)
  {
    LL bas=fac[n-i]*inv[n]%MOD;
    A[i]=bas*dcnt[i]%MOD;
  }
  rep(i,n+1) B[i]=inv[i];
  C=atcoder::convolution(A,B);
  mint ans=0;
  rep(i,n)
  {
    mint res=n*qpow(n-i,MOD-2)%MOD,sub=C[i]*fac[i]*qpow(n-i,MOD-2);
    res-=sub;
    ans+=res;
  }
  cout<<ans.val()<<endl;
  return 0;
}
posted @ 2022-10-11 11:55  LegendStane  阅读(193)  评论(0)    收藏  举报