小白月赛25(小白版)

A AOE还是单体?

题目链接

https://ac.nowcoder.com/acm/contest/5600/A

解题思路

说实话,这个题靠着惯性思维就可以想出贪心思路(其实,我都没感觉这是个贪心,以为顶多算个思维题)。AOE技能是范围攻击,造成群体伤害,就看AOE的伤害-能耗比,与单体攻击的伤害-能耗比的大小。举个例子,五只怪物,一次AOE消耗3mp时,我们需要先用AOE消耗吧,因为要是用单体攻击,对每个怪物造成1点伤害需要5mp,而AOE攻击只消耗3mp,对应到伤害-能耗比上,AOE:5/3,单体:5/5,AOE的效率比单体高,先用AOE。
问题来了,AOE不可能一直效率比单体高啊,什么时候AOE就不如单体了?举个例子,五只怪物,一次AOE消耗10mp,对应到伤害-能耗比上,AOE:5/10,单体:5/5,这时候AOE就不如单体来的实在了,所以选单体攻击。
总结一下会发现,
当x(即一次AOE消耗xmp)> 现存怪物数量时,我们使用一技能(即单体攻击)。不理解的话我们用数据说话,假设num代表现存怪物数量(因为一次AOE对全体怪物造成一点伤害,所以总伤害=num*1=num),那么对应到伤害-能耗比上,AOE:num/x,单体:num/num,毋庸置疑,单体效率永远是1,而此时的AOE效率<1,因此用一技能为佳。

当x(即一次AOE消耗xmp)= 现存怪物数量时,还是对应到伤害-能耗比上,会发现都是1,意味着用一技能还是二技能都可以

当x(即一次AOE消耗xmp)< 现存怪物数量时,对应到伤害-能耗比上,AOE:num/x,单体:num/num=1,AOE效率>1=单体效率,因此二技能为佳

简化实现

基本思路都有了,与思路同等重要的还有代码的实现(因为其实解题思路只是为代码实现提供了个大框架,与真正的实现还是有不小的区别的)
思考一下,我们能不能先算出“把全部怪物都消灭所使用的AOE消耗的全部mp之和”,再算出“把全部怪物都消灭所使用的单体技能消耗的全部mp之和”,两者之和就是消耗的全部mp。为保证消耗尽量少的mp,我们就得判断一下AOE的效率是不是比单体高,高了才能用,要不然还不如用单体伤害呢。
我们去按照怪物的血量从小到大排序,判断一下现存怪物数量时,AOE的效率是不是高,如果AOE效果好,那就多次使用AOE直接把血量最少的怪物消灭,因为如果不使用多次把当前怪物消灭,现存怪物数量不会减少,再比较现存怪物数量和x大小的时候,还是要AOE,所以不如直接干掉当前这个怪物,让现存怪物数发生改变。
当遍历排好序的怪物,现存数量到达x=现存的时候,以后就可以使用单体了,因为AOE效率不如单体高了。
其实,算出了“把全部怪物都消灭所使用的AOE消耗的全部mp之和”,也就是遍历到x=现存数量的时候,就不需要再继续遍历,求“把全部怪物都消灭所使用的单体技能消耗的全部mp之和”了。所有现存怪物的剩余血量之和就是我要使用单体攻击的次数,在数值上也等于单体攻击消耗的mp之和。所以,我们就在输入的时候累加算出全部怪物的总血量,拿总血量减去AOE造成的伤害就是所有现存怪物的剩余血量,即单体攻击消耗的mp。
(这就是代码实现的一点小技巧)

