P15406 [NOISG 2026 Prelim] 摩天大楼

思路

首先我们发现若 $k\le 2$,则一定有解。
我们现将所有高度的大楼数量按照高度为关键字排序。
考虑从上到下,从左到右填数字。
这时所有位置的上面和左边一定不存在比它高的大楼。
然后若 $k=4$,显然当且仅当所有大楼都一样高时成立。
这时便只剩下 $k=3$ 的情况需要讨论。
我们考虑从小到大填,从外往内覆盖整个矩形。
发现当且仅当用同样高度填充整行或整列才能合法地填入最小高度的大楼。
用完了一种高度的大楼就用大于它的最小高度的大楼。
若当前剩余的最小大楼数量不足填充任何一条整边,这种填充方法不合法。
考虑进行 DFS,传参还需要填充的行数和列数,现在用到了哪个高度以及用了多少。
但这样还无法通过,考虑进行记忆化,用一个 map 存下哪些行列数量已经遍历过了(因为从小到大填,所以填的顺序没有影响)。
所有的行数和列数都只会被考虑到一次,总时间复杂度为 $O(n^2\cdot \log n)$,$n$ 最大仅有 $1000$,足以通过。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
#define fir first
#define sec second
#define int long long
#define pii pair<int,int>
#define fep(i,s,e) for(int i=s;i<e;i++)
#define pef(i,s,e) for(int i=s;i>e;i--)
#define rep(i,s,e) for(int i=s;i<=e;i++)
#define per(i,s,e) for(int i=s;i>=e;i--)
namespace FastIO{
template<typename T>inline void read(T &x){
x=0;int f=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);x*=f;
}
template<typename T,typename...Args>
inline void read(T &x,Args&...args){
read(x);
read(args...);
}
template<typename T>void print(T x){
if(x<0)x=-x,putchar('-');
if(x>9)print(x/10);
putchar((x%10)^48);
}
}
using namespace std;
using namespace FastIO;
const int M=1e4+7;
struct node{
int h,cnt;
}b[M];
int n,k,m;
map<pii,int>mp;
inline bool cmp(node a,node b){
return a.h<b.h;
}
void dfs(int n,int m,int cur,int usd){
if(n==0||m==0){
puts("YES");
exit(0);
}
if(mp[{n,m}]==1)return;
int lft=b[cur].cnt-usd;
if(lft!=0)mp[{n,m}]=1;
if(lft==0)dfs(n,m,cur+1,0);
if(lft>=n)dfs(n,m-1,cur,usd+n);
if(lft>=m&&n-1>=m)dfs(n-1,m,cur,usd+m);
if(lft<min(n,m))return;
}
signed main(){
srand(time(0));
read(n,k,m);
if(k<=2){
puts("YES");
return 0;
}
if(k==4){
if(m!=1)puts("NO");
else puts("YES");
return 0;
}
rep(i,1,m)read(b[i].h,b[i].cnt);
sort(b+1,b+m+1,cmp);
dfs(n,n,1,0);puts("NO");
}