【CF题解】Codeforces Round #721 (Div. 2) A-E

A. And Then There Were K

题意: 给一个n,求最小的k满足
n & (n−1) & (n−2) & (n−3) & … (k) = 0
思路:
因为是位运算,我们会发现在与的情况下,只需要出现一个0就可以消掉这一位上的数,我们会发现由于在数字不断减小的过程中,后面几位会不断地在0和1之间变换,那么最难被消除掉的位其实就是n最前面的1,然后我们会发现在某个数到形如1111的数过程中,0会不断地在出现过的位置上出现,所以,最后的答案就是小于n的最大的二进制形式为1111的数
代码:

#include
#include
#include
using namespace std;

int main()
{
    int t,n;
    cin>>t;
    while(t--)
    {
        cin>>n;
        if(n==1) printf("0\n");
        else {
        vector<int> res;
        while(n>0)
        {
            res.push_back(n%2);
            n/=2;
        }
        int num=(int)res.size()-1;
        int ans=0;
        for(int i=0;i<num;i++)
            ans=ans*2+1;
        printf("%d\n",ans);
        }
    }
    return 0;

}

B1. Palindrome Game (easy version)

题意: 给一个回文串,先手和后手有两种操作:
1.把一个0,变成一个1,花费1的代价
2.如果前一次操作不是2操作并且当前串不是个回文串,把当前串反转,不花费代价
思路: 我们会发现:

  1. 当0的个数为偶数时,先手只能填1,而后手为了赢,可以在先手填了1以后,把对称的也填掉,直到最后一步时,不填只翻转,就可以保证最后先手比后手多两个,在这种情况下,先手没有别的选择余地,必输如图:

【CF题解】Codeforces Round #721 (Div. 2) A-E_第1张图片

  1. 当0的个数为奇数时,也就是在这个回文串正中间为0时,先手先把中间那个位置填掉,那么就变成后手面临着第一种情况,这种情况下,先手就变成必胜了
    代码:
#include
#include
#include
using namespace std;

int main()
{
    int t,n;
    char s[1010];
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        scanf("%s",s);
        int num=0;
        for(int i=0;i<n;i++)
            if(s[i]=='0') num++;
        if(num==1) printf("BOB\n");
        else if(num%2==0) printf("BOB\n");
        else printf("ALICE\n");
        
    }
    return 0;

}

B2. Palindrome Game (hard version)

题意: 跟B1一样,只不过给的串不一定是回文串了
思路: 是回文串的情况如B1,

  1. 现在就来考虑不是回文串的情况,我们会发现,在成为回文串之前,先手可以一直翻转,后手就只能填1,但是先手为了必胜,可以采取在还差一个的时候就提前把它变成回文串,这样可以保证后手翻转不了,使得先手和后手之间的差值保持不变,所以最后还是先手赢,如图:
    【CF题解】Codeforces Round #721 (Div. 2) A-E_第2张图片
  2. 只有当只有两个0,并且只要填补其中一个0就可以变成回文串的时候才会产生平局的情况
    代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e5+100;

char s[1010];
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        scanf("%s",s);
        int f=0;
        int num0=0;
        for(int i=0;i<n;i++)
        {
            if(s[i]=='0') num0++;
            if(s[i]=='0'&&s[i]!=s[n-1-i]) f++;
        }
        if(f==1&&num0==2)
            printf("DRAW\n");
        else if(f>0)
        {
            printf("ALICE\n");
        }
        else
        {
            if(num0==1) printf("BOB\n");
            else if(num0%2==0) printf("BOB\n");
            else printf("ALICE\n");
        }
    }
    return 0;
}

C. Sequence Pair Weight

题意: 定义sum为一个数列中相同的数在不同位置的对数,问对于一个数列,它的串及其所有子串的sum和为多少
思路:
对于样例一,我们可以这样来考虑:
1 , 2 , 1 , 1
他们对应的位置是1 ,2 ,3 ,4
对于(1,3)这一对,会出现在【1,3】和【1,4】中,所以贡献为2(1 * 2 )
对于(1,4)这一对,会出现在【1,4】中,所以贡献为1(1 * 1)
对于(3,4)这一对,会出现在【1,4】【2,4】【3,4】中,所以贡献为3(1 * 3)
然后我们会发现每一对的贡献其实就是(左边的个数)*(右边的个数)
这样显然复杂度是O(n^2)的,我们可以把左端点统一一下,把右端点的答案用一个类似后缀和的数组预处理一下,就是最后的答案
代码:

