ZJOI2020训练题2
T1 做任务
题目描述
现有一款游戏,你作为玩家,拥有k种物品。开始时,每种物品有1000件。
现在,在你面前有n个任务,每种任务都可能消耗一些物品,也可能得到一些物品。做第i个任务的物品得失情况用一个包含k个字母的字符串Si表示,其中每个字母都是+,-,/中的一种,第j个字母表示该任务对物品j的数量的影响。+表示做这个任务能得到一个物品j,-表示做这个任务会消耗一个物品j,/表示做这个任务对物品j的数量不产生影响。
但是,做任务是有前提条件的。游戏设计者约定了m个限制关系,每个限制关系是一个有序数对(i, j),表示做任务i前必须先做任务j。
现在,你需要选择性的做一些任务,使得获得的物品最多。
比较两种方案的获得的物品数的方法如下:先比较他们获得第物品1的个数,若相同,再比较物品2,若相同,再比较物品3,以此类推。
Input
第一行有三个整数n,m,k
接下来的n行,每行有一个字符串,第i行的字符串表示Si
接下来的m行,每行有两个整数i,j表示限制关系
Output
输出包含一行,k个空格分隔的整数,表示做完你选的任务之后每种物品的拥有量。
Sample Input
3 3 2
+-
+/
-+
2 1
2 3
3 1
Sample Output
1001 1000
Constraints
有20%的数据,n ≤ 10, k = 1
另有20%的数据,m = 0。
另有20%的数据,限制关系是一条链(m = n-1且所有i各不相同,所有j各不相同)。
另有20%的数据,限制关系是一棵树(m = n-1且所有i各不相同)。
100%的数据,n≤1000, k≤5, m≤5000,保证限制关系不会有环。
时间限制:1S
内存限制:512M
考试时的思路
分类讨论,前80pts
- n <= 10 二进制枚举暴力
- m = 0 无限制条件,直接贪心选第一位为+的,然后在第一位不变的情况下选第二位为+的,依次找下去,当然也可以排序
- m = n - 1 当成是树来做,将没有被限制的点作为根,然后记dp[i]表示以i为根子树中选一些方案的最优解,每次先递归处理子树,然后考虑要不要加入这棵子树,最后再和全0做一次比较
以上为80pts的思路
[Code]
#include<bits/stdc++.h>
using namespace std;
#define N 2050
#define M 10050
int read(){
int x=0; char c=getchar(); int flag=1;
while(!isdigit(c)) { if(c=='-') flag=-1; c=getchar(); }
while(isdigit(c)) { x=((x+(x<<2))<<1)+(c^48); c=getchar(); }
return x*flag;
}
int n,m,k;
int a[N][6];
struct Edge{
int to,next;
}edge[M];
struct EDGE{
int u,v;
bool tag;
}e[M];
int head[N];
int tmp;
int rd[N];
void add(int x,int y){
++ tmp; ++ rd[y]; edge[tmp].to = y; edge[tmp].next = head[x]; head[x] = tmp;
e[tmp].u = x; e[tmp].v = y; e[tmp].tag = 0;
}
void Add(int x,int y){
++ tmp; ++ rd[y]; edge[tmp].to = y; edge[tmp].next = head[x]; head[x] = tmp;
}
namespace Task1{
int ans[6],tmp[6];
void work(){
for(int i = 1; i <= k; ++ i) ans[i] = 1000;
for(int i = 0; i < (1 << n); ++ i){
bool flag = 1;
for(int j = 1; j <= n; ++ j){
if(!(i & (1 << (j - 1)))){
for(int p = head[j]; p != -1; p = edge[p].next){
int t=edge[p].to;
if(i & (1 << (t - 1))) { flag = 0; break ;}
}
}
if(!flag) break;
}
if(!flag) continue;
//printf("OK = %d\n",i);
for(int j = 1; j <= k; ++ j) tmp[j] = 1000;
for(int j = 1; j <= n; ++ j) if(i & (1 << (j - 1)) ) { for(int p = 1; p <= k; ++ p) tmp[p] += a[j][p]; }
flag = 0;
for(int p = 1; p <= k; ++ p)
if(tmp[p] > ans[p]) { flag = 1; break; }
else if(tmp[p] < ans[p]) break;
if(flag){
//printf("MORE %d\n",i);
for(int p = 1; p <= k; ++ p) ans[p] = tmp[p];
}
}
for(int i = 1; i <= k; ++ i) printf("%d ",ans[i]);
puts("");
}
}
namespace Task2{
int ans[6];
void work(){
for(int i = 1; i <= k; ++ i) ans[i] = 1000;
for(int i = 1; i <= n; ++ i){
bool flag = 1;
for(int j = 1; j <= k; ++ j){
if(a[i][j] == 0) continue;
if(a[i][j] < 0) flag = 0;
break;
}
if(flag){
for(int j = 1; j <= k; ++ j) ans[j] += a[i][j];
}
}
for(int i = 1; i <= k; ++ i) printf("%d ",ans[i]);
puts("");
}
}
namespace Task3{
int g[N][6];
void dfs(int nod){
for(int i = 1; i <= k; ++ i) g[nod][i] = a[nod][i];
for(int i = head[nod]; i != -1; i = edge[i].next){
int t = edge[i].to;
dfs(t);
bool flag = 1;
for(int j = 1; j <= k; ++ j) {
if(g[t][j] == 0) continue;
if(g[t][j] < 0) flag = 0;
break;
}
if(flag) { for(int j = 1; j <= k; ++ j) g[nod][j] += g[t][j]; }
}
bool flag = 1;
for(int i = 1; i <= k; ++ i){
if(g[nod][i] == 0) continue;
if(g[nod][i] < 0) flag = 0;
break;
}
if(!flag) { for(int i = 1; i <= k; ++ i) g[nod][i] = 0; }
}
void work(){
int root;
for(int i = 1; i <= n; ++ i) if(!rd[i]) { root = i; break; }
dfs(root);
for(int i = 1; i <= k; ++ i) printf("%d ",g[root][i] + 1000);
puts("");
}
}
signed main(){
memset(head, -1, sizeof(head));
n = read(), m = read(), k = read();
for(int i = 1; i <= n; ++ i){
char s[10];
scanf("%s",s);
for(int j = 1; j <= k; ++ j)
if(s[j - 1] == '+') a[i][j] = 1;
else if(s[j - 1] == '-') a[i][j] = -1;
}
for(int i = 1; i <= m; ++ i){
int x = read(), y = read();
add(y,x);
}
if(n <= 10) { Task1::work(); return 0; }
if(m == 0) { Task2::work(); return 0; }
if(m == n - 1) { Task3::work(); return 0; }
return 0;
}
//Still WA
正解
考虑一个任务的奖励很像k位数,所以将其转化成10进制数,然后权值有正有负,考虑对于限制关系建图,方式就是如果做任务i之前需要做任务j,那就从i向j连一条边,然后对于这个图求最大权闭合子图.选出的子图就是答案.
首先最大权闭合子图是用所有正权值之和减去原图的最小割,然后我们相对于限制反向建边,就是要求如果要选一个点,则它之前的所有点必须要选.这样就满足了限制.