LINK:Multiple Testcases
得到很多种做法。其中O(n)的做法值得一提。
容易想到二分答案 check的时候发现不太清楚分配的策略。
需要先考虑如何分配 容易发现大的东西会对小的产生影响 而 对于某个能放的位置 我们放大的一定比小的要优。所以为了防止出现对小的影响到了需要调整的局面和放大的的局面不会比放小的差的思想 可以得到策略。
有了贪心的思路 可以考虑先放大的 考虑对于每个桶中从大到小放。
放完一个数字之和容易二分到下一个位置 然后set取出相应的值即可。复杂度nlogn.
容易发现其实不需要二分 预处理一下跳跃数组即可。
这个做法有另外的形式 sort一下从大到小放 然后发现位置不够用的时候再开一个 用堆或者set维护能用的位置。复杂度还是nlogn.
接下来考虑二分答案的思路。
一个比较不容易想到是正确的分配思路 二分答案之后 如果答案合法那么按照 1...mid ->1,,,mid放一定可以合法。
换句话说 答案满足这样构造的情况。设(a_i)为每个数字的个数(g_i=sum{w=i}^ka_w)(c_i)表示限制
证明:如果mid>=ans 那么必然满足 (midgeq max{lceil frac{g_i}{c_i} ceil})
这个结论是显然的。而这样构造可以发现对于某个i和其之后的数字来说算是平均分配了给了mid.
而这样平均分又有上述的关系作为保证 所以构造合法。
至此 进一步的 可以发现答案直接为上述式子的最大值即可。复杂度O(n)
code nlogn的set:
const ll MAXN=200010;
ll n,m,ans;
int c[MAXN],vis[MAXN];
vector<int>g[MAXN];
multiset<int>s;
multiset<int>:: iterator it;
signed main()
{
//freopen("1.in","r",stdin);
get(n);get(m);
rep(1,n,i)
{
int get(x);
s.insert(x);
}
c[0]=INF;
rep(1,m,i)get(c[i]),c[i]=min(c[i-1],c[i]);
int cnt=0;
while(cnt<n)
{
++ans;
it=s.end();--it;
int R=*it;
int ww=0;
while(1)
{
++ww;++cnt;
g[ans].pb(R);
if(cnt==n)break;
s.erase(it);
int l=0,r=R;
while(l+1<r)
{
int mid=(l+r)>>1;
if(c[mid]>ww)l=mid;
else r=mid;
}
int cc=c[r]>ww?r:l;
R=cc;
if(!R)break;
it=s.upper_bound(R);
if(it==s.begin())break;
--it;R=*it;
}
}
put(ans);
rep(1,ans,i)
{
printf("%d ",g[i].size());
for(ui j=0;j<g[i].size();++j)printf("%d ",g[i][j]);
puts("");
}
return 0;
}
O(n)的构造
const int MAXN=200010;
int n,m,ans;
int c[MAXN],w[MAXN],a[MAXN];
vector<int>g[MAXN];
signed main()
{
freopen("1.in","r",stdin);
get(n);get(m);
rep(1,n,i)++a[read()];
rep(1,m,i)get(c[i]);
fep(m,1,i)w[i]=a[i]+w[i+1],ans=max(ans,(w[i]-1)/c[i]+1);
int cnt=0;
rep(1,m,i)while(a[i])g[cnt].pb(i),cnt=(cnt+1)==ans?0:cnt+1,--a[i];
put(ans);
rep(0,ans-1,i)
{
printf("%d ",g[i].size());
for(ui j=0;j<g[i].size();++j)printf("%d ",g[i][j]);
puts("");
}
return 0;
}