AC代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int blood[N];
int main(){
 	int n,x;
 	cin>>n>>x;
 	ll sumblood=0;//记录所有怪物的总血量,用于减去AOE造成的伤害,求得单体攻击的次数
 	for(int i=1;i<=n;i++){
  		cin>>blood[i];
  		sumblood+=blood[i];
 	}
   
 	sort(blood+1,blood+n+1);
 
 	ll num=n,summp=0;//num表示现存怪物数目,summp表示AOE所消耗的mp之和

 	for(int i=1;i<=n;i++,num--)//i表示第i个怪物,每次循环消灭一个怪物i++,num--  
  		if(x<num){//AOE效率高
   			sumblood-=num*(blood[i]-blood[i-1]);//AOE造成的伤害=这个怪物与前一个怪物的血量之差*当前存在的怪物数量//为了最后计算单体攻击的次数
   			summp+=(blood[i]-blood[i-1])*x;//进行了“这个怪物与前一个怪物的血量之差”次AOE,所以消耗的mp为(blood[i]-blood[i-1])个x
  		}
  		else break; //单体攻击高效了,直接break了
 
 	cout<<sumblood+summp; //现在的sumblood是已经减去AOE伤害后的剩余血量(即单体攻击次数),加上summp记录的是AOE消耗的mp,二者之和即为消耗的全部mp
}

这个题很简单就这样结束吧。
对了对了,真的是差点忘了要附张图的,我之所以写上面那句话就是因为感觉好像还有点东西忘了,我真是天才(滑稽~)
在这里插入图片描述
画技拙劣……
解释一下,这个图描述的就是循环的过程。
这已经是从小到大排好序的,红色区域就是循环的第一次,即如果使用AOE的话,消灭第一个怪物造成的伤害;绿色区域就是循环的第二次,即如果使用AOE,消灭第二个怪物造成的伤害,以此类推。注意一下,我这样画是为了体现AOE伤害为什么是两怪物血量之差,实际并不一定一直用AOE。
好了好了这次真的没了。

B K-size字符串

对不起,我真不会了……

C白魔法师

题目链接

https://ac.nowcoder.com/acm/contest/5600/C

解题思路

本题用到并查集,并查集的解法来源于大佬,我不会并查集,所以以下讲解都是我的方法。

思考过程:

开始想着建树,但是发现建树也无法实现或者不会实现;又想到了dfs,所以从dfs方面入手思考了一下。

分析题目:

改变的是黑点或白点,我们肯定是能改黑点绝不改白点,因为黑点改成白点会让白点数目变多,而将白点改成白点相当于什么都没做;只有在点全为白色的时候,才需要将白点改成白点,这种情况也是容易被忽略的。

大体思路:

遍历黑点,含义是将遍历到的黑点作为被改色点。我们只要求出当前黑点相邻的连通区域面积之和即可,再把黑点遍历一遍,得到最大的面积和就是答案。

代码实现

p[i]表示第i个点的颜色和id,白色color=1,黑色color=0,id的含义是,第i个点所处的连通区域的id编号。

area[i]表示连通区域id编号为i的区域面积。

vector a[i]表示二维数组,用于存储边。

add_around函数,把当前遍历到的黑点相邻的连通区域的和求出来。若相邻的节点为黑色,continue;若相邻节点为白色但是白色id已经存在,意味着这个白点所处连通区域已经有了编号,所以res直接加上所处区域面积;若相邻节点为白色且id未知,就要通过dfs函数求出此节点所在连通区域的面积及此节点的id(即所处区域的id)。

dfs函数,递归,确定点的id和所处连通区域面积的大小。注意continue的条件,下一个节点的id不为0的判断不能省略,防止再次访问到父亲节点。

最后注意全为白点的情况,直接输出n,即全部点的个数即可。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int id,ans;
vector<int> a[N];//store map
int area[N];//the area is s[id]
struct point{
 	int color,id;
}p[N];//attribute of point
void dfs(int cur){
 	for(int i=0;i<a[cur].size();i++){
  		int now_p=a[cur][i];
  		if(p[now_p].color==0 || p[now_p].id!=0) continue;
  		area[id]++;
  		p[now_p].id=id;
  		dfs(now_p);
 	}
}
int add_around(int cur){
 	int res=0;
 	for(int i=0;i<a[cur].size();i++){
  		int now_p=a[cur][i];
  		if(p[now_p].color==0) continue;
  		if(p[now_p].id!=0) {res+=area[p[now_p].id];continue;}
  		p[now_p].id=++id;
  		area[id]++;
  		dfs(now_p);
  		res+=area[id];
 	}
 	return res;
}
int main(){
 	int n;
 	char ch;
 	cin>>n;
 	for(int i=1;i<=n;i++){
  		cin>>ch;
  		if(ch=='W') p[i].color=1;//white
  		if(ch=='B') p[i].color=0;//black
 	}
 	for(int i=1;i<n;i++){
  		int x,y;
  		cin>>x>>y;
  		a[x].push_back(y);
  		a[y].push_back(x);
 	}
 	for(int i=1;i<=n;i++){
  		if(p[i].color==0){//is black
  		//!!!notice:when the points are all white, loop can't be conducted
  		 	ans=max(add_around(i)+1,ans);
  		}
 	}
 	if(ans==0) cout<<n;//all white
 	else cout<<ans;
} 

