2020牛客多校第八场 解题报告GIK

题目链接:https://ac.nowcoder.com/acm/contest/5673

G-Game SET

题意

  • 给了n张SET牌,包含了万能牌,输出任意一种可以够成SET的方案。 
  • 形成SET条件:抽3张牌,每张牌有四个属性,如果这3张牌的对应的属性要么都相同,要么都不同。“*”是万能牌可以充当对应属性任何值,当然每个属性只有三种值。
  • 如果有随便输出一组,否则输出 -1

思路 

  • 直接暴力枚举,三个for循环
  • 如果属性一栏有万能牌的,那么其他两个无论取什么都能使得该属性一栏要么都相同要么都不同。
  • 所以只需排除不满足的情况,剩下的就是满足条件的了。
  • 不满足条件的就是,该属性栏没有万能牌,并且有出现两个相同,剩下一个不同的情况,所以一一排除。

AC代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define sc(a) scanf("%d", &a)
#define sc2(a, b) scanf("%d%d", &a, &b)
#define sc3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define scl(a) scanf("%lld", &a)
#define scl2(a, b) scanf("%lld%lld", &a, &b)
#define ss(a) scanf("%s", a)
#define mem(a, b) memset(a, b, sizeof(a))
#define PII pair<int, int>
using namespace std;
const double pi = acos(-1.0);
const int mod = 1e9 + 7;
const int N = 1e5 + 5;
string s[300][5];
bool ok(int i, int j, int k, int pos)
{
    return (s[i][pos] == "*" || s[j][pos] == "*" || s[k][pos] == "*"); //该属性一栏有出现万能牌的
}
bool check(int i, int j, int k)
{
    //属性一栏没出现万能牌,并且有出现两个相同,剩下一个不同的情况,一一排除
    if (!ok(i, j, k, 1) && ((s[i][1] == s[j][1] && s[i][1] != s[k][1]) || (s[i][1] == s[k][1] && s[i][1] != s[j][1]) || (s[j][1] == s[k][1] && s[i][1] != s[j][1])))
        return false;
    if (!ok(i, j, k, 2) && ((s[i][2] == s[j][2] && s[i][2] != s[k][2]) || (s[i][2] == s[k][2] && s[i][2] != s[j][2]) || (s[j][2] == s[k][2] && s[i][2] != s[j][2])))
        return false;
    if (!ok(i, j, k, 3) && ((s[i][3] == s[j][3] && s[i][3] != s[k][3]) || (s[i][3] == s[k][3] && s[i][3] != s[j][3]) || (s[j][3] == s[k][3] && s[i][3] != s[j][3])))
        return false;
    if (!ok(i, j, k, 4) && ((s[i][4] == s[j][4] && s[i][4] != s[k][4]) || (s[i][4] == s[k][4] && s[i][4] != s[j][4]) || (s[j][4] == s[k][4] && s[i][4] != s[j][4])))
        return false;
    return true;
}
int main()
{
    int t, cas = 1;
    sc(t);
    while (t--)
    {
        int n;
        sc(n);
        string a;
        memset(s, 0, sizeof(s));
        for (int i = 1; i <= n; i++)
        {
            cin >> a;
            int pos = 1;
            for (int j = 1; j <= 4; j++) //存数组
            {
                while (a[pos] != ']')
                {
                    s[i][j] += a[pos++];
                }
                pos += 2;
            }
        }
        cout << "Case #" << cas++ << ": ";
        int flag = 0;
        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {
                for (int k = j + 1; k <= n; k++)
                {
                    if (check(i, j, k))
                    {
                        flag = 1;
                        cout << i << " " << j << " " << k << endl;
                        break; //如果找到了,退出循环
                    }
                }
                if (flag)
                    break;
            }
            if (flag)
                break;
        }
        if (!flag) //没找到输出-1
            cout << "-1" << endl;
    }
    system("pause");
    return 0;
}

I-Interesting Computer Game 

题意 

  • 有两个数组{a1,a2,a3,,,an},{b1,b2,b3,,,bn}
  • 第i步可以从ai和bi中取出一个值
  • 求最终取出不同的数的最多个数

思路 

  • 并查集+离散化
  • 可以将选中的不同值值当作一个点,(ai,bi)当做一条边
  • 如果形成的连通块的边数不低于点数,那么可以取得个数就是连通块所有点个数;
  • 如果形成的连通块的边数少于点数,就是普通的树,那么由于边数的限制,最后一条边的一边取完之后,另外一边的值就不能取,所以取的个数是连通块所有点的个数减1
  • 我们可以反过来算,直接计算所有点个数减去边数少于点数的连通块的个数,即k个点,k-1条边的连通块
  • 这里可以用并查集来维护连通块,当a1和a2执行并操作时,如果之前已经联通了,现在加上了一条边,那么肯定满足边数多于点数了,标记一下祖先。
  • 如果两个连通块进行并操作时,其中一个祖先已经做过标记,那么形成的连通块边数也肯定不低于点数了,现在左边的祖先要叫右边的为爸爸,那么也给另一个祖先(连通块的祖先)也做个标记
  • 数据有点大,先进行个离散化操作

 

 AC代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define sc(a) scanf("%d", &a)
