2024 中国大学生程序设计竞赛全国邀请赛(山东) The 2024 CCPC Shandong Invitational Contest and Provincial Collegiate Programming Contest

I. Left Shifting 左移

签到

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    string s;
    cin>>s;

    if(s[0]==s.back()){
        cout<<0<<endl;
        return;
    }

    for(int i=0;i<s.size()-1;i++){
        if(s[i]==s[i+1]){
            cout<<i+1<<endl;
            return;
        }
    }

    cout<<-1<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

A. Printer 打印机

显然的二分。

如何发现是二分?因为时间越长,打印的题目数量一定变多,题目数量是随着时间单调递增的

假设时间是mid,如何找到第i个打印机在mid时间内打印了多少题目?

mid / (t[i] * L[i] + w[i]) * L[i] + min(L[i], mid % (t[i] * L[i] + w[i]) / t[i]);

分为两部分,(t[i] * L[i] + w[i]) * L[i] 和 +min(L[i], mid % (t[i] * L[i] + w[i]) / t[i])

前一部分是,每打印L个题目后,会休息w分钟,这相当于是一个循环,所以有几个这个时间,就相当于打印了多少 L个题目

后一部分,是最后剩下的小于一个循环的时间,因为一个循环最多打印L个,所以要跟L取min。这点在样例里也有体现

两个可能wa的点:r如果不确定就尽量开大点;check中途只要符合条件随时退出,否则会爆ll。(曾经某次div3 f二分题因此被hack)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    int n,k;
    cin>>n>>k;

    vector<int> t(n+10),L(n+10),w(n+10);

    for(int i=1;i<=n;i++){
        cin>>t[i]>>L[i]>>w[i];
    }

    int l=1,r=2e18;

    auto check=[&](int mid)->bool {
        int cnt=0;
        for(int i=1;i<=n;i++){
            cnt+=mid/(t[i]*L[i]+w[i])*L[i]+min(L[i],mid%(t[i]*L[i]+w[i])/t[i]);
            if(cnt>=k) return 1;
        }

        return 0;
    };

    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }

    cout<<l<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

K. Matrix 矩阵

这辈子赛时写不出来构造了

n=5时:

1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 5 6 7 8
4 5 6 9 10

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    int n;
    cin>>n;

    int g[n+1][n+1];
    int now=1;

    for(int i=1;i<=n-2;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=now;
        }
        now++;
    }

    for(int j=1;j<=n-2;j++){
        for(int i=n-1;i<=n;i++){
            g[i][j]=now;
        }
        now++;
    }

    for(int i=n-1;i<=n;i++){
        for(int j=n-1;j<=n;j++){
            g[i][j]=now++;
        }
    }

    cout<<"Yes\n";

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cout<<g[i][j]<<" ";
        }
        cout<<endl;
    }


}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    // cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

F. Divide the Sequence 分割序列

前六题里最有意思的一道题

首先先找到n-1个后缀,从大到小排序

初始值是sum

后续k每+1,其实就是加一个此时最大的后缀进来

所以在从大到小的后缀上做前缀和即可,这个数组称为t

k从2到n (k=1是sum)

ans(k)=sum+b[k-1] (若b数组下标从1开始)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    int n,sum=0;
    cin>>n;

    vector<int> a(n+10),suf(n+10),t;

    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum+=a[i];
    }

    for(int i=n;i>=2;i--){
        suf[i]=a[i]+suf[i+1];
        t.push_back(suf[i]);
    }

    sort(t.begin(),t.end(),greater<int>());

    for(int i=1;i<t.size();i++){
        t[i]=t[i-1]+t[i];
    }

    cout<<sum<<" ";
    for(int k=2;k<=n;k++){
        cout<<sum+t[k-2]<<" ";
    }
    cout<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

C. Colorful Segments 2 多彩的线段 2

感觉很板的题

将所有线段按照右端点排序

