2022天梯赛题解

2022天梯赛

博客传送门

L1

L1的题目冲的非常快,就败在了L2-1的模拟题,主要是种种原因后面心态炸了

L1-7

赛场上我用容斥写的
但是听说下面这种解法更简便

#include<bits/stdc++.h>
using namespace std;
int visn[100010],vism[100010];
#define int long long
int n,m,k,op,x;
signed main(){
    cin>>n>>m>>k;
    while(k--){
        cin>>op>>x;
        if(op==0)
            if(!visn[x])n--,visn[x]=1;
        if(op==1)
            if(!vism[x])m--,vism[x]=1;
    }
    cout<<n*m;
}

L1-8

对权值进行维护就好了

L2

L2-1

天梯赛就三个小时占了我
这个一个半小时的题目,我觉得写不出来一是基础不扎实,二就是当时心态炸了,后面根本没心思调

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
queue<int>q;
stack<int>stk;
int n,m,k;
vector<vector<int>>ans;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        q.push(x);
    }
    vector<int>t;
    int cnt=1;
    while(q.size()||stk.size()){
//         cout<<"cnt="<<cnt<<endl;
        //if(++cnt>20)break;
        if(stk.empty()&&q.size()){
            int x=q.front();
            //cout<<"x="<<x<<endl;
            if(t.size()==0)t.push_back(x),q.pop();
            else if(x<=t.back())t.push_back(x),q.pop();
            else stk.push(x),q.pop();
            if(t.size()==k)ans.push_back(t),t.clear();
        }
        else{
            //如果栈符合要求就塞进去
            int x;
            x=stk.top();
            //cout<<"x="<<x<<endl;
            if(t.size()==0)t.push_back(x),stk.pop();
            else if(x<=t.back())t.push_back(x),stk.pop();
            else if(q.size()){
                //说明栈不能放
                //从队列里面找
                x=q.front();
                //cout<<"x="<<x<<endl;
                if(x<=t.back())t.push_back(x),q.pop();
                else if(stk.size()<m)stk.push(x),q.pop();
                else ans.push_back(t),t.clear();
            }
            else ans.push_back(t),t.clear();
            if(t.size()==k)ans.push_back(t),t.clear();
            
        }
    }
    if(t.size())ans.push_back(t);
    for(auto t:ans){
        cout<<t[0];
        for(int i=1;i<t.size();i++)cout<<" "<<t[i];
        cout<<'\n';
    }
}

L2-2

垃圾题
傻逼题

#include<bits/stdc++.h>
using namespace std;
int n;
struct node{
    int h,m,s,hh,mm,ss;
    bool operator<(const node&t){
        if(h!=t.h)return h<t.h;
        if(m!=t.m)return m<t.m;
        return s<t.s;
    }
}a[100010];
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d:%d:%d - %d:%d:%d",&a[i].h,&a[i].m,&a[i].s,&a[i].hh,&a[i].mm,&a[i].ss);
    }
    sort(a+1,a+1+n);
    int ph=0,pm=0,ps=0;
    for(int i=1;i<=n;i++){
        int h=a[i].h,m=a[i].m,s=a[i].s,hh=a[i].hh,mm=a[i].mm,ss=a[i].ss;
        if(ph==h&&pm==m&&ps==s);//printf("%02d:%02d:%02d - %02d:%02d:%02d\n",a[i].h,a[i].m,a[i].s,a[i].hh,a[i].mm,a[i].ss);
        else printf("%02d:%02d:%02d - %02d:%02d:%02d\n",ph,pm,ps,a[i].h,a[i].m,a[i].s);
        ph=hh,pm=mm,ps=ss;
    }
    if(ph!=23||pm!=59||ps!=59)printf("%02d:%02d:%02d - %02d:%02d:%02d\n",ph,pm,ps,23,59,59);
}

L2-3

就是个典型的小树形dp,但是赛场上没思路,感觉把时间丢在这道题而不是L2-1的模拟应该可以冲
题目要求的是最后不用回来的总代价距离最小值
但是对确切的外卖点,往返各一次是确定的,所以很显然预处理到根的距离,剪掉要送外卖的地点集合的最大值就好了

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
#define int long long
vector<int>g[N];
int vis[N],n,m,fa[N],ans,ma,f[N],x;
void dfs(int u){
    for(auto j:g[u]){
        f[j]=f[u]+1;
        dfs(j);
    }
}
int dfs(int x,int d){
    if(vis[x]){
        ma=max(ma,f[x]+d);
        return d*2;
    }
    vis[x]=1;
    return dfs(fa[x],d+1);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    int u;
    for(int i=1;i<=n;i++){
        cin>>fa[i];
        if(fa[i]==-1)u=i;
        else g[fa[i]].push_back(i);
    }
    dfs(u);
    vis[u]=1;
    while(m--){
        cin>>x;
        ans+=dfs(x,0);
        //cout<<"ans="<<ans<<" ma="<<ma<<endl;
        cout<<ans-ma<<'\n';
    }
}

