牛客小白月赛31 题解
本人初三,第一次给牛客比赛写题解,轻喷。
因为先开了B以37秒的差距痛失rk1。
代码给的都是考场代码,可能比较丑。(有些后加的注释)
upd:D题证明补了一下。
A. A|B
考虑 和
的某一个二进制位
,只有在两个都是
的情况下满足
。
所以不存在一位使得 和
那一位都是
(不然和会大于 or)。
考虑从高位到低位依次填 ,可以发现如果这一位
是
就一定要填
,如果这一位
是
且目前填的前缀和
相等,那么也一定要填
(不然
就会
)。
所以令 表示填到了第
位,
的前
位与
的前
位是否相等,的方案数。
转移的时候从高位到低位枚举 ,把这一位填
的情况分别转移。
总复杂度 。
代码:
#include <iostream>
#include <cstring>
using namespace std;
int dp[50][2];
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
memset(dp,0,sizeof dp);
int a,b;
cin >> a >> b;
dp[30][1]=1;
for(int i=29;i>=0;i--)
{
if(a&(1<<i))//这一位必须填0
{
if(b&(1<<i)) dp[i][0]=dp[i+1][0]+dp[i+1][1];//x这一位是1的话,从这一位开始前缀就不再相等,所以都转移到dp[i][0]
else dp[i][0]=dp[i+1][0],dp[i][1]=dp[i+1][1];//x这一位是0,保持原来是否相等的状态
}
else
{
if(b&(1<<i)) dp[i][0]=dp[i+1][0]*2+dp[i+1][1],dp[i][1]=dp[i+1][1];//这一位可以任选
else dp[i][0]=dp[i+1][0]*2,dp[i][1]=dp[i+1][1];//x这一位是0的话,只有前缀不等的情况可以填1,否则只能填0
}
}
cout << dp[0][0]+dp[0][1]-1 << "\n";//题目要求b>=1,这里会多算一个0,所以要-1
}
return 0;
} B. A + B
模拟题。
先算出表达式的值,再按要求输出。
把输入分成 一段,每段是一个字符,这个字符只能是
。
维护一个变量 ,表示上一个加号之后到当前位置组成的多位数的值。
如果是加号的话,就把 加到答案里。
如果是数字 的话,就把
更新成
。
分辨这个字符具体是多少的话,就根据每个数字的特征判断一下,比如加号的右下角是 '.', 左边一列中间的字符是
。(具体可以看代码,这里就不一一列举了)
算出来答案之后按照格式从高位到低位输出,注意每一行的末尾没有'.',每组数据之间用回车隔开。
代码(考场代码比较丑):
#include <iostream>
using namespace std;
string a[10]={"###","..#","###","###","#.#","###","###","###","###","###"};
string b[10]={"#.#","..#","..#","..#","#.#","#..","#..","#.#","#.#","#.#"};
string c[10]={"#.#","..#","###","###","###","###","###","#.#","###","###"};
string d[10]={"#.#","..#","#..","..#","..#","..#","#.#","..#","#.#","..#"};
string e[10]={"###","..#","###","###","..#","###","###","..#","###","###"};//输出时候用的
int XX;
inline void print(int X,int x)//第X行
{
if(x>=10)print(X,x/10);
int t=x%10;
if(x==XX)
{
if(X==1) cout << a[t] << '\n';
if(X==2) cout << b[t] << '\n';
if(X==3) cout << c[t] << '\n';
if(X==4) cout << d[t] << '\n';
if(X==5) cout << e[t] << '\n';
}
else
{
if(X==1) cout << a[t] << '.';
if(X==2) cout << b[t] << '.';
if(X==3) cout << c[t] << '.';
if(X==4) cout << d[t] << '.';
if(X==5) cout << e[t] << '.';
}
}
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
string a,b,c,d,e;
cin >> a >> b >> c >> d >> e;
int X=0,Y=0,ans=0;
for(int i=0;i<a.size();i+=4)//四列一组
{
int x=0;
if(e[i+2]=='.')//加号
{
ans+=X,X=0;//把当前数加到答案
continue;
}//判断这个数是多少
if(a[i]=='.'&&c[i]=='.'&&e[i]=='.') x=1;
else if(b[i]=='#'&&c[i]=='#'&&d[i]=='.'&&e[i]=='.'&&c[i+1]=='.'&&a[i+1]=='#') x=7;
else if(e[i]=='.'&&d[i]=='.') x=4;
else if(b[i]=='.'&&d[i]=='.') x=3;
else if(c[i+1]=='.') x=0;
else if(b[i]=='#'&&b[i+2]=='#'&&d[i]=='#'&&d[i+2]=='#') x=8;
else if(b[i+2]=='.'&&d[i]=='.') x=5;
else if(b[i+2]=='.') x=6;
else if(b[i]=='.') x=2;
else x=9;
X*=10,X+=x;//更新当前值
}
ans+=X;//把最后一个数加到答案
XX=ans;
for(int i=1;i<=5;i++)
{
print(i,ans);
// cout << "\n";
}
cout << "\n";
}
return 0;
} C. 图像存储
第一问就对每个连通块 dfs 一遍。
考虑从一个连通块的左上角 dfs ,并且以任意固定优先级遍历,走的"路径"总是一样的。
比如优先级是右下上左:
1101 0111 1110
"路径"就是从 开始 右 下 右 右 上 回溯 回溯 下 左 左 回溯 回溯 回溯 回溯 回溯 回溯
给每个方向和“回溯”赋一个权值,这个操作序列就变成了 1 2 1 1 3 5 5 2 4 4 5 5 5 5 5 5。
如果两个连通块是全等的,那么操作序列一定是全等的,如果两个连通块不全等,那么操作序列一定不全等。
所以问题就变成了,给定若干个序列,判断有几个本质不同的序列,这个可以哈希+map或者别的方法判断。
总复杂度 其中
是连通块个数,而且跑不满。
#include <iostream>
#include <map>
#define mod 998244353
using namespace std;
int a[1005][1005],now1,now2;
const int fx[5]={0,0,1,-1},fy[5]={1,-1,0,0};
const int base1=27;
const int base2=23;
inline void dfs(int x,int y)
{
for(int i=0;i<=3;i++)
{
int nx=x+fx[i],ny=y+fy[i];
if(a[nx][ny])
{
now1=((long long)now1*base1+i+3)%mod;
now2=((long long)now2*base2+i+7)%mod;
a[nx][ny]=0,dfs(nx,ny);
}
}
now1=((long long)now1*base1+11)%mod;
now2=((long long)now2*base2+13)%mod;//双哈希操作序列
}
map <int,int> mp1,mp2;
int main(int argc, char** argv) {
int n,m;
while(cin >> n >> m)
{
mp1.clear(),mp2.clear();
if(!n) break;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
char c;
cin >> c;
if(c=='1') a[i][j]=1;
else a[i][j]=0;
}
}
int cnt=0,ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j])
{
now1=now2=0;
a[i][j]=0,dfs(i,j);
++cnt;
if(!mp1[now1]||!mp2[now2]) ++ans;
mp1[now1]=mp2[now2]=1;//map去重
}
}
}
cout << cnt << " " << ans << "\n";
}
return 0;
} D. 坐标计数
结论:所有点最终都会变成 ,所以答案就是矩形大小。
证明:
如果 异奇偶,那么一次操作之后会变得同奇偶。
如果 都是奇数,那么一次操作之后都会变成偶数。
如果 都是偶数,二进制位下最后一个
就永远不会变了,能到
等价于
能到,所以把它们都
。
发现每次操作之后 二进制最高位不会增加,而在
都是偶数的时候,
都会被
。
所以每个点会在 次内变成
。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
long long a,b,c,d;
cin >> a >> b >> c >> d;
cout << (max(a,c)-min(a,c)+1)*(max(d,b)-min(b,d)+1) << "\n";
}
return 0;
} E. 解方程
结论:答案是 的因数个数。
目前也不太会证。
所以把 和
每个质因数次数加起来,然后 (次数+1) 的乘积就是答案。
#include <iostream>
#include <vector>
using namespace std;
inline long long F(long long x)
{
long long rtn=0;
for(long long i=2;i*i<=x;i++)
if(x%i==0) rtn+=2-(i*i==x);
return rtn;
}
long long cnt[1000005];
vector <long long> v;
int main(int argc, char** argv) {
long long T;
cin >> T;
while(T--)
{
long long a,b;
v.clear();
cin >> a >> b;
if(a==1&&b==1)
{
cout << 1 << "\n";
continue;
}
long long ans=1;
for(long long i=2;i*i<=a;i++)
{
while(a%i==0)
{
ans/=cnt[i]+1;
v.push_back(i);
++cnt[i],a/=i;
ans*=cnt[i]+1;
}
}
if(a>1)
{
v.push_back(a);
ans/=cnt[a]+1;
++cnt[a];
ans*=cnt[a]+1;
}
for(long long i=2;i*i<=b;i++)
{
while(b%i==0)
{
v.push_back(i);
ans/=cnt[i]+1;
++cnt[i],b/=i;
ans*=cnt[i]+1;
}
}
if(b>1)
{
v.push_back(b);
ans/=cnt[b]+1;
++cnt[b];
ans*=cnt[b]+1;
}
for(auto t:v) cnt[t]=0;
cout << ans << '\n';
}
return 0;
} F. 消减整数
先暴力模拟一轮,假设减到了 ,
还剩下
。
由于自然数之和是平方级别的,所以 是根号级别的。
如果 ,答案就是
。
否则的话,之后每一轮都会剩下 。
所以答案就是 满足 的最小
。
暴力枚举 判断即可,可以发现最多判断
次。
复杂度
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int T;
cin>> T;
while(T--)
{
int n,X;
cin >> n;
X=n;
int x=1;
while(n>=x)
n-=x++;//模拟
for(int i=1;i<=X;i++)//枚举答案
{
if(n*i%x==0)
{
cout << i << "\n";
break;
}
}
}
return 0;
} G. 简单题的逆袭
发现 的时候一定无解(因为如果
满足条件,那么
一定满足条件)。
的时候也一定无解,(因为显然
的任意幂次
)。
否则一定有解 ()。
所以就从小到大枚举 ,找到最大的合法的解。
由于 ,所以
。
复杂度 。
可能会爆 long long,所以可以用 double 或者 __int128 等方式实现。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
long long x,y;
cin >> x >> y;
if(x==0||x==1)//x<=1无解
{
puts("-1");
continue;
}
if(y==0)//y=0无解
{
puts("-1");
continue;
}
__int128 X=x;
int ans=0;
while(X<=y)//枚举答案
{
++ans;
X*=x;
}
cout << ans << "\n";
}
return 0;
} H. 对称之美
如果 回文的话,当且仅当对于任意
满足
。
所以只需判断是否对于任意 ,满足
和
存在相同字符。
暴力判断任意两字符即可。
#include <iostream>
using namespace std;
string a[105];
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
int n;
cin >> n;
for(int i=1;i<=n;i++)
cin >> a[i];
int ans=1;
for(int i=1;i<=n;i++)
{
int t=n-i+1;
if(t<=i) break;
int flag=0;
for(auto x:a[i])//从a[i]里枚举字符
{
for(auto y:a[t])//从a[t]里枚举字符
{
if(x==y){
flag=1;
break;
}
}
if(flag) break;
}
if(!flag)
{
ans=0;
break;
}
}
if(ans) puts("Yes");
else puts("No");
}
return 0;
} I. 非对称之美
考虑如果原串不是回文串,答案就是原串的长度。
如果原串所有字符都相等,那么答案一定是 。
否则答案一定是 。
证明:
如果一个长度为 的回文串
删掉第一个字符之后还是回文串,
那么一定有 (原串回文),
(删之后回文),所以
。
同理, ,
。
所以 的所有字符都相等。
暴力判断是否回文,是否都相等即可。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
string s;
cin >> s;
int x=0,y=s.size()-1;
int F=1;
for(auto t:s) if(t!=s[0]) F=0;//均相等
if(F)
{
cout << 0;
return 0;
}
int flag=1;
while(x<y)//回文
{
if(s[x]!=s[y])
{
flag=0;
break;
}
++x,--y;
}
cout << s.size()-flag;
return 0;
} J. 排列算式
首先 没有用。
显然如果总和 一定无解。
然后可以发现,如果只存在 那么一定合法。
构造性证明:
如果当前表达式 ,任选一个加法,否则任选一个减法。
如果没有加法/减法的话,就都选上,由于总和在 所以肯定合法。
所以就要尽可能消耗 和
。
只有在当前值 才能用
,
才能用
。
首先先把 和
内部尽量消耗,之后至少有一个被用完了,不妨设剩下的是
。
然后用 或者
或者
可以消耗一个
。
(如果用了 就会白白浪费一些
,如果同时用了
就相当于白浪费一个
一个
)
尽量消完 后,如果还有
或者
个数不止一个,那么就无解,否则有解。
因为如果剩一个 可以一开始用掉,由于之前已经尽量消耗
,所以之后的序列里不可能再出现
。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int T;
cin >> T;
while(T--)
{
int n,a,b,c,d,e,f;
a=b=c=d=e=f=0;
cin >> n;
for(int i=1;i<=n;i++)
{
int x;
cin >> x;
if(x==1) ++a;
if(x==2) ++b;
if(x==3) ++c;
if(x==-1) ++d;
if(x==-2) ++e;
if(x==-3) ++f;//统计个数
}
while(c&&f) --c,--f;//+3-3
while(f&&a&&b) --a,--b,--f;//+1+2-3
while(d&&e&&c) --d,--c,--e;//-1-2+3
while(c&&d>=3) d-=3,--c;//-1-1-1+3
while(f&&a>=3) a-=3,--f;//+1+1+1-3
while(c&&e>=2&&a) e-=2,--c,--a;//-2+1-2+3
while(f&&b>=2&&d) b-=2,--f,--d;//+2-1+2-3
if(c>1||f||a+b+b+c+c+c-d-e-e-f-f-f<0||a+b+b+c+c+c-d-e-e-f-f-f>3)//判断总和以及剩余 +-3 个数
{
puts("No");
continue;
}
else puts("Yes");
}
return 0;
}
滴滴公司福利 1784人发布