以此加进来每一条线段,如果加进来是,这条线段上被 cnt 条线段覆盖,则这条线段可选颜色有k - cnt个

如果cnt>=k,直接输出0

如何找区间内有几条线段覆盖?离散化+线段树维护区间最大值

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =998244353;

class SegmentTree{
    public:
    #define lc u<<1
    #define rc u<<1|1

    struct Node{
        int l,r,mx;
        int add;
    };

    int n;
    vector<int> w;
    vector<Node> tr;

    SegmentTree(int n){
        init(n);
    }

    void init(int n){
        this->n=n;
        w.resize(n+10);
        tr.resize(4*n+10);
    }

    void pushup(int u){
        tr[u].mx=max(tr[rc].mx,tr[lc].mx);
    }

    void pushdown(int u){
        if(tr[u].add){
            tr[rc].add+=tr[u].add;
            tr[lc].add+=tr[u].add;
            tr[rc].mx+=tr[u].add;
            tr[lc].mx+=tr[u].add;
            tr[u].add=0;
        }
        return;
    }

    void build(int u,int l,int r){
        if(l==r) tr[u]={l,r,w[r],0};
        else{
            tr[u]={l,r};
            int mid=l+r>>1;
            build(lc,l,mid);
            build(rc,mid+1,r);
            pushup(u);
        }
    }

    int query(int u,int l,int r){
        if(l<=tr[u].l && r>=tr[u].r) return tr[u].mx;
        else{
            pushdown(u);
            int mx=0,mid=tr[u].l+tr[u].r>>1;
            if(l<=mid) mx=query(lc,l,r);
            if(r>mid) mx=max(mx,query(rc,l,r));
            return mx;
        }
    }

    void modify(int u,int l,int r,int k){
        if(l<=tr[u].l && r>=tr[u].r){
            tr[u].mx+=k;
            tr[u].add+=k;
        }
        else{
            pushdown(u);
            int mid=tr[u].r+tr[u].l>>1;
            if(l<=mid) modify(lc,l,r,k);
            if(r>mid) modify(rc,l,r,k);
            pushup(u);
        }
    }
};

void solve(){
    int n,k;
    cin>>n>>k;

    SegmentTree tr(2*n);
    tr.build(1,1,2*n);

    vector<pii> p(n+1);

    vector<int> b;

    for(int i=1;i<=n;i++){
        int x,y;
        cin>>x>>y;
        p[i]={x,y};
        b.push_back(x);
        b.push_back(y);
    }

    sort(b.begin(),b.end());
    b.erase(unique(b.begin(),b.end()),b.end());

    map<int,int> mp;

    for(int i=0;i<b.size();i++){
        mp[b[i]]=i+1;
    }

    for(int i=1;i<=n;i++){
        auto [x,y]=p[i];
        p[i]={mp[x],mp[y]};
    }

    sort(p.begin(),p.end());

    ll ans=1;

    for(int i=1;i<=n;i++){
        auto [x,y]=p[i];
        int cnt=tr.query(1,x,y);
        if(cnt>k){
            cout<<0<<endl;
            return;
        }
        ans*=(k-cnt);
        ans%=mod;
        tr.modify(1,x,y,1);
    }

    cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

J. Colorful Spanning Tree 多彩的生成树

对每个颜色做最小生成树,跟普通最小生成树唯一不同的是每个颜色可能有多个点

做克鲁斯卡尔时,遍历到一个边u,v,w

如果u == v 如果u此时已经在一个连通块内,不是一个孤立的点,则跳过,否则 ans+=(a[i]-1) * g[u][u]

(把所有颜色是u的点连起来)

如果u != v 先给答案+w,如果u是一个孤立的点,则给用v给所有u连上,ans+=(a[u]-1) * w,v同理

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

class DSU{
	public:
		vector<int> fa,sz;
		int setCount;
		int n;

        DSU(){}
        DSU(int n){
            init(n);
        }

		void init(int n){
            fa.resize(n+1);
            sz.resize(n+1,1);
            this->n=n;
            setCount=n;
  			iota(fa.begin(), fa.end(), 0);
		}
		
		int find(int x) {
			if(fa[x] == x) return x;
			return fa[x] = find(fa[x]);
		} 
		
		
		void unite(int x, int y) {
			x = find(x),y = find(y);
			if(x == y) return;

		 	if(sz[x] <= sz[y] ) swap(x, y);
		 	fa[y] = fa[x];
			sz[x] += sz[y];
			--setCount;
		}
};


void solve(){
    int n;
    cin>>n;

    vector<int> w(n+10);
    int g[n+1][n+1];

    for(int i=1;i<=n;i++){
        cin>>w[i];
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>g[i][j];
        }
    }

    DSU dsu(n);
    vector<array<int,3>> edge;

    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            edge.push_back({g[i][j],i,j});
        }
    }

    sort(edge.begin(),edge.end());

    int ans=0;
    set<int> st;

    for(auto [len,u,v]:edge){
        if(u==v){
            if(st.count(u)) continue;
            st.insert(u);

            ans+=(w[u]-1)*len;
        }else{
            if(dsu.find(u)!=dsu.find(v)){
                dsu.unite(u,v);
                ans+=len;

                if(!st.count(u)){
                    ans+=(w[u]-1)*len;
                    st.insert(u);
                }

                if(!st.count(v)){
                    ans+=(w[v]-1)*len;
                    st.insert(v);
                }
            }
        }
    }

    cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

