2025“钉耙编程”中国大学生算法设计春季联赛(3)

1005 修复公路

用并查集维护连通块个数-1

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
using lll=__int128_t;
using ull=unsigned 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(0);
            fa.resize(n+1);
            sz.resize(0);
            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> a(n+10);

    DSU dsu(n);

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

        if(i-a[i]>=1){
            dsu.unite(i,i-a[i]);
        }

        if(i+a[i]<=n){
            dsu.unite(i,i+a[i]);
        }
    }

    cout<<dsu.setCount-1<<endl;
}

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

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

1001 数列计数

因为要求累乘起来是奇数,所以每一项都要是奇数。

所以对每一个a,统计每一个C(a,b) 结果是奇数的b的个数,乘起来即可

根据lucas定理,C(a,b) === C(a/2,b/2) * C(a%2,b%2) (mod 2)

所以如果让C(a,b) mod 2 = 1,则要求所有的C(a%2,b%2)结果都是1。

所以要求对a和b二进制的每一位x,y, 满足C(x,y)=1 此时符合条件的是 x=1y=1, x=1y=0, x=0y=0。

这个条件和 a & b = b 等价

所以对a从低到高枚举每一位,如果这一位是1,则ans*=2, 最后将 从1到n的所有ans乘起来即可

上面是在不考虑L的情况下。

如果考虑L:

当L >= a时,无事发生。因为在上面的统计中,b最大就到a。

当L < a时 要删去那些b,满足b>L

具体做法:

从高到低枚举a和L的二进制位x和y,如果x==y,则无事发生,continue到下一位

如果x=0,y=1,则从这一位开始往后考虑,L已经大于a了,break掉

如果x=1,y=0, 则b的这一位不能是1,所以要给ans-= 2^cnt。cnt是a的x这一位后面1的个数 (不包括x)

数学题有思路了代码很好写

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

void solve(){
    int n;
    cin>>n;
    vector<int> a(n+10),l(n+10);

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

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

    int ans=1;

    //C(a,b) -> a & b = b
    for(int i=1;i<=n;i++){
        int val=a[i],tmp=1;

        while(val){
            if(val&1) tmp*=2;
            val/=2;
        }

        if(l[i]>=a[i]){
            ans*=tmp;
            ans%=mod;
            continue;
        }

        int cnt=__popcount(a[i]);

        for(int j=31;j>=0;j--){
            bool fl=0,fa=0;
            if((l[i]>>j) & 1)  fl=1;
            if((a[i]>>j) & 1)  fa=1;

            if(fa) cnt--;

            if(fl==fa) continue;
            else if(fl) break;
            else{
                tmp-=1<<cnt;
            }
        }
        ans*=tmp;
        ans%=mod;
    }
    cout<<ans<<endl;
}

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

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

1010 选择配送

对于两点(x1,y1), (x2,y2),曼哈顿距离:D:|x1-x2|+|y1 -y2|

这个算式可以转换成:D:max{ (x1-y1)-(x2-y2),(x1+y1)-(x2+y2),(x2-y2)-(x1-y1),(x2+y2)-(x1+y1) }

所以只需要找到所有客户最大和最小的x2+y2,x2-y2即可

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

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

    vector<pii> a,b;
    for(int i=0;i<n;i++){
        int x,y;
        cin>>x>>y;
        a.push_back({x,y});
    }

    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        b.push_back({x,y});
    }

    int mx1=-inf,mx2=-inf,mn1=inf,mn2=inf;
    //mx1:max(x2+y2),mx2:max(x2-y2)
    //mn1:min(x2+y2),mn2:min(x2-y2)

    for(auto [x,y]:a){
        mx1=max(mx1,x+y);
        mx2=max(mx2,x-y);
        mn1=min(mn1,x+y);
        mn2=min(mn2,x-y);
    }

    int ans=inf;

    for(auto [x,y]:b){
        int tmp=-inf;
        tmp=max(tmp,x+y-mn1);
        tmp=max(tmp,x-y-mn2);
        tmp=max(tmp,mx1-x-y);
        tmp=max(tmp,mx2-x+y);
        ans=min(ans,tmp);
    }
    cout<<ans<<endl;
}

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

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

1003 拼尽全力

显然是要贪心,但要考虑好怎么贪。初见时感觉像24年河北省赛C题

对m个能力,开m个优先队列,把每个公司的第i个能力要求放进第i个优先队列中

单独计算每项你能力,如果第i项能力满足第k个公司的第i项能力要求,则把这个公司的第i项能力要求从优先队列中取出,同时给cnt[k]++;

