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();
}
}