题目
codeforces Round 377 Div2 E
题解
A~D都很水的样子,一到E就是题意模糊+做不来+看不懂题解。
题意:n台电脑(记为A),每台一个权值,m个插头(记为B),每个一个权值,权值相同的可以配对,有无数个转换器(记为C)可以使得插头的权值/2向上取整,一个插头可以安多个转换器,问最大匹配数和匹配最大时最少使用的转换器个数。
题解:可以发现,不冲突时,每个B应选择使用最少C能匹配到的A,那如果冲突,既然两个B都能匹配到A,那么继续使用C对于这两者来说没有任何差别,因为它们到达A后继续使用C的变化情况是一样的,所以如果要继续使用C使得匹配数增加的话,没有必要确定是谁使用了。所以贪心策略正确。
实现:实现稍稍有别于题解,先对A,B升序排序枚举使用C个数,然后用两个指针指向当前A,B决策到哪一位(当前A记为Ai,将使用C个转换器后的B记为Bj),如果Bj这个插头本身用过或Ai>Bj则j后移,如果Ai用过或者Ai < Bj则i后移,如果相同则匹配。
刚开始确定x枚举上界时煞笔了导致调了一个小时,不应该取(1<< x)<=max{B}的上界,而应该模拟使用转换器的过程,,因为使用转换器要向上取整,cf题质量真心高,贪心都这么机智。
代码
//QWsin
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int logn=31;
const int maxn=200000+10;
inline int read()
{int ret=0;char ch=getchar();while(ch<'0'||ch>'9') ch=getchar();for(;ch>='0'&&ch<='9';ch=getchar()) ret=ret*10+ch-'0';return ret;
}int powercnt,cnt,link[maxn],id[maxn];
int dn[maxn],usec[maxn],used[maxn],power[maxn];
//power[i]第i个插头用的转换器数量
//dn[i] 第i个电脑权值
//usec标记插头是否使用,used标记电脑
//link[i]表示第i个电脑与谁配对 struct Sc{
//ct[i]._[j]表示使用j个转换器后权值int _[logn+2],id;bool operator < (const Sc &rhs)const{return _[0] < rhs._[0];}
}ct[maxn];int cmp(int a,int b){
return dn[a]<dn[b];}
int main()
{int n,m;cin>>n>>m;for(int i=1;i<=n;i++) dn[i]=read();for(int i=1;i<=n;i++) id[i]=i;for(int i=1;i<=m;i++) {ct[i]._[0]=read();ct[i].id=i;for(int j=1;j<=logn;j++) ct[i]._[j]=(ct[i]._[j-1]+1)>>1;}sort(id+1,id+n+1,cmp);sort(ct+1,ct+m+1);int sz=0,tmp=ct[m]._[0];while(tmp!=1) sz++,tmp=(tmp+1)>>1;for(int x=0;x<=sz;x++){int pd=1,pc=1;while(pd<=n&&pc<=m){if(usec[pc]||ct[pc]._[x] < dn[id[pd]]) pc++;else if(used[pd]||dn[id[pd]] < ct[pc]._[x]) pd++;else {power[ct[pc].id]=x,powercnt+=x;link[id[pd]]=ct[pc].id;cnt++,usec[pc]=used[pd]=1;pc++;pd++;}}}printf("%d %d\n",cnt,powercnt);for(int i=1;i<=m;i++) printf("%d ",power[i]);putchar('\n');for(int i=1;i<=n;i++) printf("%d ",link[i]);return 0;
}