小白入门主要注重在思维
B.打开石门
问题描述
小哥和天真迷上了探索古墓,最近他们发现了一条通往西王母宫的密道,但密道入口处有一扇刻满符号的石门。 这些符号组成了一个符号串 SS ,符号串 SS 仅包含字符 LL 和 QQ 。为了打开石门,他们需要先解读门上的符号。
古籍记载,解读符号的方法如下:
- 首先,需要在 LL 和 QQ 中选择一种字符作为“机关字符”。
- 之后,可以多次(含 00 次)选择符号串 SS 中任意两个相邻的“机关字符”,并将它们替换成一个“机关字符”。
举个例子,假设初始符号串为 LLQQLLQQ,如果你选择 LL 作为“机关字符”,那么你可以将两个相邻的 LL 替换为一个 LL,得到 LQQLQQ;如果你选择 QQ 作为“机关字符”,那么你可以将两个相邻的 QQ 替换为一个 QQ,得到 LLQLLQ。
请问,小哥和天真最终能将符号串缩短到多短呢?
由题目其实可以知道,连续相同的字符串最后就会缩短为1个,不管有多长,只要是连续的,那么最后最短就会缩短为1。
所以就是选取L为机关字符,那么只需要统计一段中连续的L数量,缩短为1,然后获取完所有的连续的L子串即可,Q也是同理。
#include <bits/stdc++.h>
using namespace std;
int getcnt(string s, char target) {
int n = s.size();
int l = 0, r = 0;
int cnt = 0;
while (r < n) {
if (s[r] != target) { //不相同的时候就跳过
while (r < n && s[r] != target) r++;
// 此时出来的时候就是相同的了
l = r;
}
else if (s[r] == target) {
while (r < n && s[r] == target) r++; //此时就是统计相同的
if (r - l >= 2) cnt += r - l - 1;
l = r;
}
}
return cnt;
}
int main()
{
string s; cin >> s;
int ret1 = getcnt(s, 'Q');
int ret2 = getcnt(s, 'L');
cout << min(s.size() - ret1, s.size() - ret2) << endl;
return 0;
}
C.青铜门上的涂鸦
问题描述
闷油瓶去长白山闭关修炼了,留下吴邪一人在家百无聊赖。为了排解寂寞,吴邪决定研究研究青铜门上的涂鸦。通过观察他发现,青铜门上的涂鸦是由两种特定的符号组成:代表闷油瓶的画像 LL 和代表吴邪自己的画像 QQ。
闲来无事,吴邪便开始琢磨能不能修改一下这些符号,搞点新花样出来。他的改造方法很简单:每次选取门上连续的两个符号,然后把这两个符号都变成代表他自己的画像 QQ。
比方说,如果涂鸦是 LQLLQL,那么吴邪可以选择替换前两个符号 LQLQ,得到 QQLQQL;当然,他也可以选择替换后两个符号 QLQL,得到 LQQLQQ。
现在,请你帮吴邪算算,在只进行一次修改操作的前提下,吴邪可以创作出多少种不同的涂鸦图案呢?
一开始想到的是,用一个map存储每个状态,只记录不重复的状态的个数即可,这样的话,从前往后遍历。但是这样子的话会有mle。
那样子该怎么获取最优的解呢?
仔细观察发现,假如连续的是 LQL 那么改变的话就会是 QQL 、LQQ,这样是不重复的
连续的是LLQ 那么就是 QQQ LQQ 。 以此类推
但如果连续的是QLQ -> 变化后就是 QQQ 和QQQ ,此时只需要记录一次即可
但由于是从前往后获取的,所以只需要把后续记录的不进行记录即可
所以只需要对于满足 Q_Q的,那么就不进行记录,因为此时的情况就已经被记录过了。
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;cin>>s;
int n=s.size();
// 罗列出重复的即可 QLQ 其实就一个 因为改变前面 QQQ 和后边QQQ 都是同一个 但是对标的是第i位,所以就是说相同的话就直接跳过即可
int flag=0;
int ret=0;
for(int i=0;i<n-1;i++){
if(s[i]=='Q'&&s[i+1]=='Q'){
flag=1; continue;
}
if(i>=1&&s[i-1]=='Q'&&s[i+1]=='Q') {
continue;
}
else ret++;
}
cout<<ret+flag<<endl;
return 0;
}
D.敲打骷髅兵
问题描述
在一个昏暗的地下古墓里,胖子、小哥和你正在面对一只不安分的骷髅兵,这家伙的初始生命值为 NN。
每当你们用洛阳铲狠狠敲一下骷髅兵的脑袋,它就会消失,然后诡异地冒出两只新的骷髅兵!这两只新骷髅兵的生命值均为敲打前的骷髅兵的生命值的一半(向下取整)。例如,如果敲打前的骷髅兵的生命值为 3,那么敲打后就会冒出两只生命值为 ⌊3/2⌋=1的骷髅兵。如果敲打前的骷髅兵的生命值为 4,那么敲打后就会冒出两只生命值为 ⌊4/2⌋=2 的骷髅兵。
如果敲打前的骷髅兵的生命值的一半(向下取整)为 0,则不会冒出新的骷髅兵。
第一次见到这场面,你吓得差点把铲子掉了!见状,胖子拍了拍你的肩膀,语气坚定地说:“伙计,敲脑袋的事就交由你胖爷来。不过,这骷髅兵可不简单!每次你敲它一下,它就会分裂出两只新的,真的是让人想哭啊!”
小哥在旁边插嘴:“是啊!也不知道要敲到什么时候!”
现在,请你计算出使初始以及后来不断冒出来的所有骷髅兵都消失所需要的最少敲打次数。
由题目可以知道
当骷髅兵只有1个的时候,那么只需要敲1次
2 : 2-> 1 1-> 3次
3 : 3-> 1 1-> 3次
4 :4-> 2 2-> 1 1 1 1 ->7次
.
.
.
.
7 : 7-> 3 3-> 1 1 1 1 -> 7次 == 1+2+4
8 : 8-> 4 4 -> 2 2 2 2 -> 1 1 1 1 1 1 1 1 ->15 = 1+2+4+8
15 : 15-> 7 7 -> 3 3 3 3 -> 1 1 1 1 1 1 1 1 -> 15 次
因此观察规律,其实可以知道的是 这是一个三角形的分布
1 1
2 3 2
4 5 6 7 4
8 9 10 11 12 13 14 15 8
所以问题就是如何将其数字对应的位置,但其实可以发现这就是二进制的
1-> 1 1位 2-> 10 2位 4->100 3位 7-> 111 3位
8-> 1000 4位 15->1111 4位
那么其对应的值其实就是 2^n(位数)-1 所以只需要统计出每个数的二进制的位数n即可,套公式
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve(){
ll n;cin>>n;
ll s=0;
if(n==1) {
cout<<1<<endl;return ;
}
while(n){
s++;n/=2;
}
cout<<(int)pow(2,s)-1<<endl;
}
int main()
{
int tt;cin>>tt;
while(tt--){
solve();
}
return 0;
}
E. 净化王胖子
问题描述
吴邪和王胖子决定前往青铜古树寻找三叔的下落!
在青铜古树中,共有 n 个房间排成一排。其中第 i 个房间的编号为 ai,两人将从编号为 11 的房间开始找寻。
每个房间都有着关于三叔的线索,且这些线索之间相互关联。具体地,对于编号为 ii(i>1) 的房间,只有当编号为 [1,i−1] 之间的所有的房间线索都被解开时,该房间的线索才能被解开。而一旦他们获得了所有房间的线索,就能得知三叔的下落。
然而,王胖子在进入古树时不慎被怪物抓伤,伤口感染程度为 k。在离开之前,必须将他的感染程度成功降至 0,否则将会产生意想不到的后果。
由于时间紧迫,吴邪无法停下为王胖子净化,两人将仍然按照最优策略寻找线索(移动步数最少),但他可以在行动前在任意两个相邻的房间之间建立净化站点。每当他们从一个房间经过净化站点到达另一个房间时,如果王胖子的感染程度仍为正数,就会减少 1。
请你帮助吴邪计算出至少需要建立多少个净化站点,才能确保在离开青铜古树时将王胖子的感染程度降为 0。如果无法做到这一点,则输出 −1。
注意:在任意两个相邻房间之间最多只能建立 1个净化站点。
由题目可以知道,有一些空会重复走,也就两个房间之间会重复走,那么肯定优先在走的次数最多的位置放置净化站点,经过几次那个站点,那么就会减少多少次感染程度。
所以这里其实就是一个覆盖的区间的问题。想到覆盖的区间问题,那么就是差分来获取。
但是这里又有一个新的问题,走的位置的顺序不一定是从右到左的,那么这怎么解决??
其实只需要将数据对应的下标存储即可,然后照样的还是获取从数字1到数字2的,但是差分的本质是从小到大,所以需要对于其下标进行处理即可。也就是通过其数字的先后顺序,来获取下标的标记
然后处理结束后就进行前缀和,那么此时就可以获取每个位置的经过的次数,然后累加,直到超过了就可以返回,如果全加起来都不能净化完的话,那么就返回-1。
#include <bits/stdc++.h>
using namespace std;
const int N=2*1e5;
int a[N],b[N];
int main(){
int n,k,x;cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>x;a[x]=i; //将数字赋予其下标
}
for(int i=1;i<=n-1;i++){
int L=min(a[i],a[i+1]),R=max(a[i],a[i+1]); //这里的i其实表示的就是相邻的元素,但是要获取其下标,对于下标进行处理
// 但是差分数组为什么一定是,小的+大的-??? 对于小的下标进行++ 大的下标--
b[L]++;
b[R]--;
}
//然后进行前缀和,其实此时就是获取了每个区间经过的次数,取最大的次数即可
for(int i=1;i<=n;i++){
b[i]+=b[i-1];
//cout<<b[i]<<" ";
}
sort(b+1,b+1+n,greater<int>());
int cnt=0;
for(int i=1;i<=n;i++){
cnt+=b[i];
if(cnt>=k){
cout<<i<<endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}
F. 云顶天宫
问题描述
其实根据题目,最后留下的数一定是m
所以一个序列里至少保证一个m的存在
示例中的4 5,表示是4个位置,m=5
那么 其实就是 _ _ _ _ 假如第一个位置放的就是 5 _ _ _ 那么剩下的位置就放1-4即可
所以就是有4^3 =64个方案数,而且因为5可以放其他位置,所以就是 4*64=256个
但需要注意的是,删除的要求是,当前的元素大于相邻元素即可,便可以进行删除自身或者删除左右两个元素的操作,比如 5_5_ 此时也是满足要求的
只需要选定一个5 (m==5 为最大值,一定可以在这个位置进行操作),然后删除该位置的5,剩下的就只是 _ 5 _ 的,也就又变为了一个m的情况。这种总共就有 5_5_ | _5_5 | 5_ _ 5 ->4^2 *3=48 加起来刚好满足
48+256=304 个方案数
所以根据规律,一个序列里可以放入几个 m呢??
假如个数为x,那么m个数范围应该为 1<= x <= (n+1)/2
那么如果放 1个,那么个数应该为(组合数表示) C[n][1] * (m-1)^(n-1)
在示例中表示的就是 C[4][1]* 4^3 ,表示的是4 个位置,任意选一个放入m,然后其他n-1个位置就是放入 1->m-1(m个数) ,方案数就是 (m-1)^(n-1) 再乘上放入m的数量
那么如果是2个以上的m呢?? 那么此时需要注意的是,为了满足删除的条件,所以两个m不能相邻,那么应该怎么解决相邻的问题呢?? ——> 填空问题
因为是4个位置 _ _ _ _ 那么此时就是有5个空可以放置
所以就是从这个5个空里选择2个 那么此时剩下的就只有3个位置可以放
所以就是 C[4+1-2][2] * (m-1)^(2) 其实就是填空,填空的话一定可以保证是不会有相邻的 m存在。
因此=总体的就是 C[n-i+1][i] *(m-1)^(i) ,i从1到 (n+1)/2 一个个遍历即可,方案数相加。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e6+10,mod=998244353;
typedef long long LL;
#define int long long
int fact[N],infact[N];//存放阶乘结果的数组
int qmi(int a,int k,int p){ //逆元求组合数
LL res=1;
while(k){
if(k&1) res=(LL)res*a%p;
a=(LL)a*a%p;
k>>=1;
}
return res;
}
int pow(int a,int k){ // 求平方的
if(!k) return 1;
if(k%2) return pow(a*a%mod,k/2)*a%mod;
else return pow(a*a%mod,k/2);
}
signed main(){
int n,m;cin>>n>>m;
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod; //阶乘的倒数
}
int ans=0;
for (int i=1; i<=(n+1)/2; i++)
ans += (int)(fact[n+1-i]*infact[i]%mod*infact[n+1-i-i]%mod)*pow(m-1, n-i)%mod;
cout<<ans%mod<<'\n';
return 0;
}