Nowcoder | [题解-N189]牛客OI赛制测试赛3
这场说实话确实水(逃*1),表示差一点就AK了(逃*2),然而被卡两个特判的我\(ssfd\)...\(qwq\)
表示这是第一次发整场比赛的题解...还请各位大佬原谅我太蒻写的垃圾啊\(qwq\)...
难度:据出题人之一\(sqn\)小\((da)\)姐\((ge)\)姐\((ge)\)说大概是难度倒序排列...但是个人感觉其实都不难(逃*3)...所以差一点就AK了(逃*4)
\(A\) 数字权重
题目描述:设一个\(n\)位数的最高位为\(a_1\),最低位为\(a_n\),对于给定的\(n,k(1<n\leq10^{13},|k|\leq10^{13})\),求不含前导\(0\)且满足\((\sum_{i=2}^{n}(a_i-a_{i-1}))=k\)的\(n\)位数的个数,结果对\(10^9+7\)取模
题目本质:\(imone\):"**题啊"(逃*5)
思路:看一下式子就显然了(逃*6)...不就是\(a_n-a_1=k\)嘛(逃*7)
详解:显然题目条件给出的\(k\)只对\(a_n\)和\(a_1\)产生限制,对于\(a_i(i\in[2,n-1])\)没有任何影响,所以对于所有的\(a_i(i\in[2,n-1])\)都有\(10\)种可能的结果\((a_i\in[0,9])\),由于\(a_n=a_1+k,a_1\in[1,9],a_n\in[0,9]\),所以分为三类讨论
- \(|k|\geq10:\)由于两数码的差不可能\(>9\),所以此时无解,输出\(0\)即可
- \(0\leq k\leq9:\)此时\(a_n\in[0,9],a_1\in[-k,9-k]\cap[1,9]\) \(\because k\in[0,9]\) \(\therefore (-k)\in[-9,0],(9-k)\in[0,9]\) \(\therefore a_1\in[1,9-k]\)
所以此时答案为\((9-k)\times 10^{n-2}\) - \(-9\leq k\leq-1:\)同理\(a_n\in[0,9],a_1\in[-k,9-k]\cap[1,9]\) \(\because k\in[-9,-1]\) \(\therefore (-k)\in[1,9],(9-k)\in[10,18]\) \(\therefore a_1\in[-k,9]\)
所以此时答案为\((10+k)\times 10^{n-2}\)
\(AC\)代码:
#include<cstdio>//N189A
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int MOD=1e9+7;
long long n,k,ans=1;
long long qpow(long long base,long long u){
long long rep=1;
while(u){
if(u&1){
rep*=base;
rep%=MOD;
}
base*=base;
base%=MOD;
u>>=1;
}
return rep%MOD;
}
int main(){
scanf("%lld%lld",&n,&k);
if(k>9||k<-9){
printf("0\n");
return 0;
}
ans*=qpow(10,n-2);
if(k>=0){
ans*=(9-k);
ans%=MOD;
}
else{
ans*=(10+k);
ans%=MOD;
}
printf("%lld\n",ans);
return 0;
}
\(B\) 毒瘤\(xor\)
题目描述:对于给定的数列\(a_1,a_2,\dots,a_n\)和\(q\)次询问\([l,r]\)求一个数\(x\)满足\(0\leq x<2^{31}\)且\(\sum_{i=l}^{r}x\oplus a_i\)最大,\(\oplus\)表示异或操作
题目本质:把所有数按照二进制打出来找规律啊(逃*8)
思路:如果用样例按二进制位找规律不难发现可以预处理所有二进制位上\(1\)的个数的前缀和,对于每次询问求一次区间和,进而判断\(x\)的对应二进制位的\(01\)情况(口胡)
详解:考虑异或操作的实质可以知道,所求的\(x\)和所有的\(a_i(i\in[l,r])\)对应的二进制位相异的个数应当尽可能多,(因为出题人的原因一开始写的是求最小的\(x\)...所以后面就按照写的时候的思路讲...其实都无所谓...毕竟\(spj\)修好了)所以预处理所有的\(a_i(i\in[1,n])\)二进制位上\(1\)的个数的前缀和,用树状数组求区间和,显然,当对应二进制位上\(1\)的个数不少于\(0\)的个数时\(x\)的对应二进制位为\(0\),反之为\(1\),在此策略下所求的\(x\)为满足条件的最小解
\(AC\)代码:
#include<cstdio>//N189B
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define lowbit(x) x&-x
using namespace std;
const int N=1e5+5;
int n,q,ql,qr,qlen,ans;
int a[N],c[N][32],base[32];
void add(int u,int v){
while(u<=n){
c[u][v]++;
u+=lowbit(u);
}
}
int sum(int u,int v){
int rep=0;
while(u){
rep+=c[u][v];
u-=lowbit(u);
}
return rep;
}
void pre(){
for(int i=0;i<=30;i++){
base[i]=(1<<i);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
pre();
for(int i=1;i<=n;i++){
for(int j=0;j<=30;j++){
if(a[i]&base[j]){
add(i,j);
}
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
ans=2147483647;
scanf("%d%d",&ql,&qr);
qlen=(qr-ql+2)>>1;
for(int j=0;j<=30;j++){
if(sum(qr,j)-sum(ql-1,j)>=qlen){
ans-=base[j];
}
}
printf("%d\n",ans);
}
return 0;
}
\(C\) 硬币游戏
题目描述:给定一个整数\(n(n\leq10^6)\)和两个长度为\(2n\)的字符串\(s,t\)(只包含\(U,D\))分别对应\(clccle\)和\(sarlendy\)面前的硬币,从\(clccle\)开始取,每次只能取两个人之前都没取过的位置的硬币,如果所取硬币朝上\((U)\)的话记为\(1\),所取硬币朝下\((D)\)的话记为\(0\),这样\(n\)次后两个人就得到了长度为\(n\)的数字串,若\(clccle\)的数字串大输出\(clccle\ trl!\),若\(sarlendy\)的数字串大输出\(sarlendy\ tql!\),若一样大输出\(orz\ sarlendy!\)
题目本质:贪心选\(U\)判断两个人最大能选取到的\(U\)的个数
思路:先选对应位置为\(U\ U\)的位置,然后选\(U\ D/D\ U\)(对应选取各自的\(U\)),剩下的\(D\ D\)可以直接忽略(对最终结果无影响),不过...如果\(clccle\)能取到的\(U\)比\(sarlendy\)少\(1\)个,\(clccle\)可以先手阻止\(sarlendy\)选\(U\)从而平局
详解:读入\(s,t\)后遍历\(1-2n\)对四种情况分别统计\(cnt\)可以分为以下几种情况
- \(cnt_{U\ U}\)为奇数
此时显然\(clccle\)能取到的\(U\)比\(sarlendy\)多\(1\)个- \(cnt_{U\ D}+1=cnt_{D\ U}||cnt_{U\ D}+2=cnt_{D\ U}\)
此时两人能取到的\(U\)一样多 - \(cnt_{U\ D}+1>cnt_{D\ U}\)
此时\(clccle\)能取到的\(U\)多 - \(cnt_{U\ D}+2<cnt_{D\ U}\)
此时\(sarlendy\)能取到的\(U\)多
- \(cnt_{U\ D}+1=cnt_{D\ U}||cnt_{U\ D}+2=cnt_{D\ U}\)
- \(cnt_{U\ U}\)为偶数
此时显然\(clccle\)能取到的\(U\)和\(sarlendy\)一样多- \(cnt_{U\ D}=cnt_{D\ U}||cnt_{U\ D}+1=cnt_{D\ U}\)
此时两人能取到的\(U\)一样多 - \(cnt_{U\ D}>cnt_{D\ U}\)
此时\(clccle\)能取到的\(U\)多 - \(cnt_{U\ D}+1<cnt_{D\ U}\)
此时\(sarlendy\)能取到的\(U\)多
- \(cnt_{U\ D}=cnt_{D\ U}||cnt_{U\ D}+1=cnt_{D\ U}\)
\(AC\)代码:
#include<cstdio>//N189C
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int N=2e6+6;
int n,cntuu,cntud,cntdu,cntdd;
char s[N],t[N];
int main(){
scanf("%d",&n);
scanf("%s",s+1);
scanf("%s",t+1);
for(int i=1;i<=(n<<1);i++){
if(s[i]=='U'&&t[i]=='U'){
cntuu++;
continue;
}
if(s[i]=='U'&&t[i]=='D'){
cntud++;
continue;
}
if(s[i]=='D'&&t[i]=='U'){
cntdu++;
continue;
}
if(s[i]=='D'&&t[i]=='D'){
cntdd++;
continue;
}
}
if(cntuu&1){
if(cntdu==cntud+1||cntdu==cntud+2){
printf("orz sarlendy!\n");
return 0;
}
if(cntud+1>cntdu){
printf("clccle trl!\n");
return 0;
}
if(cntud+2<cntdu){
printf("sarlendy tql!\n");
return 0;
}
}
else{
if(cntdu==cntud||cntdu==cntud+1){
printf("orz sarlendy!\n");
return 0;
}
if(cntud>cntdu){
printf("clccle trl!\n");
return 0;
}
if(cntud+1<cntdu){
printf("sarlendy tql!\n");
return 0;
}
}
return 0;
}
\(D\) 粉樱花之恋
题目描述:不可描述的情节(逃*9)
题目本质:\(Fibonacci\)数列求和
思路:显然的线性递推当然要用矩阵加速啦\(qwq\)...所以...这题显然巨水(逃*10)...用\(dummyummy\)的话说:"粘个矩阵快速幂的板子不就行了吗"...
详解:考虑矩阵加速求\(Fibonacci\)数列的方法,用\(2\times 1\)的矩阵$$\begin{vmatrix}ans_0 \ ans_1 \end{vmatrix}$$存结果,以\(2\times 2\)的矩阵$$\begin{vmatrix}1&1\1&0\end{vmatrix}$$为底,有$$\begin{vmatrix}fib_{n-1}\fib_{n-2}\end{vmatrix}\times \begin{vmatrix}1&1\1&0\end{vmatrix}=\begin{vmatrix}fib_{n}\fib_{n-1}\end{vmatrix}$$类似地,我们可以推出\(Fibonacci\)数列求和的矩阵,相对于求\(Fibonacci\)数列,求和只是多了一个存\(sum\)的位置,于是可以用\(3\times 1\)的矩阵$$\begin{vmatrix}ans_0 \ ans_1 \ans_2\end{vmatrix}$$存结果,以\(3\times 3\)的矩阵$$\begin{vmatrix}1&1&1\0&1&1\0&1&0\end{vmatrix}$$为底,有$$\begin{vmatrix}sum_{n-1} \ fib_{n-1} \fib_{n-2}\end{vmatrix}\times \begin{vmatrix}1&1&1\0&1&1\0&1&0\end{vmatrix}=\begin{vmatrix}sum_{n-1}+(fib_{n-1}+fib_{n-2}) \ fib_{n-1}+fib_{n-2} \fib_{n-1}\end{vmatrix}$$因为\(fib_n=fib_{n-1}+fib_{n-2}\),所以 $$\begin{vmatrix}sum_{n-1} \ fib_{n-1} \fib_{n-2}\end{vmatrix}\times \begin{vmatrix}1&1&1\0&1&1\0&1&0\end{vmatrix}=\begin{vmatrix}sum_{n-1}+fib_n \ fib_n \fib_{n-1}\end{vmatrix}=\begin{vmatrix}sum_n \ fib_n \fib_{n-1}\end{vmatrix}$$在找到结果矩阵和基底矩阵之后,剩下的也就只有裸的矩阵快速幂了(是不是非常简单啊(逃*11))...因为初始化结果矩阵为$$\begin{vmatrix}sum_2=(fib_1+fib_2)\ fib_2 \fib_1\end{vmatrix}=\begin{vmatrix}2\1\1\end{vmatrix}$$所以在此还需要特判\(0(ans=1)\)和\(1(ans=2)\)的情况(表示挂在\(0\)上能哭死\(qwqwqwq\))
\(AC\)代码:
#include<cstdio>//N189D
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int MOD=998244353;
long long n;
long long base[3][3],rep33[3][3],ans[3],rep13[3];
void pre(){
base[0][0]=1;
base[0][1]=1;
base[0][2]=1;
base[1][0]=0;
base[1][1]=1;
base[1][2]=1;
base[2][0]=0;
base[2][1]=1;
base[2][2]=0;
ans[0]=2;
ans[1]=1;
ans[2]=1;
}
void mula(){
memset(rep13,0,sizeof rep13);
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
rep13[i]+=base[i][j]*ans[j];
rep13[i]%=MOD;
}
}
for(int i=0;i<3;i++){
ans[i]=rep13[i];
}
}
void mulb(){
memset(rep33,0,sizeof rep33);
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
rep33[i][j]+=base[i][k]*base[k][j];
rep33[i][j]%=MOD;
}
}
}
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
base[i][j]=rep33[i][j];
}
}
}
void qpow(long long u){
while(u){
if(u&1){
mula();
}
mulb();
u>>=1;
}
}
int main(){
pre();
scanf("%lld",&n);
if(n==0){
printf("1\n");
return 0;
}
if(n==1){
printf("2\n");
return 0;
}
qpow(n-1);
printf("%lld\n",ans[0]);
return 0;
}
\(E\) 符合条件的整数
题目描述:求在\([2^n,2^m)(0\leq n<m\leq 65)\)上有多少个整数\(k\)满足\(k\equiv 1(mod\ 7)\)
题目本质:过于困(rui)难(zhi)的数学题
思路:直接算啊(逃*12)
详解:看到数据范围只有\(65\)显然可以\(O(n)\)跑暴力啊(逃*13),算出所有的区间\([1,2^i)\)上满足条件的\(k\)个数\(ans_i(i\in[0,65])\),对于询问\(n,m\)只需直接输出\(ans_m-ans_n\)即可,对于\(2^{65}\)会爆\(long\ long\)这件事,表示把除以\(7\)的商和余数分开存就不会爆,毕竟\(\frac{2^{65}}{7}\)并不会爆\(long\ long\)(其实...数据点根本就没卡满点...所以直接跑\(long\ long\)的也都过了\(qwq\))
\(AC\)代码:
#include<cstdio>//N189E
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
int n,m;
long long s[70],ys[70];
void pre(){
s[0]=0;ys[0]=1;
for(int i=1;i<=65;i++){
s[i]=s[i-1]<<1;
ys[i]=ys[i-1]<<1;
if(ys[i]>6){
s[i]+=ys[i]/7;
ys[i]%=7;
}
}
}
long long solve(){
ys[n]+=5;
ys[m]+=5;
if(ys[n]>6){
s[n]+=ys[n]/7;
ys[n]%=7;
}
if(ys[m]>6){
s[m]+=ys[m]/7;
ys[m]%=7;
}
return s[m]-s[n];
}
int main(){
scanf("%d%d",&n,&m);
pre();
printf("%lld\n",solve());
return 0;
}
\(F\) 可爱即正义
题目描述:交换字符串\(s\)中的两个字符\(s_i,s_j(i\neq j)\)使得\(s\)中不会出现\(suqingnianloveskirito\),如果不能实现输出\(No\),否则输出\(Yes\)并在第二行输出\(i,j\)
题目本质:字符串匹配
思路:看见数据范围之后暴力跑啊(逃*14)
详解:观察模式串\(suqingnianloveskirito\)不难发现根本没有前缀和后缀相同的部分,所以如果出现模式串一定是整串出现而不受前后字符影响并且随便改一下就行了(口胡)...又因为只能交换一次,所以当模式串出现次数\(t\geq3\)时显然输出\(No\),此时剩下的情况便只有\(t=0,1,2\)三种了
- \(t=2:\)此时显然可以交换第一次出现的模式串的第一个字符和第二次出现的模式串的第二个字符,所以只需要知道两次出现时的位置即可
- \(t=1:\)和\(t=2\)时类似,交换出现的模式串的第一位和第二位即可
- \(t=0:\)看起来本来就没有还要再交换一次
(sqn真(shi)可(zhen)爱(duo)...(逃*8))...这可交换哪两位好啊\(qwq\)...既然决定这么困难...那就交给\(rand()\)好了srand(prime with strlen()==8),为了保证交换之后的正确性还是再检验一下比较好
\(AC\)代码:
#include<cstdio>//N189F
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int L=1e6+6;
int len,lens=21,ap,poi,poii;
char ch[L],s[24]={' ','s','u','q','i','n','g','n','i','a','n','l','o','v','e','s','k','i','r','i','t','o'};
bool check(int u){
for(int i=u,j=1;j<=21;i++,j++){
if(ch[i]!=s[j]){
return false;
}
}
return true;
}
int main(){
srand(19260817);
scanf("%s",ch+1);
len=(int)strlen(ch+1);
if(len<21){
printf("Yes\n1 2\n");
return 0;
}
for(int i=1;i<=len-20;i++){
if(check(i)){
if(ap>1){
printf("No\n");
return 0;
}
if(ap==1){
ap++;
poii=i;
}
if(ap==0){
ap++;
poi=i;
}
}
}
if(ap==1){
printf("Yes\n");
printf("%d %d\n",poi,poi+1);
return 0;
}
if(ap==2){
printf("Yes\n");
printf("%d %d\n",poi,poii+1);
return 0;
}
while(1){
int u=(rand()*rand())%len+1,v=(rand()*rand())%len+1,flag=1;
if(u==v){
continue;
}
char jh=ch[u];
ch[u]=ch[v];
ch[v]=jh;
for(int i=1;i<=len-20;i++){
if(check(i)){
ch[v]=ch[u];
ch[u]=jh;
flag=0;
break;
}
}
if(flag){
printf("Yes\n");
printf("%d %d\n",u,v);
return 0;
}
}
return 0;
}