6.20考試總結(NOIP模擬9)[斐波那契·數顏色·分組]
T1 斐波那契
解題思路
\(70pts\)做法
這個做法比較暴力,考場上也是看到範圍\(10^{12}\)後知道需要推式子,但是感覺自己太菜了,沒敢輕易嘗試,然後就去搞\(10^6\)的部分分去了。。。
比較好理解,直接暴力建樹(其實就是找到每個節點的父親節點),然後直接在樹上搞 LCA 就好了,程式碼實現上也沒有什麼太大問題。
code
#include<bits/stdc++.h> using namespace std; const int N=1e7+10,M=N<<1; int n,m,cnt=1,las=1,f[N][25],dep[N]; struct Ques { int a,b; }q[N]; inline void build() { while(cnt<n) { int temp=las; las=cnt; for(int i=1;i<=temp;i++) { f[++cnt][0]=i; dep[cnt]=dep[i]+1; if(cnt>=n) break; } } } inline void LCA_init() { f[1][0]=1; for(int j=1;j<=20;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; } int LCA_ask(int x,int y) { if(x==y) return x; if(dep[x]>dep[y]) swap(x,y); for(int i=20;i>=0;i--) if(dep[x]<=dep[f[y][i]]) y=f[y][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) { x=f[x][i]; y=f[y][i]; } return f[x][0]; } int main() { // freopen("fibonacci2.in","r",stdin); // freopen("fibonacci2.out","w",stdout); scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%d%d",&q[i].a,&q[i].b); n=max(n,max(q[i].a,q[i].b)); } build(); LCA_init(); /* for(int i=1;i<=n;i++) { cout<<i<<"To: "; for(int j=head[i];j;j=nxt[j]) cout<<ver[j]<<' '; cout<<endl; } */ for(int i=1;i<=m;i++) printf("%d\n",LCA_ask(q[i].a,q[i].b)); return 0; }
\(100pts\) 做法
仔細觀察序列,可以發現第i個月出生的第j只兔子的編號是\(f(i-1)+j\)父親就是j,顯然\(j<f(i-1)\),然後移一下項:
\[令f(i)<x\le f(i+1),有father(x)=x-f(i) \]然後我們再斐波那契數列上二分(用lower_bound實現)。
對於每一個詢問,選擇編號大的向上跳父親節點,直到找到LCA為止(記得開long long)。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e4,M=3e5+50; int n,m,cnt=2,f[N]; struct Ques { int a,b; }q[M]; #undef int int main() { // #define int register long long #define int long long scanf("%lld",&m); for(int i=1;i<=m;i++) { scanf("%lld%lld",&q[i].a,&q[i].b); n=max(n,max(q[i].a,q[i].b)); } f[1]=f[2]=1; for(int i=3;i<=N;i++) { f[i]=f[i-1]+f[i-2]; if(f[i]>=n) { cnt=i; break; } } for(int i=1;i<=m;i++) { int a=q[i].a,b=q[i].b; while(a!=b) { if(a<b) a^=b^=a^=b; int num=lower_bound(f+1,f+cnt+1,a)-f-1; int temp=max(1ll,num); a-=f[temp]; } printf("%lld\n",a); } return 0; }
T2 數顏色
解題思路
本題做法有很多,也有許多非常好的方法,諸如線段樹,主席樹,二分之類的;
這裡主要說一下來自wtz大佬的分塊做法:
很簡單,對於每一個塊內的不同顏色分別計算個數,更改的時候直接更改個數,以及原序列就好了。
需要注意的就是每個塊的大小 \(\dfrac{1}{2}\)次方會MLE ,\(\dfrac{2}{3}\) 次方會TLE,因此我們有了。。。
\[\huge0.625次方yyds \]於是我們愉快地幹掉了此題。當然似乎別的次方也可以,可以自行嘗試。。。
code
線段樹
主席樹
二分
分塊
#include<bits/stdc++.h> using namespace std; const int N=3e5+10; int n,m,len,tot,pos[N],s[N],sum[N][115],li[N],ri[N]; inline int ask(int l,int r,int col) { int p=pos[l],q=pos[r],ans=0; if(p==q) { for(int i=l;i<=r;i++) ans+=(s[i]==col); return ans; } for(int i=p+1;i<=q-1;i++) ans+=sum[col][i]; for(int i=l;i<=ri[p];i++) ans+=(s[i]==col); for(int i=li[q];i<=r;i++) ans+=(s[i]==col); return ans; } int main() { scanf("%d%d",&n,&m); len=pow(n,0.625); tot=n/len; // cout<<tot<<endl; for(int i=1;i<=n;i++) scanf("%d",&s[i]); for(int i=1;i<=tot;i++) { li[i]=(i-1)*len+1; ri[i]=i*len; } if(ri[tot]<n) { tot++; li[tot]=ri[tot-1]+1; ri[tot]=n; } for(int i=1;i<=tot;i++) for(int j=li[i];j<=ri[i];j++) { pos[j]=i; sum[s[j]][i]++; } for(int i=1,l,r,col,x,opt;i<=m;i++) { scanf("%d",&opt); if(opt==1) { scanf("%d%d%d",&l,&r,&col); printf("%d\n",ask(l,r,col)); } else { scanf("%d",&x); if(pos[x]!=pos[x+1]) { sum[s[x]][pos[x]]--; sum[s[x+1]][pos[x+1]]--; sum[s[x]][pos[x+1]]++; sum[s[x+1]][pos[x]]++; } swap(s[x],s[x+1]); } } return 0; }
T3 分組
解題思路
參考了題解區一篇思路非常好的題解,在這裡講一下自己的見解。
首先明確一下 K 的取值只有 1 或者 2 這裡看資料範圍非常重要!,對於 \(K=1\),\(K=2\) 的情況要分開來做。
K=1
對於 \(K=1\) 的情況,為了保證字典序最小,我們需要倒著列舉序列了。
然後再次觀察資料範圍,發現\(131072 \times2=512^2\),因此我們可以列舉 \(1 \sim 512\) ,令 vis[i] 表示在當前掃到的組裡顏色為 i 的是否存在,檢視是否訪問過 \(x^2-s_i\) 。
-
如果訪問過,表示和第 i 只兔子發生矛盾的已經在這個組裡了,因此需要再次分一個組,並且記錄下分組的邊界,清空 vis 陣列。
-
如果沒有訪問過,把該種顏色的標記成 true 記錄就好了。
K=2
幾乎同樣的思路,我們仍然需要倒著列舉序列。
對於同一組的兔子,狀態之可能有兩種:同一小團體或者在敵對小團體,因此我們用並查集維護。
-
\(\operatorname{find}(1 \sim n)\) 表示 \(1\sim n\)的兔子所在的小團體。
-
\(\operatorname{find}(n+1 \sim 2 \times n)\) 表示 \(1\sim n\)的兔子所在的小團體的敵對小團體。
然後開一個 vector 陣列記錄同一顏色的序號,然後分別對於發生矛盾的兔子進行判斷,同時更新該兔子所在組以及小團體和敵對小團體。
同樣的,如果矛盾無法避免,那就重新開一個組,並清空標記,記錄分割點就好了。
- 注意:並查集合並時要在 find 的基礎上更新
code
#include<bits/stdc++.h>
using namespace std;
const int M=131080;
int n,m,K,las,s[M],fa[M<<1];
vector<int> ans,v[M<<1];
bool vis[M];
int find(int x)
{
if(fa[x]==x)
return x;
return fa[x]=find(fa[x]);
}
void work_1()
{
for(int i=n;i>=1;i--)
{
bool flag=true;
for(int j=1;j<=512;j++)
if(j*j>=s[i])
if(vis[j*j-s[i]])
{
flag=false;
break;
}
if(!flag)
{
for(int j=i+1;j<las;j++)
vis[s[j]]=false;
ans.push_back(i);
las=i+1;
}
vis[s[i]]=true;
}
printf("%d\n",ans.size()+1);
for(int i=ans.size()-1;i>=0;i--)
printf("%d ",ans[i]);
}
int update(int l,int r)
{
for(int i=l+1;i<r;i++)
vector <int>().swap(v[s[i]]);
ans.push_back(l);
return l+1;
}
void work_2()
{
for(int i=1;i<=(n<<1);i++)
fa[i]=i;
for(int i=n;i>=1;i--)
{
for(int j=1;j<=512;j++)
if(j*j>=s[i])
if(v[j*j-s[i]].size())
for(int k=0;k<v[j*j-s[i]].size();k++)
{
int temp=v[j*j-s[i]][k];
if(find(temp)==find(i))
{
las=update(i,las);
break;
}
else
{
fa[find(i+n)]=find(temp);
fa[find(temp+n)]=find(i);
}
}
v[s[i]].push_back(i);
}
printf("%d\n",ans.size()+1);
for(int i=ans.size()-1;i>=0;i--)
printf("%d ",ans[i]);
}
int main()
{
scanf("%d%d",&n,&K);
las=n+1;
for(int i=1;i<=n;i++)
scanf("%d",&s[i]);
if(K==1)
work_1();
else
work_2();
return 0;
}