#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e5+100;
int a[maxn],b[maxn];
int k;
ll hou[maxn];
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++){scanf("%d",&a[i]);b[i]=a[i];hou[i]=0;}
        sort(b,b+n);
        int k=unique(b,b+n)-b;
        vector<int> qaq[k+1];
        for(int i=0;i<n;i++)
        {
            a[i]=lower_bound(b,b+k,a[i])-b;
            qaq[a[i]].push_back(i);
        }
        for(int i=0;i<k;i++)
        {
            int num=(int)qaq[i].size();
            hou[qaq[i][num-1]]=0;
            for(int j=num-2;j>=0;j--)
            {
                hou[qaq[i][j]]=hou[qaq[i][j+1]]+n-qaq[i][j+1];
            }
        }
        ll ans=0;
        for(int i=0;i<n;i++)
        {
            ans+=(i+1)*hou[i];
        }
        cout<<ans<<endl;
    }
    return 0;

}

D. MEX Tree

题意: mex的定义:对于一个集合中的树,最小的没有出现过的非负整数
求一棵树上mex=0,1,2,…,n的简单路径各有多少条
思路: 首先显然我们会发现
第一个结论:

mex=0 无0
mex=1 有0 无1
mex=2 有0 ,1 无2
mex=3 有0 ,1,2 无3

这样显然我们会发现
(mex=k的路径条数)=(有[0,k-1]的路径条数)-(有[0,k]的路径条数)
第二个结论:
如果有[0,k]的路径存在,那么[0,k]这些数字必然会在一条链上
代码:

#include 
#include
#include
#include
#include
#include
#define ls k<<1
#define rs k<<1|1
using namespace std;
typedef long long ll;
const int maxn=2e5+100;
int t,n;
vector<int> G[maxn];
int fa[maxn],siz[maxn],vis[maxn];
ll ans[maxn];
int st,en;
void dfs(int x,int f)
{
    siz[x]=1;
    fa[x]=f;
    for(int i=0;i<G[x].size();i++)
    {
        int to=G[x][i];
        if(to==f) continue;
        dfs(to,x);
        siz[x]+=siz[to];
    }
}
ll cal()
{
    if(st==en)
    {
        ll ans=0;
        for(int i=0;i<G[st].size();i++)
        {
            int to=G[st][i];
            ans+=siz[to]*2+1ll*(siz[to])*(n-1-siz[to]);//子树到根和子树到其他子树
        }
        return ans/2;
    }
    return 1ll*siz[st]*siz[en];
}
int add(int x)
{ 
    if(vis[x]) return 1;
    int which=x;
    int sum=siz[x];
    while(vis[which]==0)
    {
        vis[which]=1;
        siz[fa[which]]-=sum;
        sum+=siz[fa[which]];
        which=fa[which];
    }
    //如果在两头,直接加上
    if(which==st)
    {
        st=x;
        return 1;
    }
    if(which==en)
    {
        en=x;
        return 1;
    }
    //如果不在链上并且不在不是接在两端
    return 0;
}
int main()
{
  scanf("%d",&t);
  while(t--)
  {
      scanf("%d",&n);
      for(int i=0;i<=n+1;i++)
      {
          G[i].clear();
          fa[i]=-1;
          vis[i]=0;
          siz[i]=0;
          ans[i]=0;
      }
      for(int i=0,u,v;i<n-1;i++)
      {
          scanf("%d%d",&u,&v);
          G[u].push_back(v);
          G[v].push_back(u);
      }
      dfs(0,-1);
      st=en=0;
      ans[0]=1ll*n*(n-1)/2;
      vis[0]=1;
      vis[n]=1;
      for(int i=1;i<=n;i++)
      {
          ans[i]=cal();
          if(!add(i)) break;
      }
      for(int i=0;i<=n;i++)
        printf("%lld ",ans[i]-ans[i+1]);
      printf("\n");
  }
  return 0;
}