哔哔赖赖

本来AC了这道难题还很开心,写完了这个题的两千字的题解花了2h也很开心。我真的了,好心情全被破坏了,写完了题解之后,CSDN卡了,我关了之后发现他们没保存,这么长时间的努力全白费了,真恶心。又忍不住吐槽CSDN的编辑界面,字数多了卡的直接成ppt了,我还是从word上写完,复制过来的。直接被恶心到了。这是又写了一遍这个题的题解,明显没第一次写的多,写的详细了,真是搞心态。AC的好心情全没了。

D 抽卡

题目链接

https://ac.nowcoder.com/acm/contest/5600/D

解题思路

这道题我一定要好好讲讲,花了我一整天(零散时间加吧加吧)才学明白的,真是浪费时间 值得
首先,我来分析一下题意,因为我就是好长时间没明白样例说明中的1/2哪里来的。题目的意思是:从n个池子里抽一张卡,抽到自己想要的卡的概率(哪怕所有的池子中只抽到一张自己想要的也算抽到了),而题目要求输出的是“抽到自己想要卡的概率的分母* x%mod=概率分子”中x的最小非负数。
显然,我们需要先求出这个概率的分子和分母吧。说实话,不好求抽到自己想要卡的概率,你想想是不是个很麻烦的式子,(但是我这个傻货 机灵鬼居然真这么求了,花了二十分钟写了个循环,最后也没成功,也是因为不知道逆元 )因此,我们不如求没抽到自己想要卡的概率,1-没抽到自己想要卡的概率=抽到自己想要卡的概率。没抽到的概率是不是好求多了,第一个池子中没抽到自己想要卡的概率 * 第一个池子中没抽到自己想要卡的概率 * 第三个 * ……,简记为∏pi,其中pi为第i个池子中没抽到自己想要卡的概率,所以1-∏pi=抽到自己想要卡的概率。

现在要补充知识了!!!
费马小定理的结论:a^ (m-1)≡1(mod m),其中a为质数,m为模,gcd(m,a)=1也就是a,m互质。先讲一下这式子的含义(我也是从不明白一点点查出来的,这个过程真的累),a的m-1次幂与1对m恒同余,也就是a^ (m-1)%m≡1%m,其实这里的mod属于一种新的运算符,取模的符号是%,同余符号是mod。(重点是上面的式子啊)
下面是大佬讲解费马小定理,写的很棒,救我于水火!太及时了,今天(发出去文章的时间肯定晚 )搜费马小定理,昨天博主出的文!
救小白的大佬讲解费马小定理

取模的一些运算规则
1.(a + b) % p = (a % p + b % p) % p
2.(a - b) % p = (a % p - b % p ) % p (要用到)
3.(a * b) % p = (a % p * b % p) % p (要用到)
分数取模
https://www.zhihu.com/question/343972537
上面的链接是分数取模的含义,有关离散数学的,真悔恨自己的离散数学因为听不懂快放弃了,回去还要考试 ,这里我只强调分数的模可能不存在(本题用不到,这个下面我应该会挂个链接,讲个例题,如果没挂就是我忘了,你就使劲戳我!),若存在那么一定是个大于1的数(本题用到),也就是说,概率(<1)对m取模,一定是个大于1的数。
有了这几个知识,我们就可以操作了。

