打了20分钟的时候突然被叫去干活了。。心急火燎的交了A题。然后在8.50的时候坐下来想B题,最后在回寝室的床上打完了。。

赛后补了C题。

这里只放了前三题的题解,第四题开始不会了。。

  • A CCA的词典

    可以证明,对两个单词进行排序,如果排序以后相同,那么就可以认为两个原单词是一样的。
    注意到单词长度<=2。所以我们直接比较两个位置的字母排序,然后哈希就可以了。

代码:

#include<bits/stdc++.h>
using namespace std;
int t[100000];
int main()
{
    int n;
    string s;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s;
        int tmp=0;
        int a,b;
        a=s[0]-'a'+1;
        if(s.size()==1)b=0;
        else b=s[1]-'a'+1;
        if(a>b)swap(a,b);
        tmp=a*100+b;
        t[tmp]++;
    }
    int q;

    cin>>q;
    string c;
    while(q--){
        cin>>c;
        int tmp=0;
        int a,b;
        a=c[0]-'a'+1;
        if(c.size()==1)b=0;
        else  b=c[1]-'a'+1;
        if(a>b)swap(a,b);
        tmp=a*100+b;
        cout<<t[tmp]<<endl;
    }
    return 0;
}
  • B CCA的搬运

    这题想了挺久的,然后写的时候由于心急一直写错。。
    首先题目告诉我们,每次取一个球然后会把他放到最上面。
    我们考虑一种比较特殊的情况,每个球都会被取一次。此时我们考虑最后一个球,当他被取的时候,他肯定在最下方。同理,上一个球被取肯定在倒数第二个位置。
    那么位置就被固定了。要让消耗最小,我们其实只能按照小球被叫的顺序从上往下放。

那我们其实只要模拟这个过程就好了。由于小球可能会被重复取,我们其实需要知道当前被取小球的上面有哪些小球。发现n,m<=2000。因此我们开个数组每次都暴力的去维护小球的位置。
复杂度就是O(n*m)

代码:

#include<bits/stdc++.h>
using namespace std;
int vis[2003],c[200003];
int main()
{
    int n,m;
    long long sum=0;
    cin>>n>>m;
    int a[3000],b[3000];
    for(int i=1;i<=n;i++)cin>>a[i];
    long long ans=0;
    long long tot=0;
    for(int i=1;i<=m;i++){
        cin>>b[i];
    }
    for(int i=1;i<=m;i++){
        if(!vis[b[i]]){
            vis[b[i]]=1;
             ans+=sum;
            c[++tot]=b[i];
            sum+=a[b[i]];
        }
       else {
           int tmp=0,k=tot;
           for(int j=tot;j>=1;j--){
               if(c[j]==b[i]){
                   ans+=tmp;
                   k=j;
                   break;
               }
               else tmp+=a[c[j]];
           }
           for(int j=k;j<=tot-1;j++)c[j]=c[j+1];
            c[tot]=b[i];
       }

    }
    cout<<ans<<endl;
}
  • C CCA的子树

    这题应该是比较容易想到的。简单的树形dp。
    首先Error的情况就是只有一条链的时候。
    令dp[i][1/2]表示以点i为根节点的选1/2个子树的最大点权和。
    考虑某个结点x,我们可以在dfs中枚举x连出去的边,得到另一个端点y。根据题意我们可以得到转移方程:
    dp[x][1]=max(dp[y][1],sum[x])。其中sum[x]为选x点的点权和
    dp[x][2]=max(dp[y][2],m1+m2)。其中m1,m2为dp[y][1]中的最大值和次大值。这个m1m2我们可以在计算dp[y][1]的时候进行维护。

代码:

#include<bits/stdc++.h>
using namespace std;
const int inf=1e16;
int n,a[200040],d[200400];
int tot,head[200030];
struct node{
    int to,next;
}e[400050];
long long f[200400][3];
void init(){
    tot=0;
    memset(head,-1,sizeof(head));
    for(int i=0;i<200005;i++)f[i][1]=f[i][2]=-inf;
}
void add(int a,int b){
    tot++;
    e[tot].to=b;
    e[tot].next=head[a];
    head[a]=tot;
}
long long dfs(int x){
    long long m1,m2;
    m1=m2=-inf;
    long long sum=a[x];
    int ff=0;
    for(int i=head[x];~i;i=e[i].next){
        int y=e[i].to;
        ff=1;
        sum+=dfs(y);
        if(m1<f[y][1])m2=m1,m1=f[y][1];
        else if(m2<f[y][1])m2=f[y][1];
        f[x][1]=max(f[x][1],m1);
        f[x][2]=max(f[x][2],f[y][2]);
        f[x][2]=max(f[x][2],m1+m2);
    }
    f[x][1]=max(f[x][1],sum);
    return sum;
}
int main()
{
    init();
    cin>>n;
    int maxi=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        add(u,v);
        d[u]++;
        maxi=max(maxi,d[u]);
    }
    dfs(1);
    if(maxi==1)cout<<"Error"<<endl;
    else
    cout<<f[1][2]<<endl;
    return 0;
}