[题解] Codeforces Global Round 23 1746 A B C D E1 F 题解

点我看题

求点赞

A. Maxmina

首先序列全0的情况肯定是NO。否则,如果\(k\ge 3\),则在序列中随便找一个1,把他左边和右边分别用第一种操作不断缩,直到序列长度为k为止,最后用一次2操作变成一个1;如果\(k=2\),直接不断用2操作把序列缩成一个元素即可。所以最后的结论就是只要序列中有1就是YES,否则是NO。

时间复杂度\(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,a[60];

int main()
{
  cin>>t;
  rep(tn,t)
  {
    cin>>n>>k;
    bool ok=false;
    rep(i,n)
    {
      cin>>a[i];
      ok|=(a[i]==1);
    }
    puts(ok ? "YES":"NO");
  }
  return 0;
}

B. Rebellion

我们把\(a_j+=a_i\)的操作称为对i的操作。我们是不会先\(a_j+=a_i\),然后再对\(a_j\)操作的,因为这样不如直接把\(a_i\)加到\(a_j\)最后加到的那个位置去。所以我们操作的对象只能是初始序列中,没被任何东西加过的元素。发现对于一个0的操作相当于是把他删除。如果还要对一些1操作,那肯定是操作原序列中最靠前的一些1比较好。假设我们对k个1进行操作,这些1应该去优先填补第i+1个1之后的那些0。如果填补不完,则必须对剩下的0操作,把它们删掉。枚举k,用前缀和计算即可。

时间复杂度\(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,a[100010],bk0[100010],nxt[100010];

int main()
{
  cin>>t;
  rep(tn,t)
  {
    cin>>n;
    rep(i,n) scanf("%d",&a[i]);
    int lst=-1,cc=0;
    for(int i=n-1;i>=0;--i)
    {
      if(a[i]==0) ++cc;
      else
      {
        nxt[i]=lst;lst=i;
        bk0[i]=cc;
      }
    }
    if(lst==-1)
    {
      puts("0");
      continue;
    }
    int ans=bk0[lst],c1=0;
    rep(i,n) if(a[i]==1)
    {
      ++c1;
      int c0;
      if(nxt[i]==-1) ans=min(ans,c1);
      else
      {
        ans=min(ans,c1+max(0,bk0[nxt[i]]-c1));
      }
    }
    printf("%d\n",ans);
  }
  return 0;
}

C. Permutation Operations

这种最优化+构造方案的题,日常先猜个结论:可以把逆序对数变成0。其实确实可以:逆序对数是0的充要条件是,每个数前面都没有比他大的数;后缀+1的操作,我们给以n开头的后缀;后缀+2的操作,我们给以n-1开头的后缀\(\cdots\)后缀+n的操作,我们给以1开头的后缀。这样可以保证每个数前面都没有比他大的数。以上"以x开头的后缀"中的x均指序列中的初始值。

时间复杂度\(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,a[100010];
vector <pii> v;

int main()
{
  cin>>t;
  rep(tn,t)
  {
    cin>>n;
    v.clear();
    repn(i,n) scanf("%d",&a[i]),v.pb(mpr(a[i],i));
    sort(v.begin(),v.end());reverse(v.begin(),v.end());
    rep(i,v.size()) printf("%d ",v[i].se);puts("");
  }
  return 0;
}

D. Paths on the Tree

首先每条路径的终点一定是叶子,因为多往下延伸一点总能多点贡献。令\(f_{i,j}\)表示以i为根的子树内,一共有j条路径的最大贡献。按照题目中的要求,如果一共有s个儿子,每个儿子都会分到\(\lfloor\frac js\rfloor\)或者\(\lceil\frac js\rceil\)条路径,且取到每种值的儿子数量是固定的,假设有\(cnt\)个儿子取到\(\lceil\frac js\rceil\)条。以记忆化搜索的方式进行dp转移,转移的时候对所有儿子的\(f_{u,\lceil\frac js\rceil}-f_{u,\lfloor\frac js\rfloor}\)排序,取这个值最大的cnt个儿子取\(\lceil\frac js\rceil\)条路径即可。手算一下会发现,对于任意节点i,会被搜索到的\(f_{i,j}\)只有最多2种,也就是j的合法取值只有最多2种。我一开始用map存j居然T了,所以最好先把每个i的可能的j取值先预处理出来。

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

点击查看代码
#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 <LL,LL>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

using namespace std;

LL t,n,k,c[200010],bas[200010],dp[200010][2];
vector <LL> g[200010];

bool cmp(pii aa,pii bb){return aa.se-aa.fi>bb.se-bb.fi;}

void dfsPre(LL pos,LL cc)
{
  bas[pos]=cc;
  if(g[pos].size()==0) return;
  cc/=g[pos].size();
  if(cc==0) return;
  rep(i,g[pos].size()) dfsPre(g[pos][i],cc);
}

LL dfs(LL pos,LL cc)
{
  if(cc==0) return 0;
  LL w=(cc==bas[pos] ? 0:1);
  if(dp[pos][w]>-1) return dp[pos][w];//cout<<pos<<' '<<cc<<' '<<bas[pos]<<endl;
  LL ret=cc*c[pos];
  if(g[pos].size()==0) return dp[pos][w]=ret;
  LL soncnt=g[pos].size(),bass=cc/soncnt,mo=bass+1,mocnt=cc%soncnt;
  vector <pii> vs;
  rep(i,g[pos].size())
  {
    LL v1=dfs(g[pos][i],bass),v2=dfs(g[pos][i],mo);
    vs.pb(mpr(v1,v2));
  }
  sort(vs.begin(),vs.end(),cmp);
  rep(i,vs.size()) if(i<mocnt) ret+=vs[i].se;else ret+=vs[i].fi;
  //if(pos==3&&cc==2) cout<<ret<<endl;
  return dp[pos][w]=ret;
}

int main()
{
  cin>>t;
  rep(tn,t)
  {
    scanf("%lld%lld",&n,&k);
    rep(i,n+3) g[i].clear();
    LL x;
    for(int i=2;i<=n;++i)
    {
      scanf("%lld",&x);
      g[x].pb(i);
    }
    repn(i,n) scanf("%lld",&c[i]);
    rep(i,n+3) rep(j,2) dp[i][j]=-1;
    dfsPre(1,k);
    LL ans=dfs(1,k);
    printf("%lld\n",ans);
  }
  return 0;
}

E1. Joking (Easy Version)

我觉得这题挺难的,为啥小学生都会做啊

为避免混淆,首先明确定义:一个询问的返回结果有两种:YES(Y),NO(N);一个询问的正确性有两种,真(T) 和 假(F)。

令当前n的可能值的集合为s。当\(|s|\ge 4\)时,把s尽量均匀地分成4份,\(s1,s2,s3,s4\)。使用两次询问,分别问\(\{s1 \cup s2\}\)\(\{s1\cup s3\}\)。得到返回结果后,讨论这两个询问的正确性是TT、TF还是FT。比如当返回结果为YN时,正确性为TT则目标在s2内;正确性为TF则目标在s1内;正确性为FT则目标在s4内,所以应该淘汰s3。其他三种情况推理类似,具体来说:返回结果为YY淘汰s4,为YN淘汰s3,为NY淘汰s2,为NN淘汰s1。所以每2个这样的询问可以让s的大小大约乘\(\frac 34\)。用下面的代码发现最多需要38次才能把s的大小减少到3以内,也就是76次操作。

int n=100000,cc=0;
while(n>3)
{
  n-=n/4;
  cc++;
}
cout<<cc;

如果现在s的大小<3的话,直接猜2次就行了。否则令s中的3个元素为\(1,2,3\),连续问以下四个问题:\(\{1\},\{2\},\{2\},\{1\}\)。我们的目标是淘汰1,2,3中的任意一个数,这样接下来可以猜两次达到目标。先看中间两个询问的返回结果,如果相同,则这两个正确性必须都是T,这就至少淘汰了一个数了;否则中间两个询问正确性必是一真一假,这时再看边上两个询问,如果返回结果相同,则也必须都是T,成功淘汰至少一个数;剩下的情况就是正确性为YNYN或NYNY了(两种对称的),用和上面类似的方法分类讨论一下,发现答案不可能是2,成功淘汰一个。总操作次数似乎最多刚好82次。

我一开始以为easy version是不用可以猜两次的特殊条件的,结果发现连n=2都做不了

时间复杂度\(O(能过)\),反正不超过\(38\cdot 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;
vector <int> v;

vector <int> comb(vector <int> aa,vector <int> bb)
{
  rep(i,bb.size()) aa.pb(bb[i]);
  return aa;
}

int ask(vector <int> aa)
{
  cout<<"? "<<aa.size()<<' ';
  rep(i,aa.size()) cout<<aa[i]<<' ';cout<<endl;cout.flush();
  string s;cin>>s;return s[0]=='Y' ? 1:0;
}

void guess(int x)
{
  cout<<"! "<<x<<endl;cout.flush();
  string s;cin>>s;
  if(s[1]==')') exit(0);
}

int main()
{
  ios::sync_with_stdio(false);
  cin>>n;
  repn(i,n) v.pb(i);
  while(v.size()>3)
  {
    int bas=v.size()/4,mo=bas+1,mocnt=v.size()%4;
    vector <int> vv[4];
    int cur=0;
    rep(i,4)
    {
      if(i<mocnt) rep(j,mo) vv[i].pb(v[cur++]);
      else rep(j,bas) vv[i].pb(v[cur++]);
    }
    int r1=ask(comb(vv[0],vv[1])),r2=ask(comb(vv[0],vv[2]));
    if(r1&&r2) v=comb(vv[0],comb(vv[1],vv[2]));
    else if(r1&& !r2) v=comb(vv[0],comb(vv[1],vv[3]));
    else if(!r1&&r2) v=comb(vv[0],comb(vv[2],vv[3]));
    else v=comb(vv[1],comb(vv[2],vv[3]));
  }
  if(v.size()==3)
  {
    int r1=ask({v[0]}),r2=ask({v[1]}),r3=ask({v[1]}),r4=ask({v[0]});
    if(r2==r3)
    {
      if(r2==1) guess(v[1]);
      else guess(v[0]),guess(v[2]);
    }
    else if(r1==r4)
    {
      if(r1==1) guess(v[0]);
      else guess(v[1]),guess(v[2]);
    }
    else guess(v[0]),guess(v[2]);
  }
  else rep(i,v.size()) guess(v[i]);
  return 0;
}

E2. Joking (Hard Version)

懒得看题解了,不会。


F. Kazaee

很多不同种类的数,判断是否都合法,取模,考虑哈希(提到哈希大家应该都会做了吧)。

先把序列中所有出现过的数都离散化。给每一种值分配一种随机的权值,一个区间的哈希值就是这个区间内所有数的权值之和。这里哈希值不对任何东西取模,也不能自然溢出,要保证分配的权值不太大(比如1e9就差不多)。对于一个合法的区间,容易发现它的哈希值对当前询问中的k取模一定为0;如果这个区间不合法,发现可以认为这个区间的哈希值是随机的,也就是对k取模的值在\([0,k-1]\)中随机,如果哈希值对k取模的值确实不为0,我们就发现了这个区间不合法。但是不合法区间的哈希值也可能刚好与k同余,比如k=2时甚至有\(\frac 12\)的概率发生。那我们干脆对这个序列多做几次哈希\(\to\)筛不合法区间的过程,比如30次,那一个不合法区间没被筛出来的概率就是\(\frac 1{2^{30}}\),所有不合法区间都被筛出来的概率是\((1-\frac1{2^{30}})^q\),这个概率是0.9999这个级别的,如果还wa可能是你脸太黑了

树状数组维护哈希值,总时间复杂度\(O(30\cdot qlogn)\)

点击查看代码
#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;

mt19937 rnd(114514);
int n,q,a[300010],bad[300010],aa[300010];
LL H[600010];
vector <int> dsc;
vector <pair <pii,pii> > que;

namespace bit
{
  LL dat[300010];
  LL lowbit(LL k){return k&-k;}
  void upd(LL k,LL val)
  {
    while(k<=n)
    {
      dat[k]+=val;
      k+=lowbit(k);
    }
  }
  LL get(LL k)
  {
    LL ret=0;
    while(k>0)
    {
      ret+=dat[k];
      k-=lowbit(k);
    }
    return ret;
  }
}

int main()
{
  cin>>n>>q;
  rep(i,n) scanf("%d",&a[i]),dsc.pb(a[i]);
  int x,y,z;
  rep(i,q)
  {
    scanf("%d",&x);
    if(x==1)
    {
      scanf("%d%d",&x,&y);--x;dsc.pb(y);
      que.pb(mpr(mpr(1,0),mpr(x,y)));
    }
    else
    {
      scanf("%d%d%d",&x,&y,&z);--x;--y;
      que.pb(mpr(mpr(2,x),mpr(y,z)));
    }
  }
  sort(dsc.begin(),dsc.end());dsc.erase(unique(dsc.begin(),dsc.end()),dsc.end());
  rep(i,n) a[i]=lower_bound(dsc.begin(),dsc.end(),a[i])-dsc.begin();
  rep(i,q) if(que[i].fi.fi==1) que[i].se.se=lower_bound(dsc.begin(),dsc.end(),que[i].se.se)-dsc.begin();
  rep(ti,30)
  {
    rep(i,dsc.size()) H[i]=rnd();
    rep(i,n+3) bit::dat[i]=0;
    rep(i,n) aa[i]=a[i],bit::upd(i+1,H[a[i]]);
    rep(i,q)
    {
      if(que[i].fi.fi==1)
      {
        bit::upd(que[i].se.fi+1,-H[aa[que[i].se.fi]]);
        aa[que[i].se.fi]=que[i].se.se;
        bit::upd(que[i].se.fi+1,H[aa[que[i].se.fi]]);
      }
      else if(bad[i]==0)
      {
        LL val=bit::get(que[i].se.fi+1)-bit::get(que[i].fi.se);
        if(val%que[i].se.se!=0) bad[i]=1;
      }
    }
  }
  rep(i,q) if(que[i].fi.fi==2) puts(bad[i] ? "NO":"YES"); 
  return 0;
}
posted @ 2022-10-16 14:16  LegendStane  阅读(337)  评论(1)    收藏  举报