A Garbage Classification

题意

给定一个字符串代表垃圾,26个字符每个字符代表某种组成成分,根据题意判断垃圾类别。

分析

温暖的签到题,注意别写成除法就行。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2050;
int T;
char s[N];
char let[30];
map<char,char> mp;
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%s",s);
        scanf("%s",let);
        mp.clear();
        for(int i=0;i<26;i++){
            mp['a'+i]=let[i];
        }
        int d=0,w=0,h=0;
        int len=strlen(s);
        for(int i=0;i<len;i++){
            if(mp[s[i]]=='d'){
                d++;
            }else if(mp[s[i]]=='w'){
                w++;
            }else if(mp[s[i]]=='h'){
                h++;
            }
        }
        printf("Case #%d: ",cas);
        if(4*h>=len){
            printf("Harmful\n");
        }else if(10*h<=len){
            printf("Recyclable\n");
        }else{
            if(d>=2*w){
                printf("Dry\n");
            }else{
                printf("Wet\n");
            }
        }
    }
    return 0;
}

B Shorten IPv6 Address

题意

给定一个128位的二进制数,先要转成32位十六进制数,每4位一组,中间用冒号分隔,然后如果一组都是0,那么4个0用1个0代替即可,然后可以选择一串且只能一串连续的0和冒号,替换成两个冒号,问可以得到的最小字典序是哪个。

分析

先构造出原字符串,然后对8个组,记录在字符串中的位置,然后枚举让每个组开头的连续0替换成双冒号后得到的字符串,直接暴力找出所有字符串,排序即可。

代码

#include <bits/stdc++.h>
using namespace std;
int T;
char s[130];
char hex(char a,char b,char c,char d){
    int t=(a-'0')*8+(b-'0')*4+(c-'0')*2+(d-'0');
    if(t<=9){
        return char(t+'0');
    }else{
        return char(t-10+'a');
    }
}
vector<string> vs;
bool cmp(string a,string b){
    int al=a.size();
    int bl=b.size();
    if(al==bl){
        return a<b;
    }else{
        return al<bl;
    }
}
int idx[8];
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%s",s);
        memset(idx,0,sizeof(idx));
        string he="";
        int cnt=0;
        for(int i=0;i<128;i+=16){
            string t="";
            bool ac=false;
            for(int j=i;j<i+16;j+=4){
                char p=hex(s[j],s[j+1],s[j+2],s[j+3]);
                if(!ac && p=='0'){
                    continue;
                }
                ac=true;
                t+=p;
            }
            int q=he.size();
            if(t==""){
                he+="0";
            }else{
                he+=t;
            }
            idx[cnt++]=q;
            if(i==112){
                continue;
            }
            he+=":";
        }
        vs.clear();
        vs.push_back(he);
        int hes=he.size();
        for(int i=0;i<8;i++){
            string tle="";
            int t=idx[i];
            if(i!=0){
                t--;
            }
            for(int j=0;j<t;j++){
                tle+=he[j];
            }
            int mle=0;
            while(t<hes && he[t]=='0' || he[t]==':'){
                if(he[t]=='0'){
                    mle++;
                }
                t++;
            }
            if(mle>=2){
                tle+="::";
            }else{
                int j=idx[i];
                if(i!=0){
                    j--;
                }
                for(;j<t;j++){
                    tle+=he[j];
                }
            }
            for(int j=t;j<hes;j++){
                tle+=he[j];
            }
            vs.push_back(tle);
        }
        sort(vs.begin(),vs.end(),cmp);
        printf("Case #%d: %s\n",cas,vs[0].c_str());
    }
    return 0;
}

C Palindrome Mouse

题意

给定一个字符串,求多少对\((a,b)\)满足\(a\)\(b\)都是原串的回文子串,且\(a\)也是\(b\)的子串。

分析

  • 先用回文树构造出所有回文子串节点,对于每个节点来说,对答案的贡献由两部分组成,一部分是fail指针,比如aaa的fail指向aa,那么aa就是aaa的子串,且因为都是树上节点,即都是回文,另一部分是next指针,比如bb的next指向abba,那么bb就是abba的子串,且可以看出bb的fail指针b应该也是abba的子串。

  • 因此我们需要求出每个节点向上fail指针跳的次数和向下next指针跳的次数,乘法定理计算每个节点的贡献。

  • next次数的求法类似于dfs求子树大小,而在dfs的同时暴力往上求fail指针,为了避免重复计数,比如bb跳过的fail指针,bbbb不能再跳,需要暴力标记每个指针是哪个节点跳的,dfs完之后也是暴力清空标记。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+50;