L2-4

一样的代码,赛场上超时只有15分,现在一发满分,我tm当时还全删了重写浪费我时间!
傻逼东西rnm退钱!

#include<bits/stdc++.h>
using namespace std;
const int N=550;
int f[N][N],n,d[N],a[N];
int ans[N];
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;i++){
        char c;
        cin>>c;
        if(c=='M')a[i]=1;
        int m;
        cin>>m;
        while(m--){
            int x,y;
            cin>>x>>c>>y;
            f[i][x]=y;
        }
    }
    for(int k=1;k<=n;k++)f[k][k]=0;
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
                //f[j][i]=min(f[j][i],f[j][k]+f[k][i]);
            }
        }
    }
    vector<int>ans1,ans0;
    int a1=0x3f3f3f3f;
    int a0=0x3f3f3f3f;
    //memset(ans,0x3f,sizeof ans);
    //求到我的,且为异性的最大值
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(a[i]!=a[j])ans[j]=max(ans[j],f[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        if(a[i])a1=min(a1,ans[i]);
        else a0=min(a0,ans[i]);
    }
    for(int i=1;i<=n;i++){
        if(a[i]&&ans[i]==a1)ans1.push_back(i);
        if(!a[i]&&ans[i]==a0)ans0.push_back(i);
    }
    swap(ans1,ans0);
    cout<<ans1[0];
    for(int i=1;i<ans1.size();i++)cout<<" "<<ans1[i];
    cout<<'\n';
    cout<<ans0[0];
    for(int i=1;i<ans0.size();i++)cout<<" "<<ans0[i];
    
}

L3

L3-1

比较裸的拓扑排序,根据数字的大小关系建边,如果我比你大,那么你会向我连一条边,我入度加加
入度相等的时候用字符串大小来排序,可以用优先队列来处理

我在打比赛的时候往L3瞄了一下,感觉第一题其实可以做,也对着样例一眼就看出来要纵向判断建边
但是“入度相等的时候用字符串大小来排序”,这里我脑抽了,没有想到用优先队列,加上前面的L2还没写完,因为分数没拿够L3不算分
所以跑去写L2了,这波属实是拉跨啊我靠,要是顺利切掉L2-1的话能水230分,百分百国二

#include<bits/stdc++.h>
using namespace std;
unordered_map<string,int>mp,d;
string S[100010];
vector<vector<string>>ans;
vector<int>g[10010];
int n,idx;
vector<string>v,res;
string s;
priority_queue<string,vector<string>,greater<string>>q;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s;
        s+='.';
        string t="";
        for(auto c:s){
            if(c=='.'){
                if(!mp.count(t))mp[t]=++idx,S[idx]=t;
                v.push_back(t),t="";
            }
            else t+=c;
        }
        
        ans.push_back(v);
        v.clear();
    }
//     for(auto v:ans){
//         for(auto t:v)cout<<t<<".";
//         cout<<endl;
//     }
    for(int i=1;i<n;i++){
        if(ans[i].size()!=ans[i-1].size())continue;
        for(int j=0;j<ans[i].size();j++){
            if(ans[i-1][j]!=ans[i][j]){
                int a=mp[ans[i-1][j]];
                int b=mp[ans[i][j]];
                g[a].push_back(b);
                d[ans[i][j]]++;
                //cout<<"aaa"<<endl;
                break;
            }
        }
    }
//     for(int i=1;i<=idx;i++){
//         cout<<S[i]<<": ";
//         for(auto j:g[i])cout<<i<<j<<" ";
//         cout<<endl;
//     }
    for(auto &[k,v]:mp){
        if(d[k]==0)q.push(k);
    }
    while(q.size()){
        string u=q.top();
        q.pop();
        res.push_back(u);
        for(auto t:g[mp[u]]){
            if(--d[S[t]]==0)q.push(S[t]);
        }
    }
    cout<<res[0];
    for(int i=1;i<res.size();i++)cout<<"."<<res[i];
}

