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

  1. n <= 10 二进制枚举暴力
  2. m = 0 无限制条件,直接贪心选第一位为+的,然后在第一位不变的情况下选第二位为+的,依次找下去,当然也可以排序
  3. 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
",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
",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连一条边,然后对于这个图求最大权闭合子图.选出的子图就是答案.

首先最大权闭合子图是用所有正权值之和减去原图的最小割,然后我们相对于限制反向建边,就是要求如果要选一个点,则它之前的所有点必须要选.这样就满足了限制.

原文地址:https://www.cnblogs.com/zzhzzh123/p/12253732.html