利用费马小定理分数取模简单变形:
先把费马小定理的符号改一下:fenmu^ (m-1)≡1(mod m),把a换成了fenmu。
操作1:fenmu^ (m-1)≡1(mod m) <=> fenmu^ (m-1)%m = 1%m (恒等和等号没有太大区别,变量常量的区别,在这不区分了)
操作2:等式两边同乘新的变量fenzi,得到fenzi * fenmu^ (m-1)%m = fenzi * 1%m
操作3:等式两边同除以fenmu,得到fenzi * fenmu^ (m-2)%m = fenzi / fenmu%m
定睛一看,等式右边不正是分数取模吗?
总结一下,由费马小定理变形得出结论:分数取模的值=分子 * (分母的m-2次幂)%m,这里也可以看出分数的模大于1。

分析一下输出:输出“抽到自己想要卡的概率的分母* x%mod=概率分子”中x的最小非负数。
初步转化:分母 * x≡分子(mod m) (这个应该可以理解吧)
再转化:把分母除过去,x≡分子/分母(mod m)<=> x=分子/分母(mod m)
最终转化:因为题目要求最小非负整数x,所以x必然要小于m且大于0,那我们就把上面的式子转化为x=分子/分母%m,因为x小于m且大于0,所以x取模还是x,只要对等式右边取模即可。
这样一来我们就分析出了题目要求输出的x到底怎么求,就是“抽到自己想要的卡的概率取模”。

快速幂
这个挺重要的经常用到。
附上个神仙写的快速幂的链接:
神仙快速幂
这个博主写的真的是无敌了,我顺着看下来直接明明白白。所以我也不丢人现眼自己写了。
快速幂用于本题求解“分数取模的值=分子 * (分母的m-2次幂)%m”中的高次幂。

解题思路就这些,我下面讲讲代码的实现。

代码实现

1.因为我们要求没抽到自己想要的卡概率的分子和分母。其分子=∏(a[i]=b[i]),其分母=∏a[i],概率=∏(a[i]=b[i])/∏a[i]。
方法1(开始理解的不是很透彻,所以直接仿写的大佬的代码,就是这样写的):我们并不在输入的时候直接累乘算出来,而是把每个池子对应的没抽到想要卡的概率用费马小定理得出的结论算出每个分数取模的值,再将这些值累乘取模即可(运用到上述中的取模运算规则3)。而这里求出每个分数取模的值就是求“分子*分母^(m-2)%m”。所以我们在输入完成之后,另设一个循环,不直接算没抽到的概率,而是算没抽到概率的模。
方法2(比较简单,而且好理解):
2.上面求出来的没抽到想要卡的概率的模,而题目要求的是抽到卡的概率的模。因此我们通过式子(1-ans+mod)%mod求得最终答案,其中ans为没抽到概率的模。1-ans是抽到想要卡的概率的模,+mod保证非负,再取模。
是不是很奇怪,为什么可以用1减去没抽到卡的概率的模???(我反正当时疑惑了好久,虽然大佬给讲解了,但是还是没听明白,最后其实还是自己理解的)
(我不理解的原因:我一直以为1-ans求出来的就是概率,甚至以为ans就是概率,其实都不是,他们都是概率的模,所以有可能大于1,甚至为负数,希望你不要跟我一样懵圈)
这就用到了“取模运算规则2”了。这是很多大佬没有写明白的其中一个地方,会让深度思考的小白疑惑好久。对“1-没抽到卡的概率”取模得:(1-p)%mod;运用“取模运算规则2”,再变得1%mod-p%mod;继续变得1-p%mod;我们求出来的ans=p%mod,所以原式就变成了1-ans,表示的就是“想要卡的概率的模”。上面说过,ans是概率的模,>1,因此未保证1-ans非负,要加上个mod再取余。

