Kabaleo Lite

题意:有n种菜,每种菜都有对应的数量,然后只能选从1开始的连续的菜给顾客,求可以支持顾客数的最大值,并且在最大顾客数量的前提下求出盈利的最大值,最大盈利值可能为负数。
思路:前缀和+高精度。这题我是没有看出会爆long long,然后疯狂交,疯狂wa。我们很容易发现,因为要从第一个开始选,那么数量最大肯定是第一种菜的数量,然后,重点就是求当前情况下的最大的盈利值。
首先,我们先求到前缀和以及每种菜的前面的最小的菜的数量。
代码如下:

a[0]=0;
b[0]=0;
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)  scanf("%lld",&a[i]);
for(int i=1;i<=n;i++){
    scanf("%lld",&b[i]);
    if(i>1) b[i]=min(b[i],b[i-1]);  
//将b数组转化为存储的是i之前的数量最小的那盘菜的数量
}
int ans1=b[1];
ll ans2=0;
for(int i=1;i<=n;i++)  a[i]=a[i]+a[i-1];  //将a数组转化为求前缀和

然后,我们在来看如何求最大的利润;

int l=1,r=1;
//现在b数组表示的是i之前的最小数量,a表示的是前缀和 
ll m=a[l]*b[l];
while(r<n+1){
    while(r<n+1&&a[r]<=a[l])  r++;
    if(r==n+1)  break;
    m+=b[r]*(a[r]-a[l]);
    ans2+=m/mod,m%=mod;
    l=r;
}

由于可能会爆long long,然后我们尽量避免将大的数进行直接相乘,我们分成一小步一小步来累加,我们先将l,r都初始化为起始点,然后,我们让r往右边找前缀和大于当前的l的那个位置,如果没有找到,说明我们已经求到了最优的利润了,直接退出,否则, m+=b[r]*(a[r]-a[l]);表示的是我们这段区间内对利润最大值还是有贡献的,(注意我们的a,b数组此时代表的值),其实思想就是将整个区间分成一小块一小块,然后对每块进行单独处理。接下来的ans2+=m/mod,m%=mod,(这里我们的mod=1e15,其中ans2表示的是商,m表示的mod之后的余数,其实这就是一个很好的高精度模拟方法,到时候只要输出ans2,再将m以15位进行输出即可。
代码如下:

#include<iostream> 
using namespace std;
typedef long long ll;
const ll mod=1e15;
ll a[100010];
ll b[100010];
int main(){
    int t;
    scanf("%d",&t);
    for(int j=1;j<=t;j++){
        a[0]=0;
        b[0]=0;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)  scanf("%lld",&a[i]);
        for(int i=1;i<=n;i++){
            scanf("%lld",&b[i]);
            if(i>1) b[i]=min(b[i],b[i-1]);
        }
        int ans1=b[1];
        ll ans2=0;
        for(int i=1;i<=n;i++)  a[i]=a[i]+a[i-1];
    //    for(int i=1;i<=n;i++)  cout<<a[i]<<" ";
        int l=1,r=1;
        //现在b数组表示的是i之前的最小数量,a表示的是前缀和 
        ll m=a[l]*b[l];
        while(r<n+1){
            while(r<n+1&&a[r]<=a[l])  r++;
            if(r==n+1)  break;
            m+=b[r]*(a[r]-a[l]);
            ans2+=m/mod,m%=mod;
            l=r;
        }
        printf("Case #%d: %d ",j,ans1);
        if(ans2)  printf("%lld%015lld\n",ans2,m);
        else  printf("%lld\n",m);
    }
    return 0;
}

Interesting Computer Game

题意:有n个回合,每个回合给两个数字,然后选其中一个数字,问最后所得到的不同的数字个数最大为多少?
思路:图+并查集。对于题目的意思,我们可以转化为并查集进行处理,我们发现,可以将每个回合给出的a,b当作一条边的两个端点,最后构成图之后,我们选择n次,我们每次只能选择一个顶点,最后最多可以选择几个顶点。我们可以发现如下规律;
1、如果转化成图之后有的地方形成了环的话,从环出发可以到达的任意点都可以被选到,这个可以自己在纸上画图模拟看看。
2、对于没有构成环的部分,加入这个联通分量有n个点的话,我们就可以选择n-1个点,这个还是自己纸上模拟一下即可。
那么,接下来就说说怎么实现我们的想法了。
首先,我们知道a,b数值太大了,所以我们需要先用map进行离散化,关于离散化的知识建议自行百度。大体就是将一些无规律又很大的数字放到一个数组里面进行存储。

for(int i=1;i<=n;i++){
    scanf("%d%d",&a[i],&b[i]);
    if(!mp[a[i]])  mp[a[i]]=num++;
    if(!mp[b[i]])  mp[b[i]]=num++;
}

我们将a,b依次放入mp容器里面(因为数组存不了这么大的数组),然后形成一一映射。
然后,我们还需要对初始化我们每个下标对应的父结点。
最核心的代码就是下面这个循环了:

int x,y;
for(int i=1;i<=n;i++){
    x=findfather(mp[a[i]]),y=findfather(mp[b[i]]);
    if(x!=y){
        f[x]=y;
        if(vis[x]||vis[y])  vis[y]=1,vis[x]=1;
    }
    else  vis[y]=1;
}

