$2023 Xian Jiaotong University Programming Contest$

发布时间 2023-09-19 01:12:30作者: EdGrass

\(A.大水题\)

void solve(){
    int n=read();
    puts(n<=6?"water":"dry");
    //puts(ans>0?"Yes":"No");
}

\(B.原粥率\)

void solve(){
    int n=read(),m=read();
    double ans=n*1.0/m;
    cout<<ans<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(C.话剧\)

void solve(){
    int x=read(),y=read(),z=read();
    printf("%.6lf",z*1.0/x/y);
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(D.点集扩张\)

进行一次移动就是扩大矩形的长或者宽。先看需要的点集是否是个矩形,否则输出不可能。答案就是最后矩形的长加宽(不包括 \(x=0\)\(y=0\) 两条线)。

int mp[N][N];
void solve(){
    int n=read(),ans=1;
    vector<PII>a(n);
    for(int i=0;i<n;i++){
        int x=read(),y=read();
        a[i]=make_pair(x,y);
        mp[x+200][y+200]=1;
    }
    sort(a.begin(),a.end());
    int cnt=0;
    for(int i=a[0].first;i<=a[n-1].first;i++){
        for(int j=a[0].second;j<=a[n-1].second;j++){
            if(!mp[i+200][j+200]){
                ans=0;
                break;
            }
            cnt++;

        }
    }
    if(!mp[0+200][0+200])ans=0;
    if(!ans||cnt!=n){
        cout<<"-1\n";
        return ;
    }
    cout<<0-a[0].first+a[n-1].first-0+0-a[0].second+a[n-1].second-0<<'\n';
    //puts(ans>0?"Yes":"No");
}

\(E.全错\)

参加聚会仅当聊天图构成多个基环图,所以判断每个点的度即可。

void solve(){
    int n=read();
    vector<int>d(n+2),r(n+2);
    double b;
    cin>>b;
    for(int i=1;i<=n;i++){
        int nex=-1;
        double maxx=-10;
        for(int j=1;j<=n;j++){
            double x;
            cin>>x;
            if(i==j)continue;
            if(x>=b){
                if(x>maxx){
                    maxx=x;
                    nex=j;
                }
            }
        }
        if(nex!=-1){
            d[i]++;
            r[nex]++;
        }
    }
    if(n==1){
        cout<<"kono jinsei, imi ga nai!\n";
        return ;
    }
    for(int i=1;i<=n;i++){
        if(d[i]!=1||r[i]!=1){
            cout<<"hbxql\n";
            return;
        }
    }
    cout<<"wish you the best in your search\n";
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(F.渡渡鸟游乐场\)

根据题意,每一高度面上只有中间和边上的木条都被拿了才会倒,那么根据拿走的顺序记录判断即可。

void solve(){
    int n=read(),ans=1,op=-1;
    vector<array<int,4> >a(n+2);
    for(int i=1;i<=3*n-3;i++){
        int u=read(),v=read(),w=read();
        a[u][v]=1;
        if(a[u][1]==1&&a[u][2]==1){
            if(ans==1)ans=0,op=i;
        }
        if(a[u][3]==1&&a[u][2]==1){
            if(ans==1)ans=0,op=i;
        }
    }
    puts(op%2==0?"Sheauhaw":"Nocriz");
    //puts(ans>0?"Yes":"No");
}

\(G.和而不同\)

这道题训练的时候写了很久没写出来,以为是什么巧妙的方法。

会发现链是最好控制的一种结构,然后考虑边权。因为每次增加的新的路径会与前面的边构成区间和,如果取 \(1,2,3,4,\ldots ,n-1\) 这样的边权会很容易重复。如果我们从后面取, \(\frac{{(n+1)}^2}{4}-n,\ldots ,\frac{{(n+1)}^2}{4}-1,\frac{{(n+1)}^2}{4}\) ,数字增长较快,更分散,显然这样只有不同长度的路径才可能会相同,而对于不同长度的路径,我们可以证明,最小的长度为 \(m\) 的边权和必然小于最大的长度为 \(m+1\) 的边权和。

void solve(){
    int n=read(),cnt=(int)((n+1)*(n+1)/4);
    cout<<n-1<<'\n';
    for(int i=1;i<n;i++){
        cout<<i<<" "<<i+1<<" "<<cnt<<"\n";
        cnt--;
    }
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(H.字符游戏\)

那么显然最后一轮删除的时候需要每个字符串的某个字母的个数都不同。那么一旦满足这个条件,其他轮也一定不会出现相同的字符串。那么我们看看这个条件是否满足即可。

int cnt[N][30];
void solve(){
    int n=read(),ans=0;
    for(int i=1;i<=n;i++){
        string s;
        cin>>s;
        for(auto x:s){
            cnt[i][x-'a']++;
        }
    }
    for(int i=0;i<26;i++){
        set<int>st;
        for(int j=1;j<=n;j++){
            st.insert(cnt[j][i]);
        }
        if(st.size()==n){
            cout<<"YES\n";
            for(char c='a';c<='z';c++){
                if(c==(char)('a'+i))continue;
                cout<<c;
            }
            cout<<(char)(i+'a')<<'\n';
            return ;
        }
    }
    cout<<"NO\n";
    //puts(ans>0?"Yes":"No");
}

\(I.喵喵喵\)

\(J.大秦酒店欢迎您\)

看到题目,就知道莫队可以写。(虽然是 \(1e6\) )。
别的都很显然,主要说一下 \(add\)\(del\) 两个函数。以 \(add\) 为例,记录上一次当前位置出现的数字,那么对于新增的右节点,增加答案大小显然是目前区间左端与上一次出现位置取 \(max\) 后,到右端点的距离。对于左端点答案增加当前区间的不同数字的个数,当前区间答案每次都会加上操作端点的答案。

void solve(){
    int n=read(),q=read();
    vector<int>a(n);
    for(int i=0;i<n;i++){
        a[i]=read();
        a[i]--;
    }
    vector<array<int,3> >que(q);
    for(int i=0;i<q;i++){
        int l=read()-1,r=read()-1;
        que[i]={l,r,i};
    }
    vector<int> p(n, -1);
    vector<int> pre(n, -1);
    for (int i = 0; i < n; i++) {
        pre[i] = p[a[i]];
        p[a[i]] = i;
    }
    p.assign(n, n);
    vector<int> suf(n, n);
    for (int i = n - 1; i >= 0; i--) {
        suf[i] = p[a[i]];
        p[a[i]] = i;
    }
    int B=sqrt(q);
    sort(que.begin(),que.end(),[&](array<int,3>a,array<int,3>b){
        if(a[0]/B!=b[0]/B)return a[0]/B < b[0]/B;
        return (a[0]/B) % 2  ? a[1]>b[1]:a[1]<b[1];
    });
    vector<mint>ans(q);
    vector<int>cnt(n);
    mint res=0,rres=0,lres=0;
    int l=0,r=-1,now=0;
    auto add=[&](int x,int op){
        ++cnt[a[x]];
        if(cnt[a[x]]==1)++now;
        if(op==1){
            rres+=x-max(pre[x],l-1);
            res+=rres;
            lres+=now;
        }else{
            lres+=min(suf[x]-1,r)-x+1;
            res+=lres;
            rres+=now;
        }
    };
    auto del=[&](int x,int op){
        if(op==1){
            res-=rres;
            lres-=now;
            rres-=x-max(pre[x],l-1);
        }else{
            res-=lres;
            rres-=now;
            lres-=min(suf[x]-1,r)-x+1;
        }
        --cnt[a[x]];
        if(cnt[a[x]]==0)--now;
    };
    for(int i=0;i<q;i++){
        while(r<que[i][1])r++,add(r,1);
        while(l>que[i][0])l--,add(l,0);
        while(r>que[i][1])del(r,1),r--;
        while(l<que[i][0])del(l,0),l++;
        ans[que[i][2]]=res;
    }
    for(int i=0;i<q;i++)cout<<ans[i]<<"\n";
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(K.莉可丽丝\)

\(L.绘画爱好者以撒\)

\(M.斑马子树\)

题目里的子树是以某个节点为根的全部子树。所以对于染色后考虑有黑色和全为黑色,先对有黑色的节点的树向上增添答案,然后对有贡献且子树全为黑色的点向上减掉贡献。由于每个点最多贡献一次答案,最多删除一次答案,复杂度为 \(O(2n)\)

int siz[N],fa[N],sum[N],res[N];
vector<int>G[N];
void dfs(int u){
    siz[u]=1;
    for(auto v:G[u]){
        dfs(v);
        siz[u]+=siz[v];
    }
}
void solve(){
    int n=read();
    fa[1]=0;
    for(int i=2;i<=n;i++){
        int x=read();
        fa[i]=x;
        G[x].push_back(i);
    }
    dfs(1);
    int ans=0;
    for(int i=1;i<=n;i++){
        int op=read();
        if(sum[op]==0&&res[op]==0){
            res[op]=1;
            ans++;
            int tmp=op;
            while(sum[fa[tmp]]==0&&res[fa[tmp]]==0&&fa[tmp]){
                res[fa[tmp]]=1;
                ans++;
                tmp=fa[tmp];
            }
        }
        sum[op]++;
        if(sum[op]==siz[op]){
            ans--;
            int tmp=op;
            res[tmp]=0;
            while(sum[fa[tmp]]+sum[tmp]==siz[fa[tmp]]&&res[fa[tmp]]&&fa[tmp]){
                res[fa[tmp]]=0;
                ans--;
                sum[fa[tmp]]=siz[fa[tmp]];
                tmp=fa[tmp];
            }
            sum[fa[tmp]]+=sum[tmp];
        }
        cout<<ans<<' ';
    }
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(N.栈列\)

如果切换到栈模式,一定可以生成给定的序列。只有当序列是完全连续的时可以用队列模式生成。

void solve(){
    int n=read(),m=read();
    int a=read(),b=read(),c=read();
    vector<int>x(n);
    for(int i=0;i<n;i++){
        x[i]=read();
    }
    int ans=INF,ok=1;
    for(int i=1;i<n;i++){
        if(x[i]!=(x[i-1]+1)%m){
            ok=0;
            break;
        }
    }
    if(ok)ans=min(ans,x[0]*(a+b)+n*a);
    int res=c;
    for(int i=0;i<n;i++){
        if(i==0){
            res+=x[i]*(a+b);
            res+=a;
            continue;
        }
        res+=(x[i]-x[i-1]+m-1)%m*(a+b);
        res+=a;
    }
    ans=min(res,ans);
    cout<<ans<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(O.打则\)

答案为 \(n!\) ,因为对于任意一个排列我们都可以找到最短的前缀,使得前 \(i\) 个数字包含某个 \(S_i\) ,然后将这个数字移到最前面,构成合法的强度表。

void solve(){
    int n=read(),m=read();
    for(int i=1;i<=m;i++){
        int k=read();
        for(int j=1;j<=k;j++){
            int x=read();
        }
    }
    mint ans=1;
    for(int i=1;i<=n;i++){
        ans*=i;
    }
    cout<<ans<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}```