到此为止本题终于是讲完了!
下面上代码!!!

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
const ll mod=1e9+7;
ll a[N],b[N];
ll ksm(ll x,ll t){//快速幂
 	ll res=1;
 	while(t>0){
  		if(t&1LL) res=res*x%mod;
 		t>>=1;
  		x=x*x%mod;
	}
 	return res;
}
int main(){
 	int n;
 	cin>>n;

/*
    	//方法1
 	ll ans=1;
 	for(int i=1;i<=n;i++) cin>>a[i];
 	for(int i=1;i<=n;i++) cin>>b[i];
 	for(int i=1;i<=n;i++){
  		ans=ans*(a[i]-b[i])%mod*ksm(a[i],mod-2)%mod;//利用费马小定理的得出结论
 	}
 	cout<<(1-ans+mod)%mod;//转换成“抽到想要卡概率的模”
 */
 	//方法2
 	ll fenzi=1,fenmu=1,ans;//fenzi存储“没抽到想要卡的概率”的分子,fenmu存分母,字如其名
 	for(int i=1;i<=n;i++) {cin>>a[i];fenmu=fenmu*a[i]%mod;}
 	for(int i=1;i<=n;i++) {cin>>b[i];fenzi=fenzi*(a[i]-b[i])%mod;}
 	ans=fenzi*ksm(fenmu,mod-2)%mod;//ans还是存的“没抽到想要卡的概率的模”
 	cout<<(1-ans+mod)%mod;//转换成“抽到想要卡概率的模”
}

总结

稍稍总结一下,主要是加深一遍印象,争取做过的题直接就记住(虽然不太可能,最起码下次拿起来能有点思路)。其实加深印象就是我写题解的其中一个重要原因。
总结:
1.分数取模(方法)(记住)->费马小定理及其变形结论
2.快速幂(板子)(理解)->本质指数通过位运算除2,指数为奇数,ans乘当前底数;指数是偶数,无操作,判断完再底数自身平方。整个循环的判断条件是指数大于0继续进行。
3.概率利用对立面(思想)(记住并能提取出来)

E 点击消除

题目链接

https://ac.nowcoder.com/acm/contest/5600/E

解题思路

栈的思想
为什么想到用栈呢?因为原来做过一个本质相同的题,都是这种消除相邻相同的而且消除之后的串再有相邻相同的就得再消除。其实记住这个方法就好了,更何况又很好理解。
输入
将字符串输入的时候getchar一个个输入,这样我就能一个个push进栈中了。输入完一个字符之后,我直接判断这个字符和当前栈顶字符是否相同。若相同,pop栈顶,输入的字符也不用入栈,因为与刚刚的栈顶消除了;若不相等,无法消除,将输入的字符入栈。
输出
方法一:因为要想输出栈的话,那么顺序是与期望顺序是相反的。因此,我们把栈不断pop,同时把pop出来的push进新的栈中,这样就实现了逆序操作,再把新栈中的字符pop出来,就是期望顺序了。
方法二:递归输出。我本来在方法一的后面加上了括号,里面备注“递归输出也可以,但是本题数据规模太大,应该不行”,但是还是不放心会不会把读者坑了,我就又写了一下递归的,发现居然也可以AC,多亏了试了试,要不真完蛋了……
注意
在栈为空的时候,要想判断栈顶与输入的字符是否相等会出错,所以我们要先push进去一个空格,保证即使是第一次判断也是有意义的,这样才能不出错,同时要注意输出的时候也会残留一个空格,稍微操作一下就能去掉。

方法一 AC代码

两个栈逆序输出

#include<bits/stdc++.h>
using namespace std;
stack<char> a,b;
int main(){
 	char ch;
 	a.push(' ');//先压入空格,保证栈不为空 
 	while(1){
  		ch=getchar();
  		if(ch=='\n') break;//输入回车就结束了 
  		if(ch==a.top()) a.pop();//输入与栈顶相等,弹出栈顶 
  		else a.push(ch);//输入与栈顶不相等,压入字符 
 	}
	while(!a.empty()){
  		b.push(a.top());
  		a.pop();
 	}//将a逆序,存入b中 
 
 	b.pop();//将空格pop掉 
 	if(!b.empty())
  		while(!b.empty()){
   			cout<<b.top();
   			b.pop();
  		}
 	else cout<<0;
}

方法二 AC代码

递归输出

