链接:https://ac.nowcoder.com/acm/contest/3005
来源:2020牛客寒假算法基础集训营4
A 欧几里得(规律)
思路:手推规律发现第三项之后可形成斐波那契数列。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=82;
ll fib[maxn]={1,3,5};
int main(){
for(int i=3;i<=maxn;i++) fib[i]=fib[i-1]+fib[i-2];
int T; scanf("%d",&T);
while(T--){
int n; scanf("%d",&n);
printf("%lld\n",fib[n]);
}
return 0;
}
B 括号序列(贪心)
思路:经典贪心问题,当我们遇到左半部分的时候,将字符存入栈中,当遇到右半部分的时候,我们检查栈顶是否为是左半部分(此时需要注意栈顶为空的时候),如果匹配就弹出栈顶,如果不匹配可以放入栈顶。最后查看栈中是否还有字符即可(如果全部匹配,那么栈中所有字符都会弹出)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
char str[maxn];
stack<char>s;
int main(){
scanf("%s",str);
int n=strlen(str);
for(int i=0;i<n;i++){
if(str[i]=='('||str[i]=='{'||str[i]=='['||!s.size()){
s.push(str[i]); continue;
}
if(str[i]==')'){
if(s.top()=='(') s.pop();
else s.push(str[i]);
}else if(str[i]==']'){
if(s.top()=='[') s.pop();
else s.push(str[i]);
}else if(str[i]=='}'){
if(s.top()=='{') s.pop();
else s.push(str[i]);
}
}
if(s.size()) puts("No");
else puts("Yes");
return 0;
}
C 子段乘积(分数取模)
思路:从头开始遍历,我们首先乘出来 k k k 个数,如果遇到 0 0 0,我们可以想到所有含有 0 0 0 的区间最大值都是 0 0 0,所以含有 0 0 0 的区间我们可以直接略过,也就是说如果某个位置上是 0 0 0,我们可以不计算它。此时我们考虑区间内满足条件的都不是 0 0 0 的时候,我们先计算出 k k k 个数的乘积 a n s ans ans,当我们在向右走的时候,我们想要得到满足条件的区间 此时需要做的是 ( a n s ∗ a [ i ] / a [ i − k ] ) % m o d (ans*a[i]/a[i-k])\%mod (ans∗a[i]/a[i−k])%mod 此时就需要使用乘法逆元。如果不对上面我所说的 0 0 0 的情况不进行处理,那么我们在模拟上述的过程中我出现 ∗ 0 和 / 0 *0 和 /0 ∗0和/0的情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=2e5+10;
int a[maxn];
ll quickPow(ll a,ll b){
ll ans=1,res=a;
while(b){
if(b&1) ans=ans*res%mod;
res=res*res%mod;
b>>=1;
}
return ans%mod;
}
int main(){
int n,k; scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
if(k>=n){
ll sum=1;
for(int i=1;i<=n;i++) sum=sum*a[i]%mod;
printf("%lld\n",sum);
}else{
ll sum=1,cnt=0,mmax=0;
for(int i=1;i<=n;i++){
cnt++;
if(a[i]==0) { cnt=0;sum=1;continue; }
sum=sum*a[i]%mod;
if(cnt==k) mmax=max(mmax,sum);
if(cnt==k+1){
cnt--;
sum=sum*quickPow(a[i-k],mod-2)%mod;
mmax=max(mmax,sum);
}
}
printf("%lld\n",mmax);
}
return 0;
}
D 子段异或(异或性质)
思路:我们知道
A
A
A ^
A
A
A =
0
0
0,
0
0
0 ^
A
=
A
A=A
A=A 我们先维护一个前缀异或和,如图:
我们假设如果有这么一个前缀和,我们可以看到
s
u
m
[
1
]
=
s
u
m
[
2
]
=
s
u
m
[
3
]
=
s
u
m
[
4
]
=
0
sum[1] = sum[2] = sum[3] = sum[4] = 0
sum[1]=sum[2]=sum[3]=sum[4]=0,
s
u
m
[
5
]
=
s
u
m
[
6
]
=
s
u
m
[
7
]
=
s
u
m
[
8
]
=
s
u
m
[
9
]
sum[5] = sum[6] = sum[7] = sum[8] = sum[9]
sum[5]=sum[6]=sum[7]=sum[8]=sum[9],如果有
i
<
j
i < j
i<j,就有:
s
u
m
[
i
]
=
a
[
1
sum[i] = a[1
sum[i]=a[1] ^
a
[
2
a[2
a[2] ^
.
.
.
.
....
.... ^
a
[
i
]
a[i]
a[i]
s
u
m
[
i
]
=
s
u
m
[
j
]
sum[i] = sum[j]
sum[i]=sum[j]
s
u
m
[
j
]
=
a
[
1
]
sum[j] = a[1]
sum[j]=a[1] ^
a
[
2
]
a[2]
a[2] *^
.
.
.
.
....
.... ^
a
[
i
]
a[i]
a[i] ^
a
[
i
+
1
]
a[i+1]
a[i+1] ^
.
.
.
.
....
.... ^
a
[
j
]
=
s
u
m
[
j
]
a[j] = sum[j]
a[j]=sum[j] ^
a
[
i
+
1
]
a[i+1]
a[i+1] ^
.
.
.
.
....
.... ^
a
[
j
]
a[j]
a[j]
则有
a
[
i
+
1
]
a[i+1]
a[i+1] ^
.
.
.
.
....
.... ^
a
[
j
]
=
0
a[j] = 0
a[j]=0
所以我们只需要知道
s
u
m
sum
sum出现的次数即可,如果是
0
0
0 的话,那么就要算上本身的
0
0
0,我们可以找到
c
n
t
∗
(
c
n
t
+
1
)
/
2
cnt*(cnt+1)/2
cnt∗(cnt+1)/2个区间,如果不是
0
0
0,我们可以找到
c
n
t
∗
(
c
n
t
−
1
)
/
2
cnt*(cnt-1)/2
cnt∗(cnt−1)/2。如果一个数已经计算过,那么后面就不用再次计算满足题意的区间。
//我的程序复杂了,直接开cnt[]数组,计算每个数字出现了多少次即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
ll a[maxn],sum[maxn],sum1[maxn];
map<ll,int>m;
int main(){
//cout<<(2^3^4^5)<<endl;
int n; scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);
if(i) sum[i]=sum[i-1]^a[i];
else sum[i]=a[i];
sum1[i]=sum[i];
m[sum[i]]=1;
}
//for(int i=0;i<n;i++) cout<<sum[i]<<" "; cout<<endl;
sort(sum1,sum1+n);
ll ans=0;
for(int i=0;i<n;i++){
int l=lower_bound(sum1,sum1+n,sum[i])-sum1;
int r=upper_bound(sum1,sum1+n,sum[i])-sum1-1;
ll cnt=r-l+1;
if(m[sum[i]]){
if(sum[i]==0){
ans+=cnt*(cnt+1)/2;
}else{
ans+=cnt*(cnt-1)/2;
}
m[sum[i]]=0;
}
}
printf("%lld\n",ans);
return 0;
}
上面的思路其实被我想复杂了,我们可以一边记录 s u m sum sum 出现的次数,一边计算满足条件的区间,在计算前缀和的过程中,如果前面出现过多少次 s u m sum sum,那么对于这个点来说就会有几个满足条件的区间,如果想要包含 l = = r l==r l==r 的情况,我们只需要初始化 m [ 0 ] = 1 m[0]=1 m[0]=1 即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
ll a[maxn],sum[maxn];
map<ll,int>m;
int main(){
int n; scanf("%d",&n);
ll ans=0; m[0]=1;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum[i]=sum[i-1]^a[i];
//cout<<sum[i]<<" "<<m[sum[i]]<<endl;
ans+=(ll)m[sum[i]];
m[sum[i]]++;
}
printf("%lld\n",ans);
return 0;
}
E 最小表达式(思维+大数)
思路:首先我们先把所有的数字字符放入另外一个数组中,将 + + + 号字符放入栈 s s s 中,很容易看出来我们需要对所有的数字字符进行排列,然后我们可以把这些数字字符分成 s . s i z e ( ) + 1 s.size()+1 s.size()+1 组,比赛的时候我是把这些数字字符分组放入一个二维数组中,之后进行大数加法,这种方法超时,但是我又想不出其他做法。赛后看了题解才知道我们可以按位直接做加法,也就是我们从后往前遍历,每次经过 s . s i z e ( ) + 1 s.size()+1 s.size()+1 个数字,就代表着进位一次。(因为我们要把这些数字分成 s . s i z e ( ) + 1 s.size()+1 s.size()+1 组,那么从后往前,前面 s . s i z e ( ) + 1 s.size()+1 s.size()+1个数字一定都是每组数字的个位,我们把它们相加即可,下一个 s . s i z e ( ) + 1 s.size()+1 s.size()+1 个数字就是十位,以此类推直到全部算完为止。这样一边计算每一位,一边进位就可以得到最终结果。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
char str[maxn],str1[maxn];
int ans[maxn];
stack<int>s;
int main(){
scanf("%s",str);
int n=strlen(str);
int cnt=0;
for(int i=0;i<n;i++){
if(str[i]=='+') s.push(str[i]);
else str1[cnt++]=str[i];
}
sort(str1,str1+cnt);
//puts(str1);
int len=0,cntNumber=0;
for(int i=cnt-1;i>=0;i--){
cntNumber++;
if(cntNumber==(int)s.size()+1||i==0){
ans[len++]+=str1[i]-'0';
ans[len]=ans[len-1]/10;
ans[len-1]%=10;
cntNumber=0;
}else ans[len]+=str1[i]-'0';
}
if(ans[len]) printf("%d",ans[len]);
for(int i=len-1;i>=0;i--) printf("%d",ans[i]);
return 0;
}
F 树上博弈(思维+搜索)
思路:必胜策略:两人相距的距离为偶数。直接使用
d
f
s
dfs
dfs 或者
b
f
s
bfs
bfs 计算出每个点距离根结点的距离。算出距离后如下图:
看图我们可以知道所有黑点之间可以形成基数的距离,所有白点之间也都可以形成偶数距离,直接利用求和公式即可算出答案。
//dfs
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int cnt=0,head[maxn],deep[maxn]={-1};
struct Edge{
int u,v,next;
}edge[maxn<<1];
void addedge(int u,int v){
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs(int fa,int v){
deep[v]=deep[fa]+1;
for(int i=head[v];i!=-1;i=edge[i].next){
if(edge[i].v==fa) continue;
dfs(v,edge[i].v);
}
}
int main(){
int n; scanf("%d",&n);
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=2;i<=n;i++){
int fa; scanf("%d",&fa);
addedge(fa,i); addedge(i,fa);
}
dfs(0,1);
int cnt0=0,cnt1=0;
for(int i=1;i<=n;i++){
if(deep[i]&1) cnt1++;
else cnt0++;
}
ll ans=1ll*(cnt0-1)*cnt0;
ans+=1ll*cnt1*(cnt1-1);
printf("%lld\n",ans);
return 0;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int cnt=0,head[maxn],deep[maxn];
bool vis[maxn];
struct Edge{
int u,v,next;
}edge[maxn<<1];
struct Node{
int n,deep;
Node(){};
Node(int n,int deep):n(n),deep(deep){};
};
void addedge(int u,int v){
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void bfs(int s){
queue<Node>q; q.push(Node(s,0));
while(!q.empty()){
Node node=q.front(); q.pop();
deep[node.n]=node.deep;
for(int i=head[node.n];i!=-1;i=edge[i].next){
if(!vis[edge[i].v]){
vis[edge[i].v]=true;
q.push(Node(edge[i].v,node.deep+1));
}
}
}
}
int main(){
int n; scanf("%d",&n);
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=2;i<=n;i++){
int fa; scanf("%d",&fa);
addedge(fa,i); addedge(i,fa);
}
bfs(1);
int cnt0=0,cnt1=0;
for(int i=1;i<=n;i++){
if(deep[i]&1) cnt1++;
else cnt0++;
}
ll ans=1ll*cnt0*(cnt0-1);
ans+=1ll*cnt1*(cnt1-1);
printf("%lld\n",ans);
return 0;
}
I 匹配星星(贪心+STL)
让我们先来看一个三维的坐标图
我们对这些点,先按照
x
x
x 进行排序,如果
x
x
x 相同,我们就按照
z
z
z排序,然后我们遍历数组,如果我们遍历到一个点的
z
z
z 是
0
0
0,那么我们将它的
y
y
y 值存入
m
u
l
t
i
s
e
t
multiset
multiset ,如果这个点的
z
z
z是
1
1
1,那么我们之前存入
y
y
y 的点的
x
,
z
x,z
x,z 值一定都比当前的值小,我们只需要关注
y
y
y 即可,只要能够找到比当前点的
y
y
y 值小的点,就可以完成配对,将点从
m
u
l
t
i
s
e
t
multiset
multiset 中删除。我们利用
m
u
l
t
i
s
e
t
multiset
multiset 二分查找到
≥
y
≥ y
≥y 的迭代器的位置,那么此位置前面一个位置存入的值一定比
y
y
y 小(如果当前迭代器位置不是
s
.
b
e
g
i
n
(
)
s.begin()
s.begin() 的话),找到以后就删除前面一个迭代器位置上的值,配对值加一。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
struct Node{
int x,y,z;
bool operator<(const Node &node) const{
if(x==node.x) return z<node.z;
return x<node.x;
}
}node[maxn];
multiset<int>ms;
int main(){
int n; scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d%d",&node[i].x,&node[i].y,&node[i].z);
sort(node+1,node+n+1);
int ans=0;
for(int i=1;i<=n;i++){
if(node[i].z==0) ms.insert(node[i].y);
else{
//>=当前点y的第一个迭代器的位置,前面一个位置就是要找到的需要匹配的点
multiset<int>::iterator it=ms.lower_bound(node[i].y);
if(it==ms.begin()) continue;
ms.erase(--it);
ans++;
}
}
printf("%d\n",ans);
return 0;
}