D. Hero of the Kingdom 王国英雄

每次买卖时,肯定是能买多少买多少。

如果买卖一次就计算一次,复杂度o(t)不可接受

每次买货物的数量肯定是不减的,所以我们可以把每次买货物数量相同的来回合起来。

假设一开始可以买1个货物,且每次买卖的时间至少是2,则(1+2+3+...+k)* 2 = t

则k的值是sqrt(n)数量级的。

当剩余时间不足以让单次购买的货物数量增加(让k增加)时,跳出循环,特殊处理完剩下的时间。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    int p,a,b,q,c,d,m,t;
    cin>>p>>a>>b>>q>>c>>d>>m>>t;

    while(1){
        if(t<a+b) break;

        int now=m/p;//计算当前情况,一次来回能背多少货,假设当前时间充足
        if(now==0) break;

        int nowget=now*(q-p);//每次买now个货物,一次赚到的钱
        int round=((now+1)*p-m+nowget-1)/(nowget);//这个now可以跑多少个来回
        int time=a*now+b+c*now+d;//交易1轮的时间
        int tottime=round*time;//round轮交易总时间

        if(t>=tottime){
            t-=tottime;
            m+=round*nowget;
        }
        else{  
            m+=t/time*nowget;
            t%=time;
            if(t<b+d) break;
            t-=b+d;
            m+=t/(a+c)*(q-p);
            break;
        }
    }
    cout<<m<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}

H. Stop the Castle 阻止城堡

二分图最大匹配,建图

先按行找,找到所有行上的可以相互攻击到的点对

再找到所有列上的可以相互攻击到的点对

普通情况是,增加一个障碍,可以消除一个点对

最有情况是,增加一个障碍,可以消除两个点对

而一个障碍可以消除两个点对的条件是,这两个点对,必须一个在行另上一个在列上,且这两个点对的连线会相交。

如何构建二分图?

设左边的点集是L,表示所有在行上的点对,二分图右边的点集是R,表示所有在列上的点对。

而连边的条件是,这两个点对的连线相交,此时可以连一条从L到R的点对。

用匈牙利算法计算二分图最大匹配即可

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;