#include<bits/stdc++.h>
using namespace std;
stack<char> a,b;
void fun(char ch){ 
 	if(ch==' ') return ;
 	char tmp=a.top();
 	a.pop();//不弹出栈顶的话,下一次调用tmp还是被赋值成与上次一样的栈顶
 	fun(tmp);
 	if(tmp!=' ')//判断一下,是空格就不输出了
  		cout<<tmp;
 	return ;
}
int main(){
 	char ch;
 	a.push(' ');//先压入空格,保证栈不为空 
 	while(1){
  		ch=getchar();
  		if(ch=='\n') break;//输入回车就结束了 
  		if(ch==a.top()) a.pop();//输入与栈顶相等,弹出栈顶 
  		else a.push(ch);//输入与栈顶不相等,压入字符 
 	}
 
 	if(a.top()==' ') {cout<<0;return 0;}
 	fun(a.top()); 
}

F 疯狂的自我检索者

题目链接

https://ac.nowcoder.com/acm/contest/5600/F

解题思路

简直了,什么lj题目,虽然是签到题,也不能这么水吧……
大佬还是大佬,这么无聊的一个题都能分析出题目类型:贪心&概率,假设所有隐藏的分数为1或5即可。
不多说,上代码!!!浪费时间,亏我还想了十分钟会不会有坑?还是我理解有问题?结果居然就是这么无聊!

AC代码

#include<bits/stdc++.h>
using namespace std;
int main(){
 	int n,m,sum=0,t;
 	cin>>n>>m;
 	for(int i=1;i<=n-m;i++){cin>>t;sum+=t;}
 	printf("%.5lf %.5lf",(sum+m)*1.0/n,(sum+5*m)*1.0/n);  
} 

G 解方程

题目链接

https://ac.nowcoder.com/acm/contest/5600/G

解题思路

二分,不用分析怎么想到用二分的吧。(我算是蒙的……)特征很明显,式子是单调的,分界点为解。(感觉是二分,没多考虑特征就写了,虽然写对了但是一定得改这个坏毛病!)
二分左端点:从1从0应该都行,大佬是从1开始的,因为x<1的时候,等式左边的值一定比1小,而等式右边的c不小于1,所以得从1以后算起。本人愚笨,没多想直接零开始干,没毛病!
二分右端点:c最大是1e9,也不多想了,直接给右端点个1e9,保证没问题。

AC代码

#include<bits/stdc++.h>
using namespace std;
int main(){
 	double a,b,c;
 	cin>>a>>b>>c;
 	double ans=-1;
 	double l=0,r=1e9;
 	for(int i=1;i<=100;i++){
  		double x=(l+r)/2;
  		if(pow(x,a)+b*log(x)>=c) r=x,ans=x;
  		else l=x;
 	}
 	printf("%.7lf",ans);
}

补充

想补充的东西挺多的。
1.首先说一点(吐槽),题目给的样例有问题,输出14位,开始也输出的14位,也AC了。看了大佬的题解,他们都输出的7位,应该是出题人笔误,大家自己注意就好了。
2.对比一下大佬代码

#include<bits/stdc++.h>
#define ld long double
using namespace std;
int a,b,c;
ld f(ld l,ld r){ 
 	if(r-l<=1e-8) return l;
 	ld x=(l+r)/2;
 	if(pow(x,a)+b*log(x)>=c) return f(l,x);
 	else return f(x,r);
}
int main(){
 	cin>>a>>b>>c;
 	printf("%.7Lf",f(1,c));
}

讲一下,大佬用的是递归难免会比我的代码跑的慢点。也就是说其实二分有两种方式,一种是二分达到一定次数结束,另一种是误差小于esp的时候结束,第一种方法适用于规定的误差很小或者并不是以误差判断结束的情况,相对普遍;第二种可能在有的情况下比较合适。通过“二分达到一定次数结束”的方式,为什么就进行100次就能找出分界点?二分是log级别的,每次折半,比如这道题范围是0~1e9,所以进行100次意味着每次循环都除以2,除了100次2,也就是除了2^ 100,int范围是2^ 32,这样你就明白为什么二分一百次完全够了吧。大佬用的方法,要注意两点,一是要用long double(第一次听说),double无法AC,而我的代码double就能过(不知道为什么);二是eps为1e-8(eps就是误差)
3. 一些私货,自己百度的。
double有效的小数位是15位,long double有效的小数位是18位(不同编译器不一样,我的编译器就是跟double一样的精度)。再就是注意long double的输出%Lf,L必须大写,小数位个数的输出什么的跟其他类型的一样。

