题目:http://codeforces.com/contest/1574/problem/D
题意:有n组数,每一组有C[i]个数,每一组组内已经从小到大排好序,然后现在让你在每一组里面选出一个数,让他们的和最大,输出你选每一组里面的下标,现在你肯定想,选每一组里面最大的就可以了,确实这是最大的答案。但是现在又有m个限制条件,每一个限制条件,给出n个数,这n个数对应每一组,组里面的下标,然后这个组合是不能被选为答案的。
题目保证输入合法,并且保证一定存在一个组合可以作为答案(每一组都要有被选上的数),如果有多个方案,输出任意一个即可。
3
3 1 2 3
2 1 5
3 2 4 6
2
3 2 3
3 2 2
样例辅助理解,总共有3组,第一组有3个数,第二组有2个数,第三组有3个数;然后有2个组合是被限制的,限制1:第一组的第3个数,第二组的第二个数,第三组的第三个数,限制2:第一组的第三个数,第二组的第二个数,第三组的第二个数。这俩个组合是不能被选为答案的。首先如果没有限制的话,最大值肯定是第一组的第三个数,第二组的第二个数,第三组的第三个数,答案为3 2 3(下标),但是这一组下标不能作为答案,所以不行。
题解:解法及其暴力,思想也很简单,但是代码可能不太好实现。
首先,注意到n很小,如果n<=2的话,那就直接将限制的方案全部放入set里面存好,然后从小到大枚举所有的方案,看看它是不是不在限制里面即可,枚举可以用一个优先队列实现,把s(选出来的数的和)大的放前面,然后取出最大的,因为n<=2,所以优先队列里面最多插入2个数,类似最短路的思想,跑出答案即可,这是n<=2的时候,pair就可以解决。
但是n虽然很小,但还是很容易大于2的,这个时候可以大胆一点,开一个结构体,里面放一个数组,存n组的信息,相当于把2扩大到10,然后把这个结构体存入set和优先队列里面,让他们去比较这个数组是否存在过,事实证明,可以实现,细节在代码中。
时间复杂度:因为是把一个长度为10的数组放入到了set和优先队列中,时间复杂度不太好说了,姑且认为是只有一个数的时候的10倍吧。
首先se存限制方案,顶多是1e5个,所以时间复杂度是:1e5 * 10 * log(1e5)
优先队列中,因为限制顶多是1e5个,所以优先队列也顶多是1e5级别的,所以时间复杂度也大概是:1e5 * 10 * log(1e5)
is的作用是判断跑优先队列的时候,是否出现过这个组合,所以is是跟优先队列是的大小是一样的。
因为优先队列那里实在不好说明有多少个,但是绝对不会超过1e6个,就一定能出答案,所以姑且认为1e6是最坏答案吧,这样那么它的时间复杂度的顶层是 1e6 * 10 * log(1e5),题目给的3秒,所以不超时。
#include <bits/stdc++.h>
// #define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
typedef unsigned long long LL;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
struct node {
int a[11]; //存n组,分别选了哪一组的下标,a[i],i是第几组
int s;//a[i]是选了这一组中的哪个下标。 s是选中的这n组数的和
};
int n;
//set所需要的从小到大排序
bool operator<(const node A, const node B) {
if (A.s < B.s)
return 1; //因为set是要判断存不存在的,所以下面这个
//数组的判断必须有,不然它比较的时候只看s是否相等,不看数组是否相等
//不信可以测一测
else if (A.s == B.s) {
for (int i = 1; i <= n; i++) {
if (A.a[i] < B.a[i])
return 1;
else if (A.a[i] > B.a[i])
return 0;
}
}
return 0;
}
bool operator>(const node A, const node B) {
if (A.s > B.s)
return 1; //因为优先队列没有判是否存在,所以下面也不需要,
//只需要根据s来判断选中的数组的大小即可
// else if (A.s == B.s) {
// for (int i = 1; i <= n; i++) {
// if (A.a[i] > B.a[i])
// return 1;
// else if (A.a[i] < B.a[i])
// return 0;
// }
// }
return 0;
}
//记录每一组里面有多少个数
int len[12];
// m个限制存入se中, is是跑优先队列的时候,判断这个组合是否出现过
set<node> se, is;
//优先队列是从最大的组合开始跑,类似最短路一样,每次选取最大的
priority_queue<node> que;
//存n组的信息
vector<int> vec[12];
signed main() {
scanf("%d", &n);
//每一组先插入一个0,方便后面下标的处理
for (int i = 0; i <= n; i++) vec[i].emplace_back(0);
for (int i = 1; i <= n; i++) {
int k;
scanf("%d", &k);
len[i] = k;
for (int j = 0; j < k; j++) {
int data;
scanf("%d", &data);
vec[i].emplace_back(data);
}
}
int m;
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
node temp;
temp.s = 0;
for (int j = 1; j <= n; j++) {
int data;
scanf("%d", &data);
temp.a[j] = data;
temp.s += vec[j][data];
}
//将所有的限制组合插入的se中
se.insert(temp);
}
node temp;
temp.s = 0;
for (int i = 1; i <= n; i++) temp.a[i] = len[i], temp.s += vec[i][len[i]];
//得到最大的组合,插入到优先队列中,并且插入到is中,证明这个数列已经出现过
is.insert(temp);
que.push(temp);
while (que.size()) {
temp = que.top();
que.pop(); //弹出
//如果找到一个没被限制的方案,直接跳出,输出答案
if (!se.count(temp)) {
break;
}
//将这个组合中的某一个数替换一个比它小的
for (int i = 1; i <= n; i++) {
if (temp.a[i] != 1) { //如果这一组的下标已经是1了,就不能再替换小的了
//修改下标和他们的和
temp.s = temp.s - vec[i][temp.a[i]] + vec[i][temp.a[i] - 1];
temp.a[i] -= 1; //下标减1
if (!is.count(temp)) { //判断是否出现过,没出先过的话,就插入
is.insert(temp), que.push(temp);
}
temp.a[i] += 1; //再还原,给下一个循环使用
temp.s = temp.s + vec[i][temp.a[i]] - vec[i][temp.a[i] - 1];
}
}
}
//输出答案
for (int i = 1; i <= n; i++) printf("%d ", temp.a[i]);
return 0;
}