鏈接: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 子段乘積(分數取模)
思路:從頭開始遍歷,我們首先乘出來 個數,如果遇到 ,我們可以想到所有含有 的區間最大值都是 ,所以含有 的區間我們可以直接略過,也就是說如果某個位置上是,我們可以不計算它。此時我們考慮區間內滿足條件的都不是 的時候,我們先計算出 個數的乘積 ,當我們在向右走的時候,我們想要得到滿足條件的區間 此時需要做的是 此時就需要使用乘法逆元。如果不對上面我所說的 的情況不進行處理,那麼我們在模擬上述的過程中我出現 的情況。
#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 子段異或(異或性質)
思路:我們知道 ^ = , ^ 我們先維護一個前綴異或和,如圖:
我們假設如果有這麼一個前綴和,我們可以看到 ,,如果有 ,就有:
] ^ ] ^ ^
^ *^ ^ ^ ^ ^ ^ ^ ^
則有 ^ ^
所以我們只需要知道 出現的次數即可,如果是 的話,那麼就要算上本身的 ,我們可以找到 個區間,如果不是 ,我們可以找到 。如果一個數已經計算過,那麼後面就不用再次計算滿足題意的區間。
//我的程序複雜了,直接開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;
}
上面的思路其實被我想複雜了,我們可以一邊記錄 出現的次數,一邊計算滿足條件的區間,在計算前綴和的過程中,如果前面出現過多少次 ,那麼對於這個點來說就會有幾個滿足條件的區間,如果想要包含 的情況,我們只需要初始化 即可。
#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 最小表達式(思維+大數)
思路:首先我們先把所有的數字字符放入另外一個數組中,將 號字符放入棧 中,很容易看出來我們需要對所有的數字字符進行排列,然後我們可以把這些數字字符分成 組,比賽的時候我是把這些數字字符分組放入一個二維數組中,之後進行大數加法,這種方法超時,但是我又想不出其他做法。賽後看了題解才知道我們可以按位直接做加法,也就是我們從後往前遍歷,每次經過 個數字,就代表着進位一次。(因爲我們要把這些數字分成 組,那麼從後往前,前面個數字一定都是每組數字的個位,我們把它們相加即可,下一個 個數字就是十位,以此類推直到全部算完爲止。這樣一邊計算每一位,一邊進位就可以得到最終結果。
#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 樹上博弈(思維+搜索)
思路:必勝策略:兩人相距的距離爲偶數。直接使用 或者 計算出每個點距離根結點的距離。算出距離後如下圖:
看圖我們可以知道所有黑點之間可以形成基數的距離,所有白點之間也都可以形成偶數距離,直接利用求和公式即可算出答案。
//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)
讓我們先來看一個三維的座標圖
我們對這些點,先按照 進行排序,如果 相同,我們就按照 排序,然後我們遍歷數組,如果我們遍歷到一個點的 是 ,那麼我們將它的 值存入 ,如果這個點的 是 ,那麼我們之前存入 的點的 值一定都比當前的值小,我們只需要關注 即可,只要能夠找到比當前點的 值小的點,就可以完成配對,將點從 中刪除。我們利用 二分查找到 的迭代器的位置,那麼此位置前面一個位置存入的值一定比 小(如果當前迭代器位置不是 的話),找到以後就刪除前面一個迭代器位置上的值,配對值加一。
#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;
}