我被自己菜惊了。
一道小题思路错两次
怕是没救了。
硬是写了2个晚上……
对于每个位置,选能选的最大数。但是要记得预留子树里的数字。
感性地说:如果没有重复数,自然是“挤满”的,但是有重复数后就可能有空余。
离散化,对于每个数字用线段树记录比它大的数中,数的个数减去已确定的不能选的数的个数。
如果两点的数冲突,即两数预留的数中有冲突,那么从这两个数到最大数显然都选了并有数被选两次,即预留的数个数大于区间内总的数个数。
则比两数小的数中必然有数的线段树中的值小于0。
故只要均大于0就合法。以此二分找当前能选的最大数即可。
另外注意:每次新到一个父节点要把其预留的数“释放”给子节点。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn = 2500005; const int inf = 0x3f3f3f3f; int n, d[maxn], tm, y[maxn], bigger[maxn]; int ans[maxn], c[maxn], kk[maxn], lsum[maxn]; double k; struct atree { #define ls (x*2) #define rs (x*2+1) int minn[maxn], tag[maxn]; void down(int x) { minn[ks]+=tag[x]; tag[ls]+=tag[x]; minn[rs]+=tag[x]; tag[rs]+=tag[x]; tag[x]=0; } void add(int x, int l, int r, int a, int b, int k) { if(l > b || a > r) return; if(a <= l && r <= b) { minn[x]+=k; tag[x]+=k; return; } int mid=(l+r)/2; down(x); add(ls, l, mid, a, b, k); add(rs, mid+1, r, a, b, k); minn[x]=min(minn[x*2], minn[x*2+1]); return; } int count(int x, int l, int r, int a, int b) { if(l > b || a > r) return inf; if(a <= l && r <= b) { return minn[x]; } int mid=(l+r)/2, t; down(x); t=min(count(ls, l, mid, a, b), count(rs, mid+1, r, a, b)); minn[x]=min(minn[x*2], minn[x*2+1]); return t; } } T; inline int find_p(int x) { int l=1, r=tm, mid; while(l < r) { mid=(l+r+1)/2; if(T.count(1, 1, n+1, 1, mid) < x) r=mid-1; else l=mid; } return l; } int main() { int i, t, tt, sum=0, tc=0; cin>>n>>k; for(i=1; i <= n; i++) cin>>d[i]; sort(d+1, d+1+n); for(i=1; i <= n; i++) { if(i == 1 || d[i] != d[i-1]) tm++, y[tm]=d[i]; T.add(1, 1, n+1, 1, tm, 1); bigger[i]=1; } for(i=n; i >= 1; i--) { t=(double)i*1.0/k; bigger[t]+=bigger[i]; } for(i=1; i <= n; i++) { t=(double)i*1.0/k; tt=(double)(i-1.0)*1.0/k; if(tt != t && t != 0) T.add(1, 1, n+1, 1, ans[t]-1+1, bigger[t]-1); t=find_p(bigger[i]); ans[i]=t; T.add(1, 1, n+1, 1, t-1+1, -bigger[i]); cout<<y[t]<<' '; } return 0; }