int vis[N],ndp[N],fdp[N];
struct PT{
    int next[N][26],fail[N],cnt[N],num[N],len[N];
    int S[N],last,id[N],n,p;
    int newnode(int l){
        for(int i=0;i<26;i++){
            next[p][i]=0;
        }
        cnt[p]=num[p]=0;
        len[p]=l;
        return p++;
    }
    void init(){
        p=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getFail(int x){
        while(S[n-len[x]-1]!=S[n]){
            x=fail[x];
        }
        return x;
    }
    void add(int c){
        c-='a';
        S[++n]=c;
        int cur=getFail(last);
        if(!next[cur][c]){
            int now=newnode(len[cur]+2);
            fail[now]=next[getFail(fail[cur])][c];
            num[now]=num[fail[now]]+1;
            next[cur][c]=now;
        }
        last=next[cur][c];
        cnt[last]++;
        id[last]=n;
    }
    void count(){
        for(int i=p-1;i>=0;i--){
            cnt[fail[i]]+=cnt[i];
        }
    }
    int dfs(int u){
        ndp[u]=1;
        fdp[u]=0;
        //计算向上跳的fail指针次数,vis保证不重复(比如bb跳的fail指针,bbbb不能再跳),>1保证不走到奇偶根
        for(int t=u;!vis[t] && t>1;t=fail[t]){
            vis[t]=u;
            fdp[u]++;
        }
        for(int i=0;i<26;i++){
            if(next[u][i]){
                ndp[u]+=dfs(next[u][i]);
            }
        }
        //清空标记
        for(int t=u;vis[t]==u && t>1;t=fail[t]){
            vis[t]=0;
        }
        return ndp[u];
    }
    ll solve(){
        //从两个根dfs
        dfs(0);
        dfs(1);
        ll ans=0;
        for(int i=2;i<p;i++){
            //除去根,每个节点的贡献(作为另一个回文子串的子串)为
            //比如对于样例abba,回文节点bb的next指针指向abba,fail指针指向b
            //因此ndp和fdp都为2,贡献为2*2-1=3
            //即(b,bb) (b,abba) (bb,bb) (bb,abba),减1就是要减掉本身
            ans+=1ll*ndp[i]*fdp[i]-1;
        }
        return ans;
    };
}ac;
int T;
char s[N];
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%s",s);
        ac.init();
        int len=strlen(s);
        for(int i=0;i<len;i++){
            ac.add(s[i]);
        }
        ac.count();
        memset(vis,0,sizeof(vis));
        printf("Case #%d: %lld\n",cas,ac.solve());
    }
    return 0;
}

D Move

题意

\(n\)个物品,各自有体积\(v_i\),要用\(k\)个箱子装,装的策略是贪心从大往小,能装就装,问每个箱子最小的容量。

分析

  • 由于贪心的策略,使得箱子容量并不满足单调性,有可能小一点的容量可以,但是如果容量大一点,可能会先取装多个大的,导致小的太多刚好装不下。

  • 最简单的做法是直接从容量的下界\(max(v_n,(sum-1)/k+1)\)开始暴力枚举,然后用multiset判断。

  • 上界的证明:
    • 假设某个容量\(V\)装不下,那么每个箱子剩余容量都小于\(maxV\)
    • 那么\(k*(V-maxV+1)<=sum\)
    • 得到\(V<=sum/k+maxV-1\)
  • 因为\(v_i\)的范围比较小,所以可以感性地理解一下,然后发现二分再暴力一段小区间也是可以的。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int T,n,k,a[N];
multiset<int> mt;
bool check(int x){
    mt.clear();
    for(int i=1;i<=n;i++){
        mt.insert(a[i]);
    }
    for(int i=0;i<k;i++){
        int u=0;
        while(u<x && !mt.empty()){
            auto p=mt.upper_bound(x-u);
            if(p==mt.begin()){
                break;
            }
            p--;
            u+=*p;
            mt.erase(p);
        }
    }
    return mt.empty();
}
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%d%d",&n,&k);
        int sum=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        sort(a+1,a+1+n);
        //从下界暴力枚举
        int ans;
        for(int v=max(a[n],(sum-1)/k+1);;v++){
            if(check(v)){
                ans=v;
                break;
            }
        }
        printf("Case #%d: %d\n",cas,ans);
    }
    return 0;
}

G Is Today Friday?

题意

给n个日期字符串,A到J代表0到9的一个排列,求一个字典序最小的排列使得这n个日期都是星期五。