我们先对每次输入的a,b找到各自的父结点来,然后如果两者的父结点是一样的话,那么就说明可以形成环,我们把与环相连通的结点的下标都用vis数组标记为1,如果两者的父结点不一样的话,那么我们就把这两个父结点合并成一个父结点,但是如果这两个父结点中至少有一个被标记为可以与环相连通的话,那么我们就将两者的vis都标记为1.
最后,我们只要检查那些结点的下标没被标记,而且它的父结点还是自己本身的点,减去它们的个数即可。(其实这里是对那些没形成环,又不能与环相联通的那些点),也就是我们上面说到的第二种情况,肯定是有一个点不能被选到的,所以我们让父结点不被选到即可。
ac代码:

#include<iostream>
#include<map>
using namespace std;
map<int,int> mp;
int a[100010],b[100010];
bool vis[200010];
int f[200010];

int findfather(int x){
    if(x!=f[x])  f[x]=findfather(f[x]);
    return f[x];
}

int main(){
    int T;
    scanf("%d",&T);
    for(int index=1;index<=T;index++){
        int num=1;
        mp.clear();
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d%d",&a[i],&b[i]);
            if(!mp[a[i]])  mp[a[i]]=num++;
            if(!mp[b[i]])  mp[b[i]]=num++;
        }
        for(int i=1;i<num;i++)  f[i]=i,vis[i]=0;
        int x,y;
        for(int i=1;i<=n;i++){
            x=findfather(mp[a[i]]),y=findfather(mp[b[i]]);
            if(x!=y){
                f[x]=y;
                if(vis[x]||vis[y])  vis[y]=1,vis[x]=1;
            }
            else  vis[y]=1;
        }
        int ans=num-1;
        for(int i=1;i<num;i++){
            if(f[i]==i&&vis[i]==0)   ans--;
        }
        printf("Case #%d: %d\n",index,ans);
    }
    return 0;
}

Game SET

题意:其实题目的意思很好懂,每张牌有4个属性,然后就是从n张牌中选择出3张牌,对于这三张牌,不允许出现两张牌表示的是某个属性的同一个值,另一张牌表示的是该属性的不同的值,另外还有万能替换牌,可以替换成任意想要的那种类型的牌。
思路:模拟+暴力。其实这个题目是不难的,我到现在都觉得榜被带偏了。出题人给的那个证明的网页登不了,没办法,本人又比较菜,只能按出题的说的去做了。直接暴力。我们发现,每张牌都有4个属性,这很显然可以用结构体去进行存储,我们首要的任务是将每张牌的四个属性从输入中提取出来。

for(int i=1;i<=n;i++){
    string ss;
    cin>>ss;
//    int len=ss.size();
    int id=0;
    for(int j=0;j<4;j++){
        string fea;
        if(ss[id]=='[')  id++;
        while(ss[id]!=']'){
            fea+=ss[id];
            id++;
        }
        if(ss[id]==']')  id++;
        card[i].feature[j]=fea;
    }
}

在确保输入是合法的前提下我们可以像上面那样提取出每张牌的4个属性。
题目之后,我们直接用3重循环来进行枚举,符合直接输出。对于judge函数,我们单独考虑每种属性,然后对于每种属性,我们可以单独记录下当前属性下的万能替换的次数,然后将其余每种属性统一放到set容器里面,我们发现,不能组成set的情况无非就是2+1,当然这里面是没有万能牌的,如果set容器里面的元素个数为2,并且万能牌没有的话,那么这种的肯定是不符合的,否则肯定是可以组成set的。
ac代码:

#include<iostream>
#include<string>
#include<set>
using namespace std;
struct Card{
    string feature[4];
}card[300];
int icase=0;
int n;

bool judge(int x,int y,int z){
    for(int i=0;i<4;i++){
        set<string> st;
        int mild_num=0;
        if(card[x].feature[i]=="*")  mild_num++;
        else  st.insert(card[x].feature[i]);
        if(card[y].feature[i]=="*")  mild_num++;
        else  st.insert(card[y].feature[i]);
        if(card[z].feature[i]=="*")  mild_num++;
        else  st.insert(card[z].feature[i]);
        if(st.size()==2&&mild_num==0)  return false;
    }
    return true;
}

void solve(){
    for(int i=1;i<=n;i++){
        string ss;
        cin>>ss;
    //    int len=ss.size();
        int id=0;
        for(int j=0;j<4;j++){
            string fea;
            if(ss[id]=='[')  id++;
            while(ss[id]!=']'){
                fea+=ss[id];
                id++;
            }
            if(ss[id]==']')  id++;
            card[i].feature[j]=fea;
        }
    }
    /*for(int i=1;i<=n;i++){
        for(int j=0;j<4;j++)  cout<<card[i].feature[j]<<" ";
        cout<<endl;
    }*/
    bool k=false;
    for(int i=1;i<=min(21,n);i++){
        for(int j=i+1;j<=min(21,n);j++) {
            for(int k=j+1;k<=min(21,n);k++){
                if(judge(i,j,k)){
                    printf("Case #%d: %d %d %d\n",icase,i,j,k);
                    return;
                }
            }
        }
    }
    printf("Case #%d: -1\n",icase);
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        icase++;
        scanf("%d",&n);
        solve();
    }
    return 0;
}