BalticOI 2010 Matching Bins【權值BIT】
題目解析
是考試的題目。
雖然是個簽到題但我還是掙扎了很久,而且我個智障把檔名打錯了,簽到失敗嚶嚶嚶
首先簡化一下題意:找到一個最大的\(k\),使得\(a[k+1]\)至\(a[2k]\)中的數都能在\(a[1]\)到\(a[k]\)中匹配到一個比它嚴格小的數(每個數都只能和一個數匹配)。如果沒有這樣的\(k\),輸出\(0\)。
(也可以理解成左半邊的數去在右半邊找一個比它大的數,是一樣的,不過題意理解不同後面的思考分析應該也會相應有所變化)
首先考慮暴力列舉\(k\),從大到小列舉,然後去\(check\)它是否符合情況。
\(k\)的範圍是\(min(\frac n 2,pos)\)
\(pos\)是最大值第一次出現的位置,因為最大值不可能出現在前半部分,沒有箱子可以裝它。注意存在整個數列沒有\(m\)的資料。(出題人大騙子嚶嚶嚶
下面來考慮一下這個\(check\)該怎麼寫。
貪心地想,我們應該給每個右半部分的數安排一個小於它的最大的數,暴力做這個的話,我們就要每次排序,然後\(lower\_bound\),想想就很可怕。
對於這種查詢左邊小於某個數的問題,我們可以考慮一下權值樹狀陣列。
我們把左半邊的數插進權值\(BIT\)裡面,然後對於右半邊的數查詢有多少個比它小的值。
由於左半邊的數可能會被“用掉”,所以我們把右半邊的數從小到大排序,然後查詢有多少個比它小的左半邊的數。
而對於第\(i\)
然後你就得到了\(75pts\)的好成績。
bool check(int x) { if(x==0) return 1; //memset(tree,0,sizeof(tree)); //for(int i=1;i<=x;i++) // Update(a[i],1); if(x!=lim) Update(a[x+1],-1); memcpy(b,a,sizeof(a)); sort(b+x+1,b+2*x+1); for(int i=x+1;i<=2*x;i++) { int tmp=Query(b[i]-1)-(i-x-1); if(tmp<=0) return 0; } return 1; }
而整個\(k\)變化的過程中,左半邊的數可以通過權值\(BIT\)維護,可以可持續發展。但是右邊的數的話,我們每次要排序,要查詢,最壞複雜度可以達到\(n^2\)。而我們又必須要重新排序再列舉右半邊的數才能知道它具體能不能匹配得到一個數字。
我們想想能不能讓右半邊的數也可持續發展一下,換句話說,能不能讓查詢與排序無關。
還是利用權值\(BIT\),把右邊的數的查詢變成一個修改\((a[i]-1,-1)\),而現在\(k\)的變化只會影響到這些位置:(假設\(k-1\)變成$k $,這裡顯然順著做要順一點,不用提前插入一些元素
- \(a[2k-1],a[2k]\)加入右半部分
- \(a[k]\)從前半部分跳到了右半部分
對應一下修改:
- \((a[2k-1]-1,-1)\)
- \((a[2k]-1,-1)\)
- \((a[k]-1,1)\)(\(a[k]\)跑走了,所以把它之前的查詢撤回
- \((a[k],1)\)
如何判斷當前的\(k\)是否可行呢?
查詢權值\(BIT\)的每一個位置(的字首和),如果有小於零的地方,說明有一個右半部分的數找不到匹配的數,不可行,否則就可行。
所有可行解取\(max\)即可。
看了一圈沒有和我寫法一樣的,倒是有兩位初三的同學寫了權值線段樹,一個\(60pts\),一個\(100pts\),不過我看了一下,修改的查詢的\(±1\)正好和我是相反的,大概是因為權值線段樹是區間加減,所以較大的區間應該為\(+1\)才能保證某一段區間和小於零時不成立吧。
別人的寫法好巧妙的,但對於我來說不太自然。
瞪了好久才想到優化,不過最後自己獨立瞪出來了,還順手拿了個最優解,在洛谷享受到了coding的快樂(雖然這題很水
理論複雜度應該是\(O(nmlogm)\),跑得比\(O(nm)\)的演算法快我也不知道為什麼。
►Code View
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 20005
#define M 1005
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
return f*x;
}
int m,n,lim=-1,ans=0;
int a[N],b[N];
int tree[M];
inline int lowbit(int x)
{
return x&(-x);
}
inline int Query(int x)
{
int res=0;
for(;x;x-=lowbit(x))
res+=tree[x];
return res;
}
inline void Update(int x,int val)
{
for(;x<M-5;x+=lowbit(x))
tree[x]+=val;
}
int main()
{
//freopen("bins.in","r",stdin);
//freopen("bins.out","w",stdout);
m=rd(),n=rd();
m++;
for(int i=1;i<=n;i++)
{
a[i]=rd()+1;
if(a[i]==m&&lim==-1)
lim=i;
}
lim=min(lim,n/2);
//if(lim==-1) lim=n/2;
//資料里居然有整個數列都沒有m的 出題人大騙子嚶嚶嚶
//其實這個m就是來告訴你值域很小的吧 可這也不需要專門輸入一個m啊 直接在資料範圍裡說不好麼233
for(int k=1;k<=lim;k++)
{
Update(a[2*k]-1,-1);
Update(a[2*k-1]-1,-1);
Update(a[k]-1,1);//a[k]不在後半部分了 得撤回
Update(a[k],1);
bool flag=0;
for(int i=1;i<=m;i++)
{
int tmp=Query(i);
if(tmp<0)
{
flag=1;
break;
}
}
if(!flag) ans=max(ans,k);
}
printf("%d\n",ans);
return 0;
}
►Code View 75pts TLE
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 20005
#define M 1005
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
return f*x;
}
int m,n,lim=-1;
int a[N],b[N];
int tree[M];
inline int lowbit(int x)
{
return x&(-x);
}
inline int Query(int x)
{
int res=0;
for(;x;x-=lowbit(x))
res+=tree[x];
return res;
}
inline void Update(int x,int val)
{
for(;x<M-5;x+=lowbit(x))
tree[x]+=val;
}
bool check(int x)
{
if(x==0) return 1;
//memset(tree,0,sizeof(tree));
//for(int i=1;i<=x;i++)
// Update(a[i],1);
if(x!=lim) Update(a[x+1],-1);
memcpy(b,a,sizeof(a));
sort(b+x+1,b+2*x+1);
for(int i=x+1;i<=2*x;i++)
{
int tmp=Query(b[i]-1)-(i-x-1);
if(tmp<=0) return 0;
}
return 1;
}
int main()
{
//freopen("bins.in","r",stdin);
//freopen("bins.out","w",stdout);
m=rd(),n=rd();
for(int i=1;i<=n;i++)
{
a[i]=rd();
if(a[i]==m&&lim==-1)
lim=i;
}
lim=min(lim,n/2);
if(lim==-1) lim=n/2;
for(int i=1;i<=lim;i++)
Update(a[i],1);
for(int k=lim;k>=0;k--)
{
if(check(k))
{
printf("%d\n",k);
break;
}
}
return 0;
}