分析

  • 因为有星期五这个限制,所以对前几个日期,先暴力全排列,找出满足条件的全排列,再从这些全排列中对剩下的日期进行判断。

  • 根据日期计算周几可以用蔡勒公式或者基姆拉尔森公式

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
int T,n;
struct node{
    char str[12];
    bool operator<(const node& rhs)const{
        return string(str)<string(rhs.str);
    }
    bool operator==(const node& rhs)const{
        return string(str)==string(rhs.str);
    }
}s[N];
vector<int> mp;
vector<vector<int> > ts;
int ms[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool leap(int y){
    return ((y%4==0 && y%100!=0) || y%400==0);
}
//判断日期合法性
bool check(int y,int m,int d){
    if(m==0 || m>12 || d==0){
        return false;
    }
    int t=ms[m];
    if(leap(y) && m==2){
        t++;
    }
    return d<=t;
}
//基姆拉尔森公式
bool cl(int y,int m,int d){
    if(!check(y,m,d)){
        return false;
    }
    //题面...
    if(y<1600){
        return false;
    }
    //1 2月要转成上一年13 14月
    if(m<=2){
        m+=12;
        y--;
    }
    int w=((d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7+7)%7;
    return w==5;
}
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s[i].str);
        }
        printf("Case #%d: ",cas);
        sort(s+1,s+1+n);
        int nn=unique(s+1,s+1+n)-s-1;
        //先根据前几个日期筛选出满足条件的全排列(不多),再代入剩下的日期判断
        //ts记得清空
        ts.clear();
        mp.clear();
        for(int i=0;i<10;i++){
            mp.push_back(i);
        }
        do{
            bool ac=true;
            //前4个
            for(int i=1;i<=min(nn,4);i++){
                int y=mp[s[i].str[0]-'A']*1000+mp[s[i].str[1]-'A']*100+mp[s[i].str[2]-'A']*10+mp[s[i].str[3]-'A'];
                int m=mp[s[i].str[5]-'A']*10+mp[s[i].str[6]-'A'];
                int d=mp[s[i].str[8]-'A']*10+mp[s[i].str[9]-'A'];
                if(!cl(y,m,d)){
                    ac=false;
                    break;
                }
            }
            if(ac){
                ts.push_back(mp);
            }
        }while(next_permutation(mp.begin(),mp.end()));
        //判断剩下n-4个
        int sz=ts.size();
//        for(int i=0;i<sz;i++){
//            for(int j=0;j<10;j++){
//                printf("%d",ts[i][j]);
//            }
//            printf("\n");
//        }
        //考虑nn<=4的情况
        if(nn<=4){
            if(sz){
                for(int i=0;i<10;i++){
                    printf("%d",ts[0][i]);
                }
                printf("\n");
            }else{
                printf("Impossible\n");
            }
            continue;
        }
        bool ok=false;
        for(int i=0;i<sz;i++){
            bool ac=true;
            for(int j=5;j<=nn;j++){
                int y=ts[i][s[j].str[0]-'A']*1000+ts[i][s[j].str[1]-'A']*100+ts[i][s[j].str[2]-'A']*10+ts[i][s[j].str[3]-'A'];
                int m=ts[i][s[j].str[5]-'A']*10+ts[i][s[j].str[6]-'A'];
                int d=ts[i][s[j].str[8]-'A']*10+ts[i][s[j].str[9]-'A'];
                if(!cl(y,m,d)){
                    ac=false;
                    break;
                }
            }
            if(ac){
                for(int j=0;j<10;j++){
                    printf("%d",ts[i][j]);
                }
                printf("\n");
                ok=true;
                break;
            }
        }
        if(!ok){
            printf("Impossible\n");
        }
    }
    return 0;
}

J Upgrading Technology

题意

\(n\)个技能,每个技能有\(m\)个等级,第\(i\)技能从\(j-1\)级升到\(j\)级需要花费\(c_{ij}\)\(n\)个技能都升到\(i\)级可以获得收益\(d_i\),问最大收益。

分析

题意中暗示可以\(O(nm)\)的复杂度,因此考虑枚举所以技能同时升到\(i\)级,此时的收益可以前缀和求出,再枚举最低等级也就是刚好\(i\)级的技能,那么这个技能的花费也能前缀和求出,而其他技能就可以在\(i\)级后面随便升了,我们只需要预处理出每个技能在某个级别之后升级的最小花费以及n个技能的最小花费和。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int T,n,m;
ll c[N][N],d[N];
ll dp[N],cp[N][N];
ll mn[N][N],smn[N];
int main(void){
    freopen("in.txt","r",stdin);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        ll ans=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            cp[i][0]=0;
            for(int j=1;j<=m;j++){
                scanf("%lld",&c[i][j]);
                cp[i][j]=cp[i][j-1]+c[i][j];
            }
        }
        //mn[i][j]表示第i个技能从第j个等级开始的最小前缀和
        memset(smn,0,sizeof(smn));
        for(int i=1;i<=n;i++){
            mn[i][m]=cp[i][m];
            ll _mn=mn[i][m];
            smn[m]+=cp[i][m];
            for(int j=m-1;j>=0;j--){
                if(cp[i][j]<_mn){
                    _mn=cp[i][j];
                }
                mn[i][j]=_mn;
                smn[j]+=mn[i][j];
            }
        }
        dp[0]=0;
        for(int i=1;i<=m;i++){
            scanf("%lld",&d[i]);
            dp[i]=dp[i-1]+d[i];
        }
        for(int i=0;i<=m;i++){
            //枚举相同等级,可以都不升级
            for(int j=1;j<=n;j++){
                ll tmp=dp[i];
                //枚举最低等级的技能
                tmp-=cp[j][i];
                //剩下的技能找大于等于i级别的最小前缀和之和
                tmp-=smn[i];
                //最低等级的技能被重复减了
                tmp+=mn[j][i];
                ans=max(ans,tmp);
            }
        }
        printf("Case #%d: %lld\n",cas,ans);
    }
    return 0;
}