题目描述
难度分:2600
输入n(1≤n≤103)和长为n的数组a(1≤a[i]≤n)。
你需要执行如下操作至多n+1次:
- 选择a的一个子序列,把子序列中的元素全部减一。
假设你操作了k次,这k个子序列,对应的下标集合必须互不相同。
你需要把所有a[i]都变成0。输出操作次数,和具体操作方案。
用长为n的01
字符串s表示具体操作方案,s[i]=1表示a[i]在子序列中。
输入样例1
5
5 5 5 5 5
输出样例1
6
11111
01111
10111
11011
11101
11110
输入样例2
5
5 1 1 1 1
输出样例2
5
11000
10000
10100
10010
10001
输入样例3
5
4 1 5 3 4
输出样例3
5
11111
10111
10101
00111
10100
算法
构造
难得一道会做的周五茶,虽然有几个细节错了好几次,但总算是AC掉了。
没办法一眼,需要自己找一下突破口,尝试一下构造的方法。注意到n≤1000且a[i]≤1000,所以时间复杂度估计就是O(n2)。尝试从最大值开始入手,由于要求每个子序列都不一样,所以每次都在所有子序列的不同位置上加一。
即所有序列存储在二维字符数组s中,由于题目中提到n+1个操作序列一定可以达成目的,所以我们先假设n个子序列可以达成目的(因为刚好想到了一种对角线填数的构造方法),用以下方法开始构造:
设mx=maxi∈[1,n]a[i],令列i的初始填1位置是p[i]=i(即还要构建一个长度为O(n)的数组p),遍历i∈[1,n],只要此时a[i]>0,就令s[p[i]][i]=1
,a[i]自减,并更新p[i]=(p[i]+1)%n,即在列上循环填。每遍历完一轮[1,n],数组的最大值mx就会减小1。周而复始直到mx=0。这样可以构造出来一个n轮操作的方案。
但是未必是合法的,所以用哈希表检查一下n个子序列有没有重的。如果没有重,直接输出方案就结束了。但如果有重,说明需要n+1次操作才能达成目的,我们找到所有重复的子序列方案,假设方案seq出现了cnt次,那么我们遍历seq,只要发现seq[i]=1
,s[n+1][i]=0
,就把seq[i]的1搬到s[n+1][i]。然后跳到下一个相同的seq继续这个流程,但是从刚才搬运的位置i之后继续检查是否有满足seq[j]=1
,s[n+1][j]=0
的位置j,有就搬运1到s[n+1]。注意我们对seq这个内容的串只需要修改cnt−1次,这样已经足够让这些原本相同的串都互不相同。
复杂度分析
时间复杂度
每一轮操作让a数组的最大值减小1,一共O(n)轮,每一轮可以对O(n)列填一个1。相当于最终要让a数组的和从Σi∈[1,n]a[i]变成0,而a[i]≤n,因此算法的时间复杂度就是O(n2)。
即使存在后续“搬运1构造第n+1个序列”的操作,也还是O(n2)级别的修正答案,不影响时间复杂度。
空间复杂度
空间消耗主要在于检验序列是否重复时的哈希表,它的映射关系是“方案字符串→ 该方案对应的行索引列表”,key的空间是O(n2),O(n)级别的序列都要存,每个序列的长度是O(n)级别;value的空间是O(n),即每一行的编号都要存。因此,整个算法的额外空间复杂度为O(n2)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1001;
int n, a[N], b[N];
char s[N][N];
int main() {
scanf("%d", &n);
int mx = 0;
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
b[i] = a[i];
mx = max(mx, a[i]);
for(int j = 0; j < n; j++) {
s[i][j] = s[i + 1][j] = '0';
}
}
if(mx == 1) {
puts("1");
for(int i = 0; i < n; i++) {
printf("%d", a[i]);
}
}else {
vector<int> p(n);
for(int j = 0; j < n; j++) {
p[j] = j;
}
while(mx > 0) {
for(int j = 0; j < n; j++) {
if(b[j] > 0) {
b[j]--;
s[p[j]][j] = '1';
p[j] = (p[j] + 1) % n;
}
}
mx--;
}
bool ok = true;
unordered_map<string, vector<int>> counter;
for(int i = 0; i < n; i++) {
string row;
for(int j = 0; j < n; j++) {
row.push_back(s[i][j]);
}
counter[row].push_back(i);
if(counter[row].size() > 1) {
ok = false;
}
}
if(ok) {
printf("%d\n", n);
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
printf("%c", s[i][j]);
}
puts("");
}
}else {
printf("%d\n", n + 1);
for(auto&[k, v]: counter) {
if(v.size() > 1) {
for(int j = 0, i = 0; j < n; j++) {
if(k[j] == '1' && s[n][j] == '0') {
if(i < v.size() - 1) {
s[v[i]][j] = '0';
s[n][j] = '1';
i++;
}
}
}
}
}
ok = true;
for(int i = 0; i <= n; i++) {
for(int j = 0; j < n; j++) {
printf("%c", s[i][j]);
}
puts("");
}
}
}
return 0;
}