欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

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

最编程 2024-01-18 13:35:21
...

点我看题

A - Max Mod Min

非常诈骗。一开始以为要观察什么神奇的性质,后来发现直接模拟就行了。可以证明总操作次数是\(O(nlog a_i)\)的。具体就是,每次操作都会有一个数a被b取模,然后变成a%b。注意到a%b是\(\leq \frac a2\)的,并且a被操作之后会变成整个数据最小的数,作为下一轮的b。所以把原数组排序后,最小值的位置是不断往左移的,每次移动1个位置,直接模拟即可。

时间复杂度\(O(nlog a_i)\)

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

using namespace std;

int n,a[200010];
multiset <int> st;

int main()
{
  cin>>n;
  rep(i,n) scanf("%d",&a[i]),st.insert(-a[i]);
  int ans=0;
  while(st.size()>1)
  {
    int val=-*st.begin(),dv=-*st.rbegin();
    st.erase(st.begin());
    val%=dv;
    if(val>0) st.insert(-val);
    ++ans;
  }
  cout<<ans<<endl;
  return 0;
}

B - Swap to Sort

数值分成2类,奇数应在奇数位置,偶数应在偶数位置。但是初始会有一些奇数在偶数位置,也会有一些偶数在奇数位置。容易发现这两类数的个数是相等的,令其中一类的个数为k。A类操作的作用就是把一个奇数位置的偶数和一个偶数位置的奇数交换。发现只要k次A操作就可以让所有奇数在奇数位置,偶数在偶数位置,完成这一步后对所有奇数位置/所有偶数位置分别用B操作冒泡排序即可,一共40000次操作左右;且这k次A操作不可缺少。其他工作都可以用B操作完成。让所有奇数在奇数位置,偶数在偶数位置的具体做法是:先随便找一个在偶数位置的奇数,再随便找一个奇数位置的偶数,用B操作把它们换到相邻,然后一次A即可,一共40000次左右。这样总操作数不会超过100000。

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

using namespace std;

int n,p[410];
vector <pii> ans;

void swpA(int i)
{
  swap(p[i],p[i+1]);
  ans.pb(mpr(0,i));
}
void swpB(int i)
{
  swap(p[i],p[i+2]);
  ans.pb(mpr(1,i));
}

void isort(int w)
{
  vector <int> poss;
  repn(i,n) if(i%2==w) poss.pb(i);
  while(true)
  {
    bool hv=false;
    rep(i,poss.size()-1) if(p[poss[i]]>p[poss[i+1]])
    {
      swpB(poss[i]);
      hv=true;break;
    }
    if(!hv) break;
  }
}

int main()
{
  cin>>n;
  repn(i,n) cin>>p[i];
  while(true)
  {
    bool hv=false;
    repn(i,n) if(i%2==0&&p[i]%2==1)
    {
      int to=-1;
      repn(j,n) if(j%2==1&&p[j]%2==0) to=j;
      if(to>i)
      {
        int stp=(to-i)/2,cur=i;
        rep(j,stp)
        {
          swpB(cur);
          cur+=2;
        }
        swpA(cur);
      }
      else
      {
        int stp=(i-to)/2,cur=i;
        rep(j,stp)
        {
          swpB(cur-2);
          cur-=2;
        }
        swpA(cur-1);
      }
      hv=true;break;
    }
    if(!hv) break;
  }
  isort(0);isort(1);
  cout<<ans.size()<<endl;
  rep(i,ans.size())
  {
    if(ans[i].fi==0) printf("A ");
    else printf("B ");
    printf("%d\n",ans[i].se);
  }
  return 0;
}

C - Min Diff Sum

考虑最终选出的所有位置,从小到大排序,令第\(\lfloor \frac n2 \rfloor\)个为x。则排序时排在x左边,且其位置的值\(<x\)的变量如果能往右移哪怕1个位置,也是好的,但是为什么不能移,因为它已经在对应线段的右端点了。排序时排在x右边的同理。这启发我们可以枚举所在的区间。把所有\(l_i\)\(r_i\)排序并去重,枚举排好的序列中相邻的2个位置\(a,b\),计算\(a \leq x \leq b\)时的最优答案。找出所有\(r_i < a\)的线段,它们对应的值只能选\(r_i\),令这样的线段有ca个。所有\(l_i > b\)的只能选\(l_i\),令这样的线段有cb个。其他的显然是选与x相同最好。如果\(ca \geq cb\),显然x选a比较好,否则x选b比较好。记录几个部分的线段数量、端点位置之和等信息即可\(O(1)\)计算。

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

using namespace std;

LL n,l[300010],r[300010],lsum=0,rsum=0,lcnt=0,rcnt=0,stat[300010],ans=LONG_LONG_MAX;
vector <pair <LL,pii> > op;
vector <LL> L;