总而言之,太简单了,也太奇怪了。

H 神奇的字母(二)

题目链接

https://ac.nowcoder.com/acm/contest/5600/H

解题思路

(copy的大佬的,刚做的时候感觉这是个水题啊,这应该就是我和大佬之间的差距之一。看了大佬题解才发现,大佬说的这几个我还真不知道,于我而言,从中学到东西其实也不能算水题)

知识点:无
思路:这道题想更多的考察大家对“若干组输入”的处理方式,可以扫描空格回车。主要的解决方法有以下几种:
①while(cin>>str)
②while(scanf("%s",str)!=EOF) //EOF可以用-1代替。
③while(gets(str)!=NULL)
第一个是c++写法,后两个是C语言写法。注意对应的头文件即可(或使用万能头文件)

我稍微讲一下,cin输入string类型的时候还是遇到tab,空格,回车等结束一个字符串的输入,while(cin>>str)好像是什么重载运算符之类的,我还没自学明白,所以不多讲这个了。记住的话就是输入str成功cin就返回非零值,输入失败就返回0。体现在这个本题样例的过程里就是,输入第一个字符串ranko,遇到空格,输入成功,进行while内部的操作,接着再次cin>>str,sekai,遇到空格,输入成功……就是这么个过程。

再补充一点,有同学可能纳闷怎么结束输入啊,为什么一直没有输出啊。
你只需要摁ctrl+z就能结束输入了。(本题注意一点,输入完样例的第二行的时候要先输入一个回车表示最后一个字符串输入成功且结束,再摁ctrl+z方可结束)

AC代码

我的代码输入不是用的大佬的方法,推荐用大佬的,大佬的可能好点。

#include<bits/stdc++.h>
using namespace std;
const int N=30;
int cnt[N],ans,maxx;
int main(){
 
 	char ch;
 	while(~scanf("%c",&ch)){
 	if(ch==' ') continue;
  	if(ch=='\n')continue;
  	int id=ch-96;  
  	cnt[id]++;
  	if(cnt[id]>maxx){maxx=cnt[id];ans=id;} 
 	}
	// cout<<maxx;
 	cout<<(char)(ans+96)<<endl;
}

I 十字爆破

题目链接

https://ac.nowcoder.com/acm/contest/5600/I

解题思路

挺简单的一道题,在输入输出中就能实现。
每一位上的数存一个数组里,每一行的和存一个数组里,每一列的和存一个数组里,循环输出的时候每一对应行列之和就是这个位置对应行之和与对应列之和的和减去这个位置的数本身(因为行数组中存了一次本位数字,列数组也存了一次,因此多加一次要减去)
要说难的话,应该是难在如何开辟mp[1e6][1e6]的数组。
傻白甜的做法就是用vector建立二维数组,注意一下vector的输入要用push_back()
大佬的做法就是用二维编号存储输入的数据,(这个方法在我之前的博客里好像讲过这里就不多讲了)这样开辟的数组的大小明显比上面的方法小的多,更为简洁,上面的没有变通就略显笨重。

我的AC代码

没错!我就是那个傻白甜!

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=1e6+10;
ll suml[N],sumh[N],tmp;
vector<ll> mp[N];
int main(){
 	int n,m;
// 	cin>>n>>m;
	scanf("%d%d",&n,&m);
 	for(int i=0;i<n;i++)
  		for(int j=0;j<m;j++){
  			scanf("%lld",&tmp);
   			mp[i].push_back(tmp);
   			sumh[i]+=mp[i][j];
   			suml[j]+=mp[i][j];
  		}
  	for(int i=0;i<n;i++){
  		for(int j=0;j<m;j++)
   			printf("%lld ",sumh[i]+suml[j]-mp[i][j]);
  		printf("\n");
 	} 
}   

大佬的AC代码

