暴力破解法
Meet-in-the-middle
給你 \(n\) 個數字 \((n\leq 40)\) 和一個 \(x\)
請問有多少種取法讓總和等於 \(x\)
先把序列切成左右兩半後暴力枚舉
再找有沒有可能有類似分治的方法將答案合併
此時枚舉左半,對右半做二分搜就好了
\(10^{12}\) 被砍到只剩 \(2\times 10^{7}\) 了
#include<iostream>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
signed main(){
int n,x,mid,t;
vector<int> p,q,a,b;
cin>>n>>x;
int l=(n+1)/2,r=n-l;
for(int i=0;i<l;i++){
cin>>t;
p.push_back(t);
}
for(int i=l;i<n;i++){
cin>>t;
q.push_back(t);
}
for(int i=0;i<(1<<l);i++){
int k[21],cur=i,now=0;
for(int j=0;j<l;j++){
k[j]=cur%2;
cur/=2;
}
for(int j=0;j<l;j++) now+=k[j]*p[j];
a.push_back(now);
}
for(int i=0;i<(1<<r);i++){
int k[21],cur=i,now=0;
for(int j=0;j<r;j++){
k[j]=cur%2;
cur/=2;
}
for(int j=0;j<r;j++) now+=k[j]*q[j];
b.push_back(now);
}
int ans=0;
sort(b.begin(),b.end());
for(int i=0;i<a.size();i++) ans+=(int)(upper_bound(b.begin(),b.end(),x-a[i])-lower_bound(b.begin(),b.end(),x-a[i]));
cout<<ans<<"\n";
}
sqrt decomposition
看到這題你想到什麼?
資料結構(線段數、稀疏表、treap)....
最接近暴力解的解法?
切一切!
4 | 2 | 1 |
---|
4 | 8 | 7 | 6 | 2 | 9 | 1 | 3 | 2 |
---|
以剛剛那題舉例,暴力查詢複雜度是\(O(n^2)\)
假設每 \(k\) 數字個分成1塊,查詢時複雜度就是\(O(k+\frac{n}{k})\)
\(min(8,7,2,1)\)=1
全部數字每\(K\)格切一塊
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
const int N=1e5+30;
int a[N],sq1[350],sq2[350]; //10^2.5 =316.22
signed main(){
int n,m,sq;
cin>>n>>m;
sq=sqrt(n);
for(int i=0;i<350;i++){
sq1[i]=-1;
sq2[i]=1e15;
}
for(int i=0;i<n;i++){
cin>>a[i];
sq1[i/sq]=max(sq1[i/sq],a[i]);
sq2[i/sq]=min(sq2[i/sq],a[i]);
}
int l,r;
while(m--&&cin>>l>>r){
l--,r--;
int L=l/sq,R=r/sq,mn=1e15,mx=-1;
if(L==R){
for(int i=l;i<=r;i++){
mx=max(mx,a[i]);
mn=min(mn,a[i]);
}
}else{
for(int i=L+1;i<R;i++){
mx=max(mx,sq1[i]);
mn=min(mn,sq2[i]);
}
for(int i=l;i<(L+1)*sq;i++){
mx=max(mx,a[i]);
mn=min(mn,a[i]);
}
for(int i=R*sq;i<=r;i++){
mx=max(mx,a[i]);
mn=min(mn,a[i]);
}
}
cout<<mx-mn<<"\n";
}
}
4 | 1 |
---|
4 | 8 | 7 | 6 | 9 | 1 | 3 | 2 |
---|
100
6
2
2
8 |
---|
4 | 8 | 7 | 6 | 100 | 9 | 3 | 2 |
---|
就算用了分塊,你還是逃不過懶標
tag:200
200
2
200
不完整包含該塊(左右最多2塊)
完整包含該塊(最多\(\frac{n}{k}\)塊)
6
1
1
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
const int N=2e5+30;
int n,q,k,a[N],sq[460]; // sqrt(2*10^5) =447.213
void upd(int pos,int val){
int p=pos/k;
a[pos]=val,sq[p]=1e9+7;
for(int i=p*k;i<(p+1)*k;i++) sq[p]=min(sq[p],a[i]);
}
int que(int l,int r){
int ans=1e9+7,L=l/k,R=r/k;
if(L==R){
for(int i=l;i<=r;i++) ans=min(ans,a[i]);
}else{
for(int i=L+1;i<R;i++) ans=min(ans,sq[i]);
for(int i=l;i<(L+1)*k;i++) ans=min(ans,a[i]);
for(int i=R*k;i<=r;i++) ans=min(ans,a[i]);
}
return ans;
}
signed main(){
cin>>n>>q;
k=sqrt(n);
for(int i=0;i<455;i++) sq[i]=1e9+7;
for(int i=0;i<n;i++){
cin>>a[i];
sq[i/k]=min(sq[i/k],a[i]);
}
int x,y,z;
while(q--&&cin>>x>>y>>z){
if(x==1) upd(y-1,z);
else cout<<que(y-1,z-1)<<"\n";
}
}
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
const int N=2e5+30;
int n,q,k,a[N],sq[460],lazy[460]; // sqrt(2*10^5) =447.213
void upd(int l,int r,int val){
int L=l/k,R=r/k;
if(L==R){
sq[L]=1e9+7;
for(int i=L*k;i<(L+1)*k;i++){
if(i>=l&&i<=r) a[i]=val;
else if(lazy[L]!=-1) a[i]=lazy[L];
sq[L]=min(sq[L],a[i]);
}
lazy[L]=-1;
}else{
for(int i=L+1;i<R;i++) sq[i]=lazy[i]=val;
for(int i=L*k;i<(L+1)*k;i++){
if(i>=l) a[i]=val;
else if(lazy[i]!=-1) a[i]=lazy[L];
sq[L]=min(sq[L],a[i]);
}
for(int i=R*k;i<min((R+1)*k,n);i++){
if(i<=r) a[i]=val;
else if(lazy[R]!=-1) a[i]=lazy[R];
sq[R]=min(sq[R],a[i]);
}
lazy[L]=lazy[R]=-1;
}
}
int que(int l,int r){
int ans=1e9+7,L=l/k,R=r/k;
if(L==R){
if(lazy[L]!=-1) ans=sq[L];
else for(int i=l;i<=r;i++) ans=min(ans,a[i]);
}else{
for(int i=L+1;i<R;i++) ans=min(ans,sq[i]);
if(lazy[L]!=-1) ans=min(ans,sq[L]);
else for(int i=l;i<(L+1)*k;i++) ans=min(ans,a[i]);
if(lazy[R]!=-1) ans=min(ans,sq[R]);
else for(int i=R*k;i<=r;i++) ans=min(ans,a[i]);
}
if(ans==-1) exit(0);
return ans;
}
signed main(){
cin>>n>>q;
k=sqrt(n);
for(int i=0;i<455;i++) sq[i]=1e9+7,lazy[i]=-1;
for(int i=0;i<n;i++){
cin>>a[i];
sq[i/k]=min(sq[i/k],a[i]);
}
int x,y,z,val;
while(q--&&cin>>x>>y>>z){
if(x==1) upd(y-1,y-1,z);
else cout<<que(y-1,z-1)<<"\n";
}
}
壓完行數發現我的線段樹行數比較少 XD
防破台題
共有 \(q\) 筆操作:
先對每一格找出下一格
對於每一筆詢問直接模擬
\(O(q\times n)\) TLE
防破台題怎麼可能這麼簡單
把 \(k\) 個位置當作一塊,共 \(\frac{n}{k}\) 塊
記錄下一個位置 \(next_i\)
如果下一個位置還在同一塊,那就記錄下下個位置,同時記錄最後走到的上一個位置 \(last_i\) 和移動的步數 \(step_i\)
複雜度:\(O(n)\)
倒著做 !
a | 1 | 1 | 1 | 1 | 1 | 2 | 8 | 2 |
---|
next | 2 | 3 | 4 | 5 | 6 | 8 | X | X |
---|
next | 3 | 3 | 5 | 5 | 8 | 8 | X | X |
---|
以範測舉例 \(n=8, k=2\)
last | 2 | 2 | 4 | 4 | 6 | 6 | 7 | 8 |
---|
step | 2 | 1 | 2 | 1 | 2 | 1 | 1 | 1 |
---|
X 代表飛出去了,可以設一個比 \(n\) 大的數來代替
更新這一格的彈力
將整塊的 \(next_i\), \(last_i\), \(step_i\) 照初始化的方式更新
複雜度:\(O(\sqrt{n})\)
a | 1 | 1 | 1 | 1 | 1 | 2 | 8 | 2 |
---|
next | 3 | 3 | 5 | 5 | 8 | 8 | X | X |
---|
last | 2 | 2 | 4 | 4 | 6 | 6 | 7 | 8 |
---|
step | 2 | 1 | 2 | 1 | 2 | 1 | 1 | 1 |
---|
如果其中一塊被改動了,同一塊在它左邊的位置也要同時改變
3
4
1
1
如果修改這一格,前一格也要一起修改
照著\(next_i\), \(last_i\), \(step_i\) 模擬,同時記錄上一格和總步數
複雜度:\(O(\sqrt{n})\)
a | 3 | 1 | 1 | 1 | 1 | 2 | 8 | 2 |
---|
next | 4 | 3 | 5 | 5 | 8 | 8 | X | X |
---|
last | 1 | 2 | 4 | 4 | 6 | 6 | 7 | 8 |
---|
step | 1 | 1 | 2 | 1 | 2 | 1 | 1 | 1 |
---|
最後抵達: 8
總步數:\(1+1+2+1=5\)
(假設初始位置為1)
#include<iostream>
#include<cmath>
using namespace std;
typedef pair<int,int> p;
const int N=1e5+30;
int n,m,r,k,power[N],nxt[N],stp[N],lst[N];
void find1(int cur){
int q=cur/k;
for(int i=cur;i>=max(q*k,1);i--){
int f=power[i]+i;
if(f>n) nxt[i]=n+1,stp[i]=1,lst[i]=i;
else if(f>=(q+1)*k) nxt[i]=f,stp[i]=1,lst[i]=i;
else nxt[i]=nxt[f],stp[i]=stp[f]+1,lst[i]=lst[f];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
k=sqrt(n);r=(n/k)+1;
for(int i=1;i<=n;i++) cin>>power[i];
for(int i=r;i>0;i--) find1(min(n,i*k-1));
while(m--){
int k,a,b;
cin>>k>>a;
if(!k){
cin>>b;
power[a]=b;
find1(a);
}else{
int now=a,ans1=a,ans2=0;
while(now<=n) ans2+=stp[now],ans1=lst[now],now=nxt[now];
cout<<ans1<<" "<<ans2<<"\n";
}
}
}
Solution A | Solution B | |
---|---|---|
Case 1 | ||
Case 2 |
TLE
AC
AC
TLE
if(Case1) SolutionA();
else SolutionB();
有 \(n\) 個點和 \(m\) 條邊
假設你能 \(O(1)\) 找出 \((i,j)\) 有邊
問可以組成幾個三角形
\((n\leq 10^5,\ m\leq 3 \times 10^5)\)
枚舉每一條邊,如果邊上的兩點都和這個點有邊,可以構成三角形
複雜度:\(O(M)\)
枚舉與該點相鄰的每個點,如果任兩個點有邊
可以構成三角形
複雜度:\(O(d^2)\)
Solution A | Solution B | |
---|---|---|
\(O(M)\)
\(O(d^2)\)
\(d < K\)
\(d \geq K\)
\(O(K\times\sum d_i)\)
\(=O(MK)\)
最多\(\frac{2M}{K}\)個點
\(O(\frac{M^2}{K})\)
\(K\) 取 \(\sqrt{M}\) 得最佳複雜度 \(O(M\sqrt{M})\)
Mo's algorithm
假設你有 \([L,R]\) 的答案
你可以 \(O(a)\) 求得\([L-1,R]\)、\([L+1,R]\)、\([L,R-1]\)、\([L,R+1]\)
(就是左右界向外推或向內縮一格的答案)
\(O(a)\) 通常不能太大,通常是 \(O(1)\) 或 \(O(log\ n)\)
#include<iostream>
#include<algorithm>
#include<cmath>
#define p1 first
#define p2 second
using namespace std;
typedef pair<int,int> p;
const int N=1e5+30,M=N*10;
int s[N],a[N],b[N],mx=0; // a:數字出現的次數 b: a[i]的出現次數
pair<int,p> ans[M];
struct Q{
int sq,r,l,num;
}que[M];
bool cmp(Q x,Q y){
if(x.sq!=y.sq) return x.sq<y.sq;
return x.r<y.r;
}
void add(int x){
a[s[x]]++;
b[a[s[x]]]++;
b[a[s[x]]-1]--;
if(mx==a[s[x]]-1) mx=a[s[x]];
}
void sub(int x){
a[s[x]]--;
b[a[s[x]]]++;
b[a[s[x]]+1]--;
if(mx==a[s[x]]+1&&b[a[s[x]]+1]==0) mx=a[s[x]];
}
int main(){
int n,m,k;
cin>>n>>m;
k=n/sqrt(m);
for(int i=0;i<n;i++) cin>>s[i];
for(int i=0;i<m;i++){
cin>>que[i].l>>que[i].r;
que[i].l--,que[i].r--;
que[i].sq=que[i].l/k,que[i].num=i;
}
sort(que,que+m,cmp);
for(int i=0;i<=n+5;i++) a[i]=b[i]=0;
b[0]=n;
int L=que[0].l,R=que[0].r;
for(int i=que[0].l;i<=que[0].r;i++) add(i);
ans[0]={que[0].num,{mx,b[mx]}};
for(int i=1;i<m;i++){
while(R<que[i].r) add(++R);
while(L>que[i].l) add(--L);
while(R>que[i].r) sub(R--);
while(L<que[i].l) sub(L++);
ans[i]={que[i].num,{mx,b[mx]}};
}
sort(ans,ans+m);
for(int i=0;i<m;i++) cout<<ans[i].p2.p1<<" "<<ans[i].p2.p2<<"\n";
}