codeforces-round-714-(B到D) 题解

B-AND Sequences

题意:
给一个非负数组A,数组元素数量为n,数组下标从1到n,
在这个数组元素的全排列中,如果有一种排列满足下标i从
1到n都能满足:在这里插入图片描述
那么这种排列就满足我们美的要求。
最后的问题是有多少种这种排列。
思路:
这里的& 是位与运算,即设a=1,b=3
则 a&b等价于
01
11
&=>01
位运算有一个很有意思的地方,那就是如果a参与了位与运算,那么最后的结果一定是小于等于a的,
换句话说,位与运算只能使一个数变小,或不变(参与运算的另一个数全为1时)
官方题解有一个很有巧妙的证明:
在这里插入图片描述
我们记 PREi 为 a1&a2…&ai
SUFi 为 a(i+1)&a(i+2)…an (还不会用latex,所以有点丑,a(i+1)的意思是A数组中下标为i+1的数)
那么我们由定义知: PRE1=SUF2 PRE2=SUF3
而由位与运算的性质,我们知: PRE2 <=PRE1 (一个数经过位于运算会变小或不变,不会变大
因为 PRE2=PRE1&a(2))
SUF2<=SUF3 (与上面同理,因为SUF2=SUF3&a(2))
那么因为 PRE1=SUF2,PRE2=SUF3
所以我们有PRE2=PRE1=SUF1=SUF2
即我们要求的数组是满足,下标i从1到n-1都满足PREi 等于SUFi+1 等于 同一个数
这个数也可以很轻松的知道:a1或a(n)
那么很明显的,如果a1和a(n)不是这个数组的最小数的话,这个数组是不满足美的要求的(原因自己仔细思考一下)。
那么我们的最后的解题思路就是:
要使一个数组满足美的性质,那么数组最左和最右边的两个数一定要是相等的最小数a,并且对于数组中的每一个数b,都有 a&b=c 且c=a,因为c 一定小于等于a且小于等于b,因为b不是最小数,所以b>a,
那么c的范围就是<=a ,如果c小于a,那最后的结果一定是某个下标i,PRRi<a,则不满足条件。
最后
判断这个数组最小数有几个,分两种情况:
1只有一个最小数
2最小数有多个

对于第一种情况,因为最小数一定要两边各有一个,所以构造不出美的数组。
对于第二种情况,我们能得到的答案就是一个组合数,即最左和最右为最小数,中间的数全排列。

#include <iostream>
#include <algorithm>
using namespace std;
int test;
int n;
int nums[500000];
int mod;
long long min_num;
long long cnt;
void solve()
{

    for(int i=0;i<n;i++)
    {
        if((min_num&nums[i])<min_num)//如果最小数&nums[i]<最小数 则结果不存在
        {
            printf("0\n");
            return ;
        }
        else 
        if(nums[i]==min_num)//判断有几个最小数
        {
            cnt++;
        }
    }
    if(cnt<2) 
    {
        printf("0\n");
        return ;
    }

    long long ans=cnt*(cnt-1);//不用  除2 因为还有所选的两个最小数前后互换也是一种答案  所以不用乘2
    // ans的意思是  从cnt个最小数里挑出两个(并前后排列)有几种可能,
    long long base=1;
    while(base<=(n-2))//  求  !(n-2)
    {
       ans*=base;
       base++;
       ans%=mod;
    }//最后的结果是 ans*=!(n-2)
    printf("%lld\n",ans);
    return ;
    /*
C cnt 2  * !(n-2)
    */
}
int main()
{
    scanf("%d",&test);
   mod=1000000007;

    while(test--)
   {
       min_num=INT_MAX;
       cnt=0;
      scanf("%d",&n);
      for(int i=0;i<n;i++)
      {
          scanf("%d",&nums[i]);
          if(nums[i]<min_num) min_num=nums[i];//求最小数
      }
      solve();
   }
   
}

C . Add One

题意:
有一个数a,以a的每一位数位上的数为单位,一次加法操作使得每一数位上的数都加一,到10了就进位。
如 a为 9,加法操作次数为3
那么
9 0=>9
9 1=> 10
9 2=>21
9 3=>32
即进位产生的新位在后面的加法操作中也要执行加一操作
问给一个数a和一个加法操作总数m,最后这个数a有几位,输出位数

