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;
}

浙公网安备 33010602011771号