准确的说,是我仿照大佬的又写了一遍。肯定也是没问题的。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=1e6+10;
ll suml[N],sumh[N],mp[N];
int n,m;
int main(){
 	scanf("%d%d",&n,&m);
 	for(int i=0;i<n*m;i++) 
  		scanf("%lld",&mp[i]),sumh[i/m]+=mp[i],suml[i%m]+=mp[i];
 	for(int i=0;i<n*m;i++){
	//  	if(!(i%m)&&i) cout<<endl;
 		cout<<sumh[i/m]+suml[i%m]-mp[i]<<' '; 
 		if(i%m==m-1) cout<<endl; 
 	} 
}

J 异或和之和

题目链接

https://ac.nowcoder.com/acm/contest/5600/J

解题思路

异或,就是位运算。因为数据规模非常大2e5,要是暴力枚举的话,Cn3,时间复杂度是O(n^ 3)=O(1e16),显然不行。所以我们要想个巧妙的方法去计算和。
进行异或的话,实际上就是从n个数中挑出3个数,从低位向高位进行同位异或。只分析一个二进制位的话,我们可以得到这样的规律:1 xor 1 xor 1 = 1;1 xor 0 xor 0 = 1 。
我们可以计算任意三个数相应的每一位二进制的异或值,将每种组合(三个数为一个组合)的异或值之和,乘以当前二进制位的权值,就是当前二进制位对最终结果的贡献。那么累加第一位,第二位……二进制的贡献,就是最终答案。

简化实现

如果直接使用for循环去从每个二进制位任意地挑三个数进行异或组合,这样时间复杂度不降反升。我们根据上面的规律,三个数中有1个数是1或者三个数是1,三者异或就为1,其他情况异或值均为0,当异或值为0的时候,乘以权值仍为0,对结果无贡献。因此我们只需要求出异或值为1的和就行,1的和就是1的个数,也是异或值为1组合数。
我们计算一下每位二进制对应的异或值为1的组合数,假设t个1,那么就有n-t个0,一个1两个0的组合数:(n-t) * (n-t-1) * t/2,稍微说一下怎么得到的,C(n-t)2 =(n-t) * (n-t-1)/2从n-t个0中选两个0,乘以t是从t个1中选一个1,不用担心0不足两个或者没有1的情况,都包含在了式子中;三个s1组合数:t * (t-1) * (t-2)/6,Ct3,从t个1中选3个1的情况数,同样的1的个数不足三个的情况也包含在内。
上述两情况之和,乘以权值,就是当前二进制位对答案的贡献,累加求出每一位的就是答案。
我们在统计每一个二进制位有多少个1的时候可以在输入的时候实现,这样后面直接循环每一个二进制位就行了。

AC代码

我不会……

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const ll mod=1e9+7;
ll ans,x;
ll cnt[70],n;
int main(){
 	cin>>n;
 	for(int i=1;i<=n;i++){
  		cin>>x;
  		ll j=0;
  		while(x){
   			if(x&1LL) cnt[j]++;
   			x>>=1;
   			j++;
  		}
 	}
 	for(int i=0;i<=64;i++){//1e18差不多就是longlong的长度,就是64位
  		ll ans1=((n-cnt[i])*(n-cnt[i]-1)*cnt[i]/2)%mod;//一个1两个0的情况下的组合数
  		ll ans2=(cnt[i]*(cnt[i]-1)*(cnt[i]-2)/6)%mod;//三个1的情况下的组合数
  		ll ans3=(1LL<<i)%mod;//当前二进制位的权值
  		ans=(ans+(ans1+ans2)*ans3)%mod;//求和累加
 	}
 	cout<<ans;
} 

错因分析

我没做出来,实在没想出如何降低时间复杂度,因为我只想到了暴力,所以一直在优化暴力。想到了拆位,bitset,但是没想到可以按位求再累加,而且每种组合的情况数也可以直接带公式,不用去循环。希望掌握这个方法。
应该学到的是:
1.位运算,对于每一位来说,结果不是1就是0。(看似是废话,但是你就是不会用,甚至想不到去用)
2.把整个数拆成每一位,分开计算。拆分之后每个二进制位都是独立的,因此可以分开求再累加。

最后的最后我想问上天一句:我什么时候才能成为大佬啊啊啊!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不牌不改

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值