经典题
首先,我们对于这个树上两个结点的关系分为两类,一类是有直接父子或祖先关系的结点对,这样的结点对在DFS序中的顺序是确定的,一定是父亲在前面,儿子在后面,那么这样的结点对,如果是逆序对,一定会出现在每一种DFS序中,所以,这样的逆序对的贡献就是这个树的DFS序的种类数。另外一类就是不具有直接父子或祖先关系的结点对,这样的结点对在每种DFS序中的顺序不固定,但是我们经过观察完全可以发现(我发现不了,杜喻皓大爹说了我才知道 ),这样的结点对要不然是逆序对,要不然不是逆序对,概率都是1/2,那么从期望的角度考虑,这样的结点对对于答案的贡献其实就是这个树的DFS序的种类数除以2

那么如何求解一颗树的DFS序种类数呢,其实很简单对于一颗子树u,它的DFS序的种类数就是它的所有子树的方案数的乘积,再乘上u的子树个数的全排列(也就是阶乘),然后向上递推就行。第二个问题,如何求解两种点对的数目。首先我们知道,对于n个点能组合出来的总点对数是n*(n-1)/2,那么只要求出其中一类的个数,另外一类就知道了,我们选择求解上述第一类的个数,令 d[i] 表示以 i 为根节点的子树第一类结点对的数目,很容易观察得到,其实就是以 i 为根节点的子树中 i 有多少个孩子or孙子,那么这个自然也可以向上递推。第三个问题,如何计算第一类结点对中逆序对的个数,我们令 mx[i] 表示从结点 i 出发往上遍历直到根节点,有多少个比它大的结点数,这个在DFS的过程中在值域上建立树状数组就可以统计。

L3-2

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
const int mod=1e9+7;
int n,root,tr[N],fac[N],A,B,K,son[N],f[N],mx[N],ans,d[N];
vector<int>g[N];
void add(int x,int c){
    for(int i=x;i<=n;i+=i&-i)tr[i]+=c;
}
int sum(int x){
    int ans=0;
    for(int i=x;i;i-=i&-i)ans+=tr[i];
    return ans;
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=(ans*a)%mod;
        b/=2;
        a=(a*a)%mod;
    }
    return ans;
}
void dfs(int u,int fa){
    mx[u]=sum(n)-sum(u);
    add(u,1);
    f[u]=1;
    for(auto j:g[u]){
        if(j==fa)continue;
        son[u]++;
        dfs(j,u);
        f[u]=(f[u]*f[j])%mod;
        d[u]+=d[j];
    }
    d[u]+=son[u];
    f[u]=f[u]*fac[son[u]]%mod;
    add(u,-1);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>root;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    fac[0]=1;
    for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    dfs(root,-1);
    K=f[root];
    
    //计算第一类节点对的贡献
    for(int i=1;i<=n;i++)ans=(ans+mx[i]*K%mod)%mod;
    
    //计算第二类节点对的贡献
    int t=n*(n-1)%mod*qpow(2,mod-2)%mod;
    for(int i=1;i<=n;i++)A=(A+d[i])%mod;
    B=(t-A+mod)%mod;
    B=(B*qpow(2,mod-2)%mod)%mod;
    ans=(ans+B*K%mod)%mod;
    
    cout<<ans;
}

csoj还几乎有道原题传送门
求期望的话不要计算dfs序种类数,删去取模操作

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
const int mod=1e9+7;
int n,root,tr[N],fac[N],son[N],f[N],mx[N],d[N];
double A,B,K,ans;
vector<int>g[N];
void add(int x,int c){
    for(int i=x;i<=n;i+=i&-i)tr[i]+=c;
}
int sum(int x){
    int ans=0;
    for(int i=x;i;i-=i&-i)ans+=tr[i];
    return ans;
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=(ans*a)%mod;
        b/=2;
        a=(a*a)%mod;
    }
    return ans;
}
void dfs(int u,int fa){
    mx[u]=sum(n)-sum(u);
    add(u,1);
    for(auto j:g[u]){
        if(j==fa)continue;
        son[u]++;
        dfs(j,u);
        d[u]+=d[j];
    }
    d[u]+=son[u];
    add(u,-1);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>root;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    dfs(root,-1);
    //计算第一类节点对的贡献
    for(int i=1;i<=n;i++)ans+=mx[i];
    
    //计算第二类节点对的贡献
    double t=(double)n*(n-1)/2;
    for(int i=1;i<=n;i++)A=(A+d[i]);
    B=(t-A)/2;
    ans+=B;
    printf("%.1lf",ans);
}

L3-3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值