E. Partition Game

题意:
把一个长度为n的串分成k个片段,每个片段的贡献为下式
在这里插入图片描述
求最小的贡献
思路:
显然我们可以得到一个状态转移方程
d p [ i ] [ j ] = m i n ( d p [ k ] [ j − 1 ] + c o s t [ k + 1 ] [ i ] ) ( 其 中 k < i ) dp[i][j]=min(dp[k][j-1]+cost[k+1][i]) (其中kdp[i][j]=min(dp[k][j1]+cost[k+1][i])(k<i)
但是这样的复杂度显然为O(k*n^2)
那么就考虑如何优化:
显然我们是要优化cost计算的过程,那么我们会发现
cost[i][j+1]可以从cost[i][j]得到:

  1. 跟a[j+1]相同的数出现在[i][j]之间,cost[i][j+1]=cost[i][j]+(j+1-距离j+1最近的出现跟a[j+1]相同的数的位置)
  2. 没有跟a[j+1]相同的数出现在[i][j]之间,cost[i][j+1]=cost[i][j]

那么显然,我们可以根据用i来转移,我们直接用线段树来维护dp[k][j-1]+cost[k+1][i],先把dp[k][j-1]都放上去,然后一步步向上加cost[k+1][i]

代码:

#include 
#include
#include
#include
#include
#include
#define ls k<<1
#define rs k<<1|1
using namespace std;
typedef long long ll;
const int maxn=2e5+100;
const ll inf=1ll<<60;
int n,m;
int a[maxn];
ll dp[maxn][110];
int bef[maxn];
int j;
struct
{
    int l,r;
    ll minn;
    ll lazy;
}node[maxn<<2];
void up(int k)
{
    node[k].minn=min(node[ls].minn,node[rs].minn);
}
void build(int k,int l,int r)
{
    node[k].l=l;node[k].r=r;
    node[k].lazy=0;
    if(l==r)
    {
        node[k].minn=dp[l][j-1];
        return ;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    up(k);
}
void push(int k)
{
    if(node[k].l==node[k].r) return ;
    if(node[k].lazy!=0)
    {
        node[ls].minn+=node[k].lazy;
        node[rs].minn+=node[k].lazy;
        node[ls].lazy+=node[k].lazy;
        node[rs].lazy+=node[k].lazy;
        node[k].lazy=0;
    }
}
ll query(int k,int l,int r)
{
    if(l<=node[k].l&&node[k].r<=r)
        return node[k].minn;
    push(k);
    int mid=node[k].l+node[k].r>>1;
    ll minn=inf;
    if(l<=mid) minn=min(minn,query(ls,l,r));
    if(mid+1<=r) minn=min(minn,query(rs,l,r));
    return minn;
}
void add(int k,int l,int r,int val)
{
    if(l<=node[k].l&&node[k].r<=r)
    {
        node[k].minn+=val;
        node[k].lazy+=val;
        return ;
    }
    push(k);
    int mid=node[k].l+node[k].r>>1;
    if(l<=mid) add(ls,l,r,val);
    if(mid+1<=r) add(rs,l,r,val);
    up(k);
}
int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%d",&a[i]);
  for(int i=1;i<=n;i++)
  {
      dp[i][1]+=dp[i-1][1];
      if(bef[a[i]]!=0)
        dp[i][1]+=i-bef[a[i]];
      bef[a[i]]=i;
  }
  for(j=2;j<=m;j++)
  {
      build(1,1,n);
      for(int i=1;i<=n;i++)
      {
          if(bef[a[i]]>1&&bef[a[i]]<i)
            add(1,1,bef[a[i]]-1,i-bef[a[i]]);
          if(i>=j)
            dp[i][j]=query(1,1,i-1);
          else dp[i][j]=inf;
          bef[a[i]]=i;
      }
  }
  printf("%lld\n",dp[n][m]);
  return 0;
}

你可能感兴趣的:(CF题解)