int main()
{
  cin>>n;
  LL addi=0;
  rep(i,n)
  {
    scanf("%lld%lld",&l[i],&r[i]);
    op.pb(mpr(l[i],mpr(-1,i)));op.pb(mpr(r[i],mpr(1,i)));
    stat[i]=1;
    L.pb(l[i]);
  }
  sort(L.begin(),L.end());
  rep(i,L.size())
  {
    addi+=L[i]*rcnt-rsum;
    rsum+=L[i];++rcnt;
  }

  sort(op.begin(),op.end());
  rep(i,op.size()-1)
  {
    if(op[i].se.fi==-1)
    {
      stat[op[i].se.se]=0;
      --rcnt;rsum-=l[op[i].se.se];
      addi-=rsum-l[op[i].se.se]*rcnt;
    }
    else
    {
      stat[op[i].se.se]=1;
      addi+=r[op[i].se.se]*lcnt-lsum;
      ++lcnt;lsum+=r[op[i].se.se];
    }
    LL lb=op[i].fi,ub=op[i+1].fi,mid=n-lcnt-rcnt,act=(lcnt>=rcnt ? lb:ub);
    LL res=0;
    res+=act*(mid+rcnt)*lcnt-lsum*(mid+rcnt);
    res+=rsum*(mid+lcnt)-act*(mid+lcnt)*rcnt;
    ans=min(ans,res+addi);
    //cout<<lb<<' '<<ub<<' '<<lcnt<<' '<<rcnt<<' '<<res<<' '<<lsum<<' '<<rsum<<' '<<act<<endl;
  }
  cout<<ans<<endl;
  return 0;
}

D - Sets Scores

出这种代码只有1行的数学诈骗题有意思吗.jpg

atcoder出题真是越来越魔怔了,arc出屑题,abc出大量板子题

n个集合,会有n+1个空隙(包括头尾)。如果第i和第i+1个集合中有一个不同的元素x,就在它们两个集合之间的空隙中填上一个编号为x的标记。同时,把1号集合中含有的元素对应的标记都填到它前面的空隙中。n号集合同理,填到它后面的空隙中。注意到每种标记都有偶数个。题目中的要求等价于每两个相邻集合中间的空隙用有恰好1个标记,首尾空隙任意。题目要求的是\(\sum\prod_i i在所有集合中出现的次数总和\),它等价于我们枚举m个集合\(t_1 \cdots t_m\),表示要求\(i在t_i\)中出现,求出满足这个条件的填标记方案数,并对所有枚举方式求和。对于某种填标记方案,i在\(t_i\)中出现当且仅当\(t_i\)前面有奇数个i号标记。乍一看很难判断,但是其实我们只要确定哪些中间空隙(不包括首尾)含有i号标记,再确定\(t_i\),首尾空隙中是否有i号标记就已经确定了,因为别忘了每种标记都有偶数个。所以可以先枚举所有\(t_i\),方案数\(n^m\);再枚举每个中间空隙填了哪种类型的标记,方案数\(m^{n-1}\),两个乘起来即可。

时间复杂度\(O(logn+logm)\)

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

using namespace std;

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,m;

int main()
{
  cin>>n>>m;
  LL ans=qpow(m,n-1);
  (ans*=qpow(n,m))%=MOD;
  cout<<ans<<endl;
  return 0;
}

E - Examination

还是挺妙的,场上有100多个人切了,后来果然发现有原题????????????

判无解的方法大家肯定都会了,把a和b分别从小到大排序,如果有一位\(a_i < b_i\)就无解。

有解的情况,我们考虑找出一个集合,它里面的元素可能会互相交换分数;剩下的元素分数都不变,自给自足,并给答案贡献1,最优解显然可以通过找出一个最小的合法集合来确定。

显然,所有\(a_i < b_i\)的人都在这个集合里;所有\(a_i=b_i\)的人都不在这个集合里,因为它们如果在集合里,最低要求就是他自己本身已有的分数,什么贡献都不能有,还可能会侵占资源。其他\(a_i > b_i\)的,可以考虑把它们一个一个加入集合中,使得集合合法;同时也要使加入的元素尽量少。分析一下集合s合法的条件,其实是:对于任意i,满足\(\sum_j^{|s|} [b_j<i]-[a_j<i]\)。证明也比较显然,这样每一个\(a_i\)都能在他前面找到一个\(b_i\)与其匹配;不满足这个条件则不行。一开始集合中有所有\(a_i<b_i\)的人,考虑贪心向其中添加一些元素,使得其合法,且添加的元素最少。我们每次找到最小的i使得i不满足上面那个式子的条件。显然,我们需要一个\(b_j \leq i,a_j > i\)的元素来救。发现\(b_i\)的大小是无关紧要的,因为i之前的位置都已经满足上面式子的条件。所以我们肯定应该选择\(a_j\)最大的j添加。实现的话,从小到大把所有i扫描一遍即可,用一个优先队列维护可以加入集合的元素。代码很好写,具体实现可以看代码。

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

using namespace std;

int n,a[300010],b[300010],mp[600010],ans=0;
vector <int> as,bs,dsc,add[600010];
priority_queue <int> q;

int main()
{
  cin>>n;
  rep(i,n)
  {
    scanf("%d%d",&a[i],&b[i]);
    as.pb(a[i]);bs.pb(b[i]);
    dsc.pb(a[i]);dsc.pb(b[i]);
  }
  sort(as.begin(),as.end());sort(bs.begin(),bs.end());
  rep(i,n)
  {
    if(as[i]<bs[i])
    {
      puts("-1");
      return 0;
    }
  }
  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();
    b[i]=lower_bound(dsc.begin(),dsc.end(),b[i])-dsc.begin();
    if(a[i]<b[i]) --mp[a[i]],++mp[b[i]];
    else if(a[i]>b[i]) add[b[i]].pb(i);
    else ++ans;
  }
  int sum=0;
  rep(i,dsc.size())
  {
    sum+=mp[i];
    rep(j,add[i].size()) q.push(a[add[i][j]]);
    while(sum<0)
    {
      int f=q.top();q.pop();
      ++sum;--mp[f];
    }
  }
  ans+=q.size();
  cout<<ans<<endl;
  return 0;
}