如果cnt[k] == m,则第k个公司已通过,此时可以给小x的能力加上第k个公司的提升量。

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

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

    vector<int> a(m+10);
    for(int i=1;i<=m;i++){
        cin>>a[i];
    }

    priority_queue<pii,vector<pii>,greater<pii>> q[m+10];
    vector<vector<int>> w(n+10,vector<int>(m+10));

    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int val;
            cin>>val;
            q[j].push({val,i});//第i家公司的第j项能力值val
        }
        for(int j=1;j<=m;j++){
            cin>>w[i][j];
        }
    }

    vector<int> st(n+10);
    int cnt=0;

    while(1){
        bool f=0;
        for(int i=1;i<=m;i++){
            while(q[i].size()){
                auto [val,idx]=q[i].top();
                if(val<=a[i]){
                    q[i].pop();
                    st[idx]++;
                    if(st[idx]==m){
                        f=1;
                        cnt++;
                        for(int j=1;j<=m;j++){
                            a[j]+=w[idx][j];
                        }
                    }
                }else{
                    break;
                }
            }
        }
        if(!f) break;
    }

    if(cnt==n){
        cout<<"YES\n";
    }
    else{
        cout<<"NO\n";
    }

}

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

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

1009 部落冲突

如果没有操作三,则就是一个简单的并查集模板题。

但是对操作三,如果每次暴力的去交换两个集合,肯定会超时,要在野蛮人->部落的映射关系中,再加一层,维护每个部落所在的位置,每次通过部落的位置找到部落。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
using lll=__int128_t;
using ull=unsigned 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,q;
    cin>>n>>q;
    DSU dsu(n);

    vector<int>r(n+1),h(n+1),pos(n+1);//r:第i个人所在的部落 h:位置对应的部落,pos:部落所在的位置
    
    iota(r.begin(),r.end(),0);
    iota(h.begin(),h.end(),0);
    iota(pos.begin(),pos.end(),0);

    while(q--){
        int ch,a,b;
        cin>>ch;
        if(ch==1){
            cin>>a>>b;
            dsu.unite(pos[a],pos[b]);
        }
        else if(ch==2){
            cin>>a>>b;
            r[a]=dsu.find(pos[b]);
        }
        else if(ch==3){
            cin>>a>>b;
            swap(pos[a],pos[b]);
            swap(h[pos[a]],h[pos[b]]);
        }
        else{
            cin>>a;
            int p=dsu.find(r[a]);
            cout<<h[p]<<endl;
        }
    }
}

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

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

1004 弯曲筷子

首先要排序。

如果要选第 i 个筷子,则跟第 i 个配对的筷子一定是第 i-1 个或 i-2 个,不可能是第 i-3 个或更远。

如果选的是第 i-2 个,则第 i-1 个肯定不会再选。

比如a,b,c,d, 如果选ac,则一定不选b,否则就是ac,bd,显然比ab,cd要差。

所以,如果第 i-1 个必选,则不可以选第 i 个和第 i-2 个这种组合

点击查看代码
#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;

//f[i][0/1]: 前i根筷子中,选上所有必选的,且选/不选第i根筷子的最小值

int cal(int a,int b){
    return (a-b)*(a-b);
}


void solve(){
    int n,m;
    cin>>n>>m;
    
    vector<pii> a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i].first;
    }

    for(int i=1;i<=m;i++){
        int idx;
        cin>>idx;
        a[idx].second=1;
    }

    sort(a.begin()+1,a.end());

    vector<array<int,2>> f(n+1);

    f[1][0]=0;
    f[1][1]=inf;

    f[2][1]=cal(a[1].first,a[2].first);
    if(a[1].second==1) f[2][0]=inf;
    else f[2][0]=0;

    //f[i][0]=f[i-1][1], f[i-2][0]+a[i-1].x+a[i-2].x
    //f[i][1]=f[i-1][0]+a[i-1].x+a[i].x  if(a[i-1].y==0) = f[i-2][0]+a[i-2].x+a[i].x

    for(int i=3;i<=n;i++){
        if(a[i-1].second==0){
            f[i][0]=min(f[i-1][0],f[i-1][1]);
            f[i][1]=min(f[i-1][0]+cal(a[i].first,a[i-1].first), f[i-2][0]+cal(a[i].first,a[i-2].first));
        }
        else{
            f[i][0]=f[i-1][1];
            f[i][1]=f[i-1][0]+cal(a[i].first,a[i-1].first);
        }
        // f[i][0]=min(f[i-1][1],f[i-1][0]);

        // f[i][1]=min(f[i-2][1]+cal(a[i].first,a[i-1].first), f[i-2][0]+cal(a[i-2].first,a[i].first));
        
        // if(!a[i-2].second) f[i][1]=min(f[i][1],f[i-2][0]+cal(a[i].first,a[i-1].first)); 

        // cout<<"i: "<<a[i].first<<" "<<f[i][0]<<" -- "<<f[i][1]<<endl;
    }

    if(a[n].second) cout<<f[n][1];
    else cout<<min(f[n][1],f[n][0]);

    cout<<endl;
}

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

    while(ct--){
        solve();
    }
    return 0;
}
posted @ 2025-05-02 17:09  LYET  阅读(46)  评论(0)    收藏  举报