#define sc2(a, b) scanf("%d%d", &a, &b)
#define sc3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define scl(a) scanf("%lld", &a)
#define scl2(a, b) scanf("%lld%lld", &a, &b)
#define ss(a) scanf("%s", a)
#define mem(a, b) memset(a, b, sizeof(a))
#define PII pair<int, int>
using namespace std;
const int maxn = 2e5+5;
int pre[maxn],vis[maxn],a[maxn],b[maxn],c[maxn];
int find(int a){
    if(a==pre[a])return a;
    return pre[a]=find(pre[a]);
}
void merge(int a,int b){
    int x=find(a);
    int y=find(b);
    if(x==y){//如果之前已经联通了,现在再加一条边,那么连通块的边数肯定不低于点数了,那么标记一下祖先
        vis[x]=1;
        return;
    }
    pre[x]=y;
    if(vis[x])vis[y]=1;
    //如果两个连通块进行并操作时,其中一个祖先已经做过标记,那么形成的连通块边数也肯定不低于点数了,现在左边的祖先要叫右边的为爸爸,那么也给另一个祖先也做个标记
}
int main(){
    int t,cas=1;sc(t);
    while(t--){
        int n,tot=0;sc(n);
        for(int i=1;i<=n;i++){
            sc2(a[i],b[i]);
            c[++tot]=a[i];
            c[++tot]=b[i];
        }
        for(int i=0;i<=maxn;i++){//初始化
            vis[i]=0;
            pre[i]=i;
        }
        sort(c+1,c+tot+1);
        int cnt=unique(c+1,c+tot+1)-(c+1);//去重,方便编号
        for(int i=1;i<=n;i++){//离散化,当所有ab都不同的时候,就需要两倍的空间,所以c数组要开2e5+5
            a[i]=lower_bound(c+1,c+cnt+1,a[i])-c;
            b[i]=lower_bound(c+1,c+cnt+1,b[i])-c;
            merge(a[i],b[i]);
        }
        int ans=cnt;
        for(int i=1;i<=cnt;i++){
            if(pre[i]==i&&!vis[i])ans--;//减去边数小于点数的连通块的个数
        }
        cout<<"Case #"<<cas++<<": ";
        cout<<ans<<endl;
    }
    system("pause");
    return 0;
}

K-Kabaleo Lite

题意

  • 有n种菜,每种菜盈利是ai,数量是bi
  • 每个顾客必须从第一种菜开始吃,连续地吃,每种吃一份
  • 保证顾客最多的情况下,盈利最多,输出顾客数量和盈利数量

思路 

  • 前缀和。
  • 首先对菜的盈利求个前缀和,并维护前缀和1到i的前缀和最大值,用栈记录区间最大值下标,以保证盈利最多
  • 然后维护一下菜数量 1到i 的最小值,b[i]同步更新区间最小值,以保证顾客能够连续吃菜,如果不更新区间最小值,那么前面可能会有空缺的菜,不能连续了。
  • 遍历栈,就是按盈利从大到小进行选择,定义num作为之前吃过的套餐数量(连续吃1到i的菜),当当前盈利最大值的套餐数量b[i]小于num的时候,说明此套餐之前已经吃完了,否则可以吃上b[i]-num份套餐,盈利增加a[i]*(b[i]-num)(a[i]是盈利前缀和),num等于b[i]
  • 需要确保顾客数尽可能多,盈利是次要的,所以1号菜一定要吃完,不管亏不亏损
  • 题目盈利前缀和最大值超1e19,会爆long long,建议使用__int128 ,并自己重写输入输出

AC代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define sc(a) scanf("%d", &a)
#define sc2(a, b) scanf("%d%d", &a, &b)
#define sc3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define scl(a) scanf("%lld", &a)
#define scl2(a, b) scanf("%lld%lld", &a, &b)
#define ss(a) scanf("%s", a)
#define mem(a, b) memset(a, b, sizeof(a))
#define PII pair<int, int>
using namespace std;
const double pi = acos(-1.0);
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
inline void print(__int128 x)
{ //自定义_int128的输出
    if (x < 0)
    {
        x = -x;
        putchar('-');
    }
    if (x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}
template <typename _Tp>
inline void read(_Tp &x)
{ //自定义_int128的输出
    char ch;
    bool flag = 0;
    x = 0;
    while (ch = getchar(), !isdigit(ch))
        if (ch == '-')
            flag = 1;
    while (isdigit(ch))
        x = x * 10 + ch - '0', ch = getchar();
    if (flag)
        x = -x;
}
__int128 a[maxn], b[maxn];
int main()
{
    int t, cas = 1;
    sc(t);
    while (t--)
    {
        int n;
        sc(n);
        stack<int> s;
        s.push(1); //1号菜必吃,先给他入栈
        for (int i = 1; i <= n; i++)
        {
            read(a[i]);
            a[i] = a[i] + a[i - 1]; //维护盈利前缀和
            if (i == 1)
                continue;
            if (a[i] > a[s.top()])
                s.push(i); //区间最大值的下标入栈
        }
        for (int i = 1; i <= n; i++)
        {
            read(b[i]);
            if (i == 1)
                continue;
            b[i] = min(b[i], b[i - 1]); //维护菜数量区间最小值
        }
        int nn = b[1], num = 0;
        __int128 ans = 0;
        while (s.size())
        {
            int t = s.top();
            s.pop();
            if (b[t] >= num)
            { //套餐数量够,选!
                ans = ans + a[t] * (b[t] - num);
                num = b[t];
            }
        }
        cout << "Case #" << cas++ << ": " << nn << " ";
        print(ans);
        cout << endl;
    }
    system("pause");
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值