void solve(){
    int n,m;
    cin>>n;

    vector<pii> c(n+10);
    for(int i=1;i<=n;i++){
        cin>>c[i].first>>c[i].second;
    }

    cin>>m;
    vector<pii> o(m+10);
    for(int i=1;i<=m;i++){
        cin>>o[i].first>>o[i].second;
    }

    vector<pii> L,R;//L:行上相邻可攻击的城堡对 ,R:列上的
    L.push_back({-1,-1});
    R.push_back({-1,-1});

    //枚举所有城堡,找同一行上的可攻击对
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            auto [a,l]=c[i];
            auto [x,r]=c[j];
            if(a!=x) continue;
            if(l>r) swap(l,r);

            if(l+1==r){
                cout<<-1<<endl;
                return;
            }
            bool flag=1;
            //检查是否已有障碍挡住
            for(int k=1;k<=m;k++){
                if(o[k].first==a && l<o[k].second && o[k].second<r) flag=0;
            }
            //检查是否已有城堡挡住
            for(int k=1;k<=n;k++){
                if(k==i || k==j) continue;
                if(c[k].first==a && l<c[k].second && c[k].second<r) flag=0;
            }
            if(flag) L.push_back({i,j});
        }
    }

    //枚举所有城堡,找同一列上的可攻击对
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            auto [l,a]=c[i];
            auto [r,x]=c[j];
            if(a!=x) continue;
            if(l>r) swap(l,r);

            if(l+1==r){
                cout<<-1<<endl;
                return;
            }
            bool flag=1;
            //检查是否已有障碍挡住
            for(int k=1;k<=m;k++){
                if(o[k].second==a && l<o[k].first && o[k].first<r) flag=0;
            }
            //检查是否已有城堡挡住
            for(int k=1;k<=n;k++){
                if(k==i || k==j) continue;
                if(c[k].second==a && l<c[k].first && c[k].first<r) flag=0;
            }
            if(flag) R.push_back({i,j});
        }
    }

    vector<vector<int>> g(n*3);

    for(int i=1;i<L.size();i++){
        for(int j=1;j<R.size();j++){
            auto [u,v]=L[i];
            auto [a,b]=R[j];
            int xi=c[u].first, yj=c[a].second;

            int xl=c[a].first, xr=c[b].first;
            if(xl>xr) swap(xl,xr);

            int yl=c[u].second, yr=c[v].second;
            if(yl>yr) swap(yl,yr);

            if(xi>xl && xi<xr && yj>yl && yj<yr){
                g[i].push_back(j);
            }
        }
    }

    vector<int> vis(R.size()+10),match(R.size()+10);

    auto dfs=[&](auto dfs,int u)->bool {
        for(auto v:g[u]){
            if(vis[v]) continue;
            vis[v]=1;
            if(!match[v] || dfs(dfs,match[v])){
                match[v]=u;
                return 1;
            }
        }
        return 0;
    };

    for(int i=1;i<L.size();i++){
        fill(vis.begin(),vis.end(),0);
        dfs(dfs,i);
    }

    vector<pii> ans;
    vector<int> ok(210);

    //把匹配上的点对加进ans
    for(int j=1;j<R.size();j++){
        if(match[j]!=0){
            int i=match[j];
            ok[i]=1;//标记已经对行中的第i个处理完了
            ans.push_back({c[L[i].first].first, c[R[j].first].second});
        }
    }

    //处理剩余的行上的点对
    for(int i=1;i<L.size();i++){
        if(ok[i]==0){
            auto [u,v]=L[i];
            int l=c[u].second,r=c[v].second;
            if(l>r) swap(l,r);
            ans.push_back({c[u].first,l+1});
        }
    }

    for(int i=1;i<R.size();i++){
        if(match[i]==0){
            auto [u,v]=R[i];
            int l=c[u].first,r=c[u].first;
            if(l>r) swap(l,r);
            ans.push_back({l+1,c[u].second});
        }
    }

    cout<<ans.size()<<endl;
    for(auto [u,v]:ans){
        cout<<u<<" "<<v<<endl;
    }
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int ct=1;
    cin>>ct;

    while(ct--){
        solve();
    }
    return 0;
}
posted @ 2025-05-07 22:16  LYET  阅读(240)  评论(0)    收藏  举报