https://acm.hdu.edu.cn/showproblem.php?pid=7087
本质
- 这题本质就是每次都把最大的对象塞到分条里,塞不了就NO,全塞完就YES
如果思路偏了,处处都是荆棘
我之前搞过从分条入手,找离分条最近的最大的对象,但是分条容量是会变的啊!这就导致处理一次分条变动就得重新循环,时间复杂度极大,TEL不在话下
对象是不变的,所以我们从对象入手找最大的分条就行了
算法鉴赏
优先队列
开俩优先队列,数值越大优先级越高,队列1装对象,队列2装分条,开循环
每次循环取出1队首(最大的对象)a,然后取出2队首(最大的分条)b
- 如果a>b,那么最大的对象没有分条可装,退出循环输出No
- 如果a==b 最大的分条正好能装下最大的对象,那么这俩都出队,继续循环
- 如果a<b,那么a出队,b出队,b-a入队2
临界条件
- 若循环过后队列1为空,队列2无论是否为空,都说明对象都装完了,输出Yes
- 若循环过后队列1非空,但队列2为空,说明分条不够用,输出No
- 若循环过后队列1非空,队列2非空但是退出了,说明是因为a>b退出的,输出No
由此得出,循环结束条件是二者有一为空,然后判断
若1空,输出Yes
否则输出No
简单明了
易错点:如果你是在取出队首的时候同时出队了,请在a>b后让a入队1
AC代码
很短,忽略快读的话三十行
#include<cstdio>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
inline int read() { 快读,可忽略
int s=0,w=1;
char c=getchar();
while(c<'0' || c>'9') {
if(c=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9') {
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
return s*w;
}
int main(){
int T=read();
while(T--){
int n=read(),m=read();
priority_queue<int,vector<int>,less<int> > p1,p2;
for(int i=1;i<=n;i++){//对象
int t=read();
p1.push(t);
}
for(int i=1;i<=m;i++){//分条
int t=read();
p2.push(t);
}
while(!p1.empty() && !p2.empty()){
int a=p1.top(),b=p2.top();p1.pop(),p2.pop();
if(a>b) {
p1.push(a);break;
}
else if(a==b) continue;
else if(a<b) p2.push(b-a);
}
if(p1.empty()) printf("Yes\n");
else printf("No\n");
}
return 0;
}
化为二进制后按位相减
这是一位AC的同学提供的思想,初见这骚操作我惊为天人
遇到2的n次幂都可以往这边想一下
都是整数,用数组模拟二进制减法
减去不大于它的最大的对象,那就先找位数相同的,这样搜索起来比较快
样例2解析
对象2 2 4 8
分条3 13
对象转化为10 10 100 1000
分条转化为11 1101
1101-1000=101
101-100=1
11-10=1
对象还剩下10 分条还剩下1 1 输出No
样例1解析
对象2 2 4 8
分条4 12
对象10 10 100 1000
分条100 1100
1100-1000=100
100-100=0
100-010=010
010-010=0
剩余对象为空 输出Yes
再看一组Debug
2 2 8 8
8 12
对象10 10 1000 1000
分条1000 1100
1100-1000=100
1000-1000=0
100-10=10
100-10=10
输出Yes
代码略