[USACO13NOV]Farmer John has no Large Brown Cow【状压 / 模拟】

Pro

Luogu3087

Sol

哇哇哇,昨晚调了一个小时没调出来,今天早上调了一会就出来了坑点不算很多,只是我比较粗心……

拿到题,一眼肯定想到和排列组合有关的知识,其实用到的也不是太多,首先对于单词的存储,我们想方设法的把每一行和每一列的单词都分别存下来,以后都会调用到的。然后预处理出第 i i 列第j行后面有多少种可能性放到 qc q c 数组中,递推公式:

qci,j=qci+1,j×cnt[i+1] q c i , j = q c i + 1 , j × c n t [ i + 1 ]

在预处理之前呢,我们求出每一列不同的单词数量,存到 cnt c n t 数组中,可以排好序之后用类似栈的做法做,不多解释了,截止到现在,排列组合的知识已经全部用完~

然后,我们求如果没有删除(在所有的牛中去掉没有的牛)操作,我们要查询的 K K 所对应的位置。什么叫对应的位置呢?就是开始我们的状压了:对于每一列,每一个单词都标记一下,该单词是这一列的第几个单词。那么我们第一次求K的位置的时候,就能把求得的这些位置存到 Knum K n u m 数组中。

接下来就是删除操作,所谓删除,其实我们可以理解为:如果有比 K K 的位置小的数,我们就把K往后移。应该很容易理解吧。于是我们就枚举每一行,把每一行的几个单词对应的位置处理出来,存到 Pnum P n u m 数组中,用 sol s o l 函数进行比较,如果比 K K 的位置要小,K就后移。最后 Knum K n u m 中的数就是我们答案在他所在列的位置,输出就好了。

再说说把单词转化为位置的做法,刚才我们维护了第 i i 行第j列后面的可能性。我们每一列每一列的求,对于每一列,枚举他的每一行,如果从枚举到的这一行向上,所有可能性的总和比我们要查的位置要大,我们就取这一行,更新我们要查的位置,更新的时候一定要记得乘上行数减一!

几个易错的地方:

1.最后比较每一行与 K K 所对应的位置的时候,一位一位的比较,发现小的,立即停止,注意,不是break,是 return r e t u r n ,因为题目中可能会有比较的两个字符串每一个位置对应相等的情况,因此在最后打上标记就行。

2.把每一个单词转化为每一列的第几个单词的时候,注意两个地方,第一个就是相等的情况,如果相等,后面的直接取最后一行;第二种是最后一列的时候,直接就可以赋值。

Code

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

struct Letr {
    int group;
    string adj;
    bool operator < (const Letr &a) const {
        return adj < a.adj;
    }
};
Letr adj[30005];
vector<string>str[350];
int n , k , num , top = 0 , cnt[350] , qc[350][1050], knum[350] , pnum[350], flag;
string gro[350][1050];

int jud(string s) {
    if(s != "Farmer" && s != "John" && s != "has" && s != "no" && s != "cow.")
        return 1;
    return 0;
}

void update(int t) {
    for(int i=1; i<num; i++) {
        int sum = 0;
        for(int j=1; j<=cnt[i]; j++) {
            sum += qc[i][j];
            if(sum == t) {
                knum[i] = j;
                for(int l=i+1; l<=num; l++)
                    knum[l] = cnt[i];
                return ;
            }
            if(sum > t) {
                knum[i] = (j-1<1)?1:j;
                t -= (qc[i][(j-1<1)?1:(j-1)]*(j-1));
                break;      
            }
        }
    }
    knum[num] = t;
} 

void sol(int x[] , int y[]) {
    for(int i=1; i<=num; i++) {
        if(x[i] > y[i])
            return;
        if(x[i] < y[i]) {
            flag = 1;
            return;     
        }
    }
    flag = 1;
}

int main() {
    scanf("%d%d",&n,&k);
    for(int i=1; i<=n; i++) {
        string s;
        num = 0;
        while(1) {
            cin>>s;
            if(jud(s)) {
                num++;
                str[i].push_back(s);
                adj[++top].adj = s;
                adj[top].group = num;
            }
            if(s == "cow.")
                break;
        }
    }
    sort(adj , adj+top+1);
    for(int i=1; i<=top; i++) {
        if(adj[i].adj == adj[i-1].adj && adj[i].group == adj[i-1].group)
            continue;
        gro[adj[i].group][++cnt[adj[i].group]] = adj[i].adj;
    }
    for(int i=1; i<=n; i++)
        qc[num][i] = 1;
    for(int i=num-1; i>=1; i--)
        for(int j=1; j<=n; j++)
            qc[i][j] = qc[i+1][j]*cnt[i+1];
    update(k);
    for(int i=1; i<=n; i++) {
        flag = 0;
        for(int j=0; j<str[i].size(); j++) {
            for(int l=1; l<=cnt[j+1]; l++) {
                if(str[i][j] == gro[j+1][l]) {
                    pnum[j+1] = l;
                    break;
                }
            }       
        }
        sol(pnum , knum);
        if(flag) {
            k++;
            update(k);
        }
    }
    for(int i=1; i<=num; i++)
        cout<<gro[i][knum[i]]<<" ";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值