Codeforces Round 1019 (Div. 2) CF 2103 A B C D F 题解

点我看题

A. Common Multiple

注意到对于一个子序列\(\{x_i\}\),其存在对应的合法的\(\{y_i\}\)当且仅当\(\{x_i\}\)中元素各不相同。要使合法的Subsequence长度最大,取原序列中所有值不同的元素即可。因此直接统计原序列中不同值的数量。

时间复杂度可以是\(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 <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()
{
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
}
void termin()
{
  std::cout<<"\n\nEXECUTION TERMINATED";
  exit(0);
}

using namespace std;

int t,n,a[110];
map <int,int> mp;

int main()
{
  //fileio();

  cin>>t;
  while(t--)
  {
    cin>>n;
    mp.clear();
    rep(i,n)
    {
      cin>>a[i];
      mp[a[i]]++;
    }
    cout<<mp.size()<<endl;
  }

  return 0;
}

B. Binary Typewriter

"开局时手放在按钮0上"不太好处理,因此我们干脆在原串\(s\)最前方加上一个0,并加一个"翻转的子串不得包括\(s\)第一个元素"的限制,然后计算"将\(s\)的一个子串翻转后,相邻两个元素不同的次数最多减少几次"即可。

  • 在翻转的子串不包括末尾字符的情况下,我们将翻转视为在串中插入两个木板,然后将木板之间的部分翻转(注意此处强制木板之间元素多于一个,因为只翻转一个元素没有意义)。令左侧木板两侧的两个字符为\(ab\),右侧木板两侧的两个字符为\(cd\)。注意到只有\(ab=cd=01\)\(ab=cd=10\)时翻转才有益处(否则还不如干脆不翻转),具体来说,分别会使得相邻两个元素不同的次数减少2。因此只需检查原串中有没有独立(没有共用部分)的两个\(01\)或两个\(10\)即可。
  • 在翻转的子串包括末尾字符的情况下,可以枚举翻转区间的开头,然后对于每种情况单独计算相邻两个元素不同的次数减少了多少。

将以上两种情况取最优解即可。

时间复杂度\(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 <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()
{
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
}
void termin()
{
  std::cout<<"\n\nEXECUTION TERMINATED";
  exit(0);
}

using namespace std;

int t,n;
string s;

int main()
{
  //fileio();

  ios::sync_with_stdio(0);

  cin>>t;
  while(t--)
  {
    cin>>n>>s;
    s="0"+s;
    int ans=0;
    rep(i,s.size()-1) ans+=(s[i]!=s[i+1]);
    int c01=0,c10=0,mx=0;
    rep(i,s.size())
    {
      if(i-3>=0)
      {
        if(s[i-3]=='0'&&s[i-2]=='1') ++c01;
        if(s[i-3]=='1'&&s[i-2]=='0') ++c10;
      }
      if(i-1>=0)
      {
        if(s[i-1]=='0'&&s[i]=='1'&&c01) mx=max(mx,2);
        if(s[i-1]=='1'&&s[i]=='0'&&c10) mx=max(mx,2);
      }
    }
    repn(i,s.size()-2) if(s[i-1]!=s[i]&&s[i-1]==s.back()) mx=max(mx,1);
    cout<<ans-mx+s.size()-1<<endl;
  }

  return 0;
}

C. Median Splits

将原序列中\(\leq k\)的元素看作0,其余看作1。问题变为:能否将序列分成三个非空部分,满足其中至少两个中0的个数不少于1的个数。方便起见,我们将一个0的个数不少于1的个数的子串称为好的子串。如果答案为YES,令数组下标从1开始,有以下几种情况:

  • 可以做到左右两个部分为好的子串。令\(l=min(i|\{a_1\cdots a_i\}is\ good)\),不存在则为\(n+1\);令\(r=max(i|\{a_i\cdots a_n\}is\ good)\),不存在则为0。注意到可以做到左右两个部分为好的子串当且仅当\(l+1<r\)
  • 可以做到左中两个部分为好的子串。从左到右遍历备选的中间子串右端点\(r\),令\(\{a_1\cdots a_r\}\)中0的个数减去1的个数为\(c\)。则可以做到左中两个部分为好的子串且中间子串右端点为\(r\),当且仅当:\(\exist l<r\)满足\(\{a_1\cdots a_l\}\)中0的个数减去1的个数\(d\geq 0\),且\(c-d\geq 0\)。维护一个前缀中0的个数减去1的个数的前缀最小值即可\(O(n)\)处理这种情况。
  • 可以做到中右两个部分为好的子串。和上面一种情况类似。

依次检查上面所有三种情况能否做到即可。

时间复杂度\(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 <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()
{
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
}
void termin()
{
  std::cout<<"\n\nEXECUTION TERMINATED";
  exit(0);
}

using namespace std;

int t,n,k,a[200010];

bool check()
{
  int mn=1e9,c=0;
  rep(i,n-1)
  {
    c+=a[i];
    if(c>=0&&mn<=c) return true;
    if(c>=0) mn=min(mn,c);
  }
  return false;
}

int main()
{
  //fileio();

  ios::sync_with_stdio(0);
  cin>>t;
  while(t--)
  {
    cin>>n>>k;
    rep(i,n) cin>>a[i],a[i]=(a[i]<=k ? 1:-1);
    int f1=1e9,f2=-1e9,c=0;
    rep(i,n)
    {
      c+=a[i];
      if(c>=0)
      {
        f1=i;
        break;
      }
    }
    c=0;
    for(int i=n-1;i>=0;--i)
    {
      c+=a[i];
      if(c>=0)
      {
        f2=i;
        break;
      }
    }
    bool ok=f1<f2;
    ok|=check();
    reverse(a,a+n);
    ok|=check();
    if(ok) cout<<"YES\n";
    else cout<<"NO\n";
  }

  return 0;
}

D. Local Construction

将整个过程反向,我们的Operation 1就变成了:在一个序列中插入若干元素,满足插入后,新插入的元素都不是local minima,而之前插入的元素都是local minima。Operation 2也是类似。已知条件是每一轮插入中插入的所有元素的下标,以及它们插入的位置。现在要给每个插入的元素分配一个数值,使得满足以上所有要求。

考虑一轮插入,以Operation 1为例。将元素分为这一轮插入的(新元素)和之前就插入的(老元素)两种。注意到任意两个老元素之间都会有至少一个新元素插入,否则就会无解(因为不可能做到两个中间无新元素插入的老元素,满足他们两个在这一轮插入结束之后仍然都是local minima)。

考虑如下的构造思路:强制所有新元素最终被分配的数值都严格大于任何一个老元素,并且在这一轮插入之后所有挨着的"一串"新元素(每两个在插入前挨着的老元素之间,在插入之后都会出现这样一串新元素;序列的首尾也可能有这样的一串新元素),都满足其最终被分配的数值是从小到大排列;序列头部的一串新元素(如果存在)除外,这一串新元素我们强迫它们的最终数值从大到小排列。这样构造之后,发现完美满足"插入后,新插入的元素都不是local minima,而之前插入的元素都是local minima"。

尝试维护一个链表,做到在对所有Operation的计算结束后,链表中依次存储了\(n\)个元素的下标,我们只要按从左到右顺序给第\(i\)个下标分配数值\(i\)就能满足所有要求。如何维护呢?对于一次Operation 1,按照上面的构造思路,其实只要每次把所有新元素的下标按之前提到的顺序依次接在这个链表的右侧就可以了。对于Operation 2,则是把新元素的下标按照一定顺序接在链表的左侧。由于链表只有这两种操作,用双端队列维护即可。

时间复杂度可以是\(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 <LL,LL>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair

void fileio()
{
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
}
void termin()
{
  std::cout<<"\n\nEXECUTION TERMINATED";
  exit(0);
}

using namespace std;

int t,n,a[200010],ans[200010];
deque <int> dq;
map <int,vector <int> > mp;

int main()
{
  //fileio();

  ios::sync_with_stdio(0);

  cin>>t;
  while(t--)
  {
    cin>>n;
    dq.clear();
    mp.clear();
    int mn,mx;
    rep(i,n)
    {
      cin>>a[i];
      if(a[i]==-1) dq.pb(i),mn=mx=i;
      else mp[-a[i]].pb(i);
    }
    for(auto it:mp)
    {
      if((-it.fi)%2==1)
      {
        int mark=-1;
        rep(i,it.se.size()) if(it.se[i]<mn) mark=i;
        for(int i=mark;i>=0;--i) dq.pb(it.se[i]);
        for(int i=mark+1;i<it.se.size();++i) dq.pb(it.se[i]);
      }
      else
      {
        int mark=-1;
        rep(i,it.se.size()) if(it.se[i]<mn) mark=i;
        for(int i=mark;i>=0;--i) dq.push_front(it.se[i]);
        for(int i=mark+1;i<it.se.size();++i) dq.push_front(it.se[i]);
      }
      rep(i,it.se.size())
      {
        mn=min(mn,it.se[i]);
        mx=max(mx,it.se[i]);
      }
    }
    rep(i,n) ans[dq[i]]=i+1;
    rep(i,n) cout<<ans[i]<<" ";
    cout<<endl;
  }

  return 0;
}

F. Maximize Nor

先来看\(k=1\)的情况。对于一个长度为\(m\)的序列\(a\),令其nor值为\(v(v=0/1)\),观察会发现一些基础性质:

  • \(m=1\),有\(a=\{1\}\Leftrightarrow v=1\)
  • \(m=2\),有\(a=\{0,0\}\Leftrightarrow v=1\)
  • \(m>2\),分以下几种情况:
    • 末尾两元素为00,则\(v=\)去掉末尾两元素后的序列的nor值
    • 末尾两元素为10,则\(v=1\)
    • 否则,\(v=0\)

枚举一个区间的右端点\(r\),我们来观察一下对于任意\(l\),区间\([l,r]\)的nor值(设其为\(v\))如何。令数组下标从1开始,\(m\)\(r\)及其左侧最靠右的1的位置,若不存在则令\(m=0\)。根据上面发现的基础性质,有:

  • \(\forall l\in(m,r]\),若\(r-l\)为奇数,\(v=1\);否则,\(v=0\)
  • \(\forall l\leq m\),分以下两种情况:
    • \(r-m\)为奇数,有对于\(l=m,v=0\);对于\(l<m,v=1\)
    • \(r-m\)为偶数,有对于\(l=m,v=1\);对于\(l<m,v=0\)

因此,区间\([l,r]\)被分成了\(O(1)\)段(可能是1~3段,令其为\(\{[l,b_1-1],[b_1,b_2-1],\cdots,[b_k,r]\}\)),满足对于其中每一段\([lb_i,ub_i]\),存在\(v_0,v_1\)使得\(\forall l\in[lb_i,ub_i]\),若\(l\)为奇数则\(v=v_1\),若\(l\)为偶数则\(v=v_0\)。令位置\(i\)的答案为\(ans_i\)。对于每一段,我们找出其中最靠左的下标为奇数的位置\(p\),其可能是\(lb_i\)\(lb_i+1\),如果该区间太小不存在下标为奇数的位置则不进行接下来的操作(别问我为什么知道)。接下来对\([p,r]\)中所有位置用\(v_1\)更新其\(ans_i\),这部分可以用经典的multiset+插入删除离线操作的套路来实现;对于偶数位置也是类似的操作。

如果\(k>1\),上面的"分段理论"仍然成立(对每个bit分别处理即可),只是段的个数变成了\(O(k)\)。我们采取的方法相同。

时间复杂度\(O(nk\cdot log_2n)\)

点击查看代码
#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 pb push_back
#define mpr make_pair

void fileio()
{
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
}
void termin()
{
  std::cout<<"\n\nEXECUTION TERMINATED";
  exit(0);
}

using namespace std;

int t,n,k,a[100010],lst1[20],ans[100010];
vector <int> ins[100010],del[100010];
multiset <int> cur;

void add(pii &x,pii y)
{
  x.fi+=y.fi;
  x.se+=y.se;
}

void updInterval(int lb,int ub,int r,pii ad)
{
  //cout<<lb<<" "<<ub<<" "<<ad.fi<<" "<<ad.se<<endl;
  int l=lb;
  if(lb%2==1) ++lb;
  if(lb<=ub&&lb<=r)
  {
    ins[lb].pb(ad.fi);
    del[r+1].pb(ad.fi);
  }

  lb=l;
  if(lb%2==0) ++lb;
  if(lb<=ub&&lb<=r)
  {
    ins[lb].pb(ad.se);
    del[r+1].pb(ad.se);
  }
}

int main()
{
  //fileio();

  cin>>t;
  while(t--)
  {
    cin>>n>>k;
    rep(i,n) cin>>a[i];
    rep(i,n+5) ins[i].clear(),del[i].clear();
    rep(i,k+2) lst1[i]=-1;
    rep(i,n)
    {
      rep(j,k) if(a[i]&(1<<j)) lst1[j]=i;

      map <int,pii> mp;
      rep(j,k)
      {
        if((i-lst1[j])%2==1)
        {
          if(lst1[j]>=0)
          {
            if(i%2==1) add(mp[-lst1[j]],mpr(-(1<<j),0));
            else add(mp[-lst1[j]],mpr(0,-(1<<j)));
          }
          if(lst1[j]-1>=0) add(mp[-(lst1[j]-1)],mpr((1<<j),(1<<j)));
        }
        else
        {
          if(lst1[j]>=0)
          {
            if(i%2==1) add(mp[-lst1[j]],mpr(0,(1<<j)));
            else add(mp[-lst1[j]],mpr((1<<j),0));
          }
          if(lst1[j]-1>=0) add(mp[-(lst1[j]-1)],mpr(-(1<<j),-(1<<j)));
        }
      }

      pii ad=mpr((1<<k)-1,0);
      if(i%2==0) swap(ad.fi,ad.se);
      int ub=i;
      for(auto it:mp)
      {
        updInterval(-it.fi+1,ub,i,ad);
        add(ad,it.se);
        ub=-it.fi;
      }
      updInterval(0,ub,i,ad);
    }
    cur.clear();
    rep(i,n)
    {
      rep(j,ins[i].size()) cur.insert(ins[i][j]);
      rep(j,del[i].size()) cur.erase(cur.lower_bound(del[i][j]));
      ans[i]=max(a[i],(cur.empty() ? -114514:*cur.rbegin()));
    }
    rep(i,n) cout<<ans[i]<<" ";
    cout<<endl;
  }

  return 0;
}
posted @ 2025-04-23 01:19  LegendStane  阅读(455)  评论(0)    收藏  举报