思路:
很快就想到可能是个dp,因为我们发现,一个数的当前状态是由上一个状态转移过来的,
我的思路是先求一个数为一位时,操作了m次有几位,最后再通过这个原数a有几位是这个数,就加上这个数的结果,
如 a是121
那么我们就先算出 1操作m次有几位,2操作m次有几位,然后统计相加。
我们定义pre[i][j] 为
位上数为j的总共有多少个,操作 i次的总位数
那么很容易想到,当j为2到9时,pre[i][j]=pre[i-1][j-1]
当 j为1时 pre[i][j]=pre[i-1][[0]+pre[i-1][9]
因为9加1也会进位产生1
当j为0时 pre[i][j]=pre[i-1][9]

#include <iostream>
#include <algorithm>
using namespace std;
int test;
long long dp[200100][10];
int n,m;
long long pre_sulution[200100][10];
int mod;
int total;
/*
dp[i][j]
数位上的数为j时 操作 i次的总位数


pre_sulution[i][j]:
保存的是最后的结果,即 从  j,经过i次操作后,有几个位
*/
void pre()
{
    mod=1000000007;
    total=200100;
        /*
优化
因为 当位上的数为2时,也是可以由位上的数为1转换而来的,
所以不需要专门每个数都要跑一遍,而是
直接跑一遍 0  其他的数可以由0 推出来
pre_sulution[0][1]=pre_sulution[1][0]

pre_sulution[0][9]=pre_sulution[1][8]

        */
        dp[0][0]=1;//初始为单个0 的情况
        for(int i=1;i<total;i++)//m的最大值位200000
      {
           dp[i][1]=dp[i-1][0]+dp[i-1][9];
           dp[i][1]%=mod;//防止溢出
           for(int j=0;j<10;j++)//从0到9
         {
             if(j!=1&&j!=0)
             {
                 dp[i][j]=dp[i-1][j-1];
             }
             else 
             {
                 if(j==0)
                 dp[i][j]=dp[i-1][9];
             }
             dp[i][j]%=mod;
         }
       for(int u=0;u<10;u++)
       pre_sulution[i][0]+=dp[i][u];//因为pre_sulution的定义,所以我们要累加经过i次操作后,位上数位j的有几位
       pre_sulution[i][0]%=mod;
       }
       for(int u=1;u<10;u++)
       for(int i=0;i<total-1;i++)
       {
          pre_sulution[i][u]=pre_sulution[i+1][u-1];//这里是一个优化,我们求出当第一个数是0,操作了total次以后数有几位
                                                    //第一个数为1到9时可以根据这个公式继续退出来
       }
      
}
void get_case()
{
   scanf("%d%d",&n,&m);
   int temp=n;
  long long ans_bit[10];
  for(int i=0;i<10;i++) ans_bit[i]=0;
   while(temp>0)
   {
       ans_bit[temp%10]++;//判断数位的数时哪些
       temp/=10;
   }

   long long ans=0;
   for(int i=0;i<10;i++)
   {

    ans+=(pre_sulution[m][i]*ans_bit[i])%mod;//累加结果
     ans%=mod;
   }
   printf("%lld\n",ans);
   return ;

}
int main()
{
    scanf("%d",&test);
     pre();//先打表求出每一种m,数0到数9有多少位,然后可以直接求
    while(test--)
    {
  get_case();
    }
    return 0;
} 

D. GCD and MST

题意可以看原题,比较容易理解
直接说思路:
我们每次都从还没加入mst的最小的数开始,试探性的往该数的左边所有数和右边所有数加边
最后判断加的边能不能生成一个mst,不能则加相应数量的权为p的边

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
struct node{
long long num,index;
bool operator<(node a) const
{
    if(num>a.num) return true;
    return false;
}
};
const int max_n=200005;
long long nums[max_n];
int father[max_n];
int find(int u)
{
   if(u==father[u]) return father[u];
   return father[u]=find(father[u]);
}
void uni(int u,int v)
{
    int f_u,f_v;
    f_u=find(u);
    f_v=find(v);
    if(f_u==f_v) return ;
    father[f_v]=f_u;
}

/*
从最小的数开始试探性的加边 ,类似于kruscal算法

*/
int n,test;

long long p;
priority_queue<node> que;//优先队列,每次弹出还没在mst中的最小的数
void  get_ans()
{
    long long ans=0;
    node now;
    int min_num;
    int find_ed=0;
     while(que.empty()==false)
     {
          now=que.top();//当前代表最小的数的节点
          que.pop();
             min_num=now.num;
             for(int u=now.index+1;u<=n;u++)//从当前节点开始,向右遍历,试探性加边
             {
                  if(nums[u]%min_num==0)//满足这个条件,说明可以加一条权重为min_num的边
                  {
                      if(find(u)!=find(now.index))//并查集判断当前节点是否已经再mst中
                      {
                          uni(u,now.index);//如果不在,则加入
                          ans+=min_num;
                          find_ed++;
                      }
                      else break;/*
                      这里是一个重要的优化,如果一个点的旁边的点已经被加入mst了,
                     因为发生这种情况只有一种可能,就是因为  u和  now.index 已经被一条边连接了
                     那么这条边的权值x一定小于  min_num,也就是此时 now点的权值,可以可以肯定
                     min_now%x==0 那么既然如此,也就没有必要继续遍历下去了,因为如果 后面的数不能整除 x,那么就更不可能整除 min_num
                      */
                      
                  }
                  else break;
             }
    
               for(int u=now.index-1;u>=1;u--)//从当前节点开始,向左遍历,试探性加边
             {
                  if(nums[u]%min_num==0)
                  {
                      if(find(u)!=find(now.index))
                      {
                          uni(u,now.index);  
                          ans+=min_num;
                          find_ed++;
                      }
                      else break;
                  }
                  else break;//优化
             }
     }
     long long delta=0;
     if(find_ed!=n-1)//优化,如果加了边不够生成mst,则要加对应的 权为p的边
     {
         delta=(n-1-find_ed)*p;
     }
     ans+=delta;
     printf("%lld\n",ans);
}


int main()
{
        scanf("%d",&test);
        while(test--)
        {
            scanf("%d%lld",&n,&p);
            for(int i=1;i<=n;i++) 
            {
                scanf("%d",&nums[i]);
            node temp;
             father[i]=i;
            if(nums[i]>=p) continue;//优化
            temp.num=nums[i];
            temp.index=i;
             que.push(temp);
           
            }
            get_ans();
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值