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
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连一条边,然后对于这个图求最大权闭合子图.选出的子图就是答案.

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

你可能感兴趣的:(ZJOI2020训练题2)