accuber

导航

2025.5.30 JYU_GDCPC训练赛2(A、F、G、I、K、H题)

本次比赛以虚拟方式参与2025年中国大学生程序设计竞赛全国邀请赛(东北)暨第十九届CCPC东北地区大学生程序设计竞赛比赛链接

赛时情况

这次我们比较快的过掉了G题和K题,然后去做I题(当时题目意思理解错了,导致到最后都没写出来),随后我们发现A题可以类似上次训练赛中的中位数的做法(枚举每一个中位数)来写,但是一些边界问题处理红温了,艰难做出来然后发现F题其实是一个很简单的模拟,但是我们到最后才发现忘了时针移动的度数不是整的,会和分针同步,但是时间不够了,难受。


G题链接

image

G题大意及做法:

签到题,这里度数该顶点连接的边的条数,我们可以记录每个点出现的次数,也就是每个点的度数,最后将这些度数为奇数的点相互连接添加一条边就可以使之都为偶数。注意:度数为奇数的点的个数一定是偶数个的
赛时代码如下:

点击查看代码
int n,m,k,kk,u,v;
int cnt[N];
void solve(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>u>>v;
        cnt[u]++,cnt[v]++;
    }
    int res=0;
    vector<int>ans;
    for(int i=1;i<=n;i++){
        if(cnt[i]%2){
            res++;
            ans.pb(i);
        }
    }
    if(res){
        cout<<res/2<<endl;
        for(int i=1;i<(int)ans.size();i+=2){
            cout<<ans[i-1]<<" "<<ans[i]<<endl;
        }
    }else cout<<res<<endl;
}

K题链接

image

K题大意及做法:

俞同学想的很快!
1.最终第一排一定包括每个颜色,我们只需要找到每个颜色放哪里操作次数最少;
2.有的颜色第一行没有出现,,但是可以通过染色道具变成需要的颜色,最后操作次数就按n来计算;
3.题目说对于每一个从1到m的数字k,在整张表格中,数字 k 出现的总次数正好是n次。(后面的输入保证写错了)所以m个染色道具是够用的。
既然这样,我们可以记录每种颜色在每一列列首出现的连续次数最多,出现次数最多的颜色列只需要n-cnt次操作就可以变成单色列,如果这种颜色没出现,就需要n次。最后判断然后把这些次数加起来就行了
赛时代码:

点击查看代码
int n,m,k,kk,u,v;
int a[N];
int g[2020][2020];
 
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>g[i][j];
        }
    }
    for(int j=1;j<=m;j++){
        int color=g[1][j],cnt=1;
        for(int i=2;i<=n;i++){
            if(color==g[i][j]) cnt++;
            else break;
        }
        if(cnt>a[color]) a[color]=cnt;
    }
    int res=0;
    for(int i=1;i<=m;i++){
        res+=(n-a[i]);
    }
    cout<<res<<endl;
}

I题链接

image

I题大意及做法:

首先,不要被“不能同时包含互为敌对关系的两点的路径”中的“同时”误导了,比如(1,3)犯冲,我们当时就认为说的是不能存在1-3这条路,但是可以存在1-4-3这种通过转接的形式走,样例也完全没有表现出来。所以其实s和t犯冲就不能到达,只需要一个map就可以解决;
其次,因为左侧每个点只与右侧一个点犯冲,所以当n>=3时,只要s和t不犯冲就一定可以通过其他点转接到达;当n=2时需要特判s和t是否在同侧,如果在同侧显然无法通过其他点转接。
补题代码如下:

点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define int long long
#define LL long long
#define lowbit(x) (x&-x)
#define endl "\n"
using namespace std;
const int N=1e6+10,M=1010,mod=1e9+7,INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-6;
int dx[]= {1,0,-1,0},dy[]= {0,1,0,-1};
int n,m,k,kk,u,v;
int a[N];

void solve(){
    int s,t;
    cin>>n>>s>>t;
	/*
	只加一个map就能过了。。。
	map<PII,bool>mp;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		mp[{a[i],i}]=1,mp[{i,a[i]}]=1;
	}
	if(mp[{s,t}]){
		cout<<"No"<<endl;
		return;
	}
	*/
    if(n>2)cout<<"Yes"<<endl;
    else if(n==1){
    	if(s==t)cout<<"Yes"<<endl;
    	else cout<<"No"<<endl;
	}else {
		if(a[1]==3){
			if(s>t)swap(s,t);
			if(s==t)cout<<"Yes"<<endl;
			else if(s==1&&t==4)cout<<"Yes"<<endl;
			else if(s==2&&t==3)cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}else {
			if(s>t)swap(s,t);
			if(s==t)cout<<"Yes"<<endl;
			else if(s==1&&t==3)cout<<"Yes"<<endl;
			else if(s==2&&t==4)cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}
	}
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
    cin>>_;
    while(_--) solve();
}

A题链接

image

A题大意及做法:

赛后看到标程是二分+gcd的ST表,但是我们赛时也通过另一种方法写出来了:
1.思路和上场训练赛的L题类似,其实是双指针的方法,只是上场的是从两端向中间缩,这次的是从中心往两边扩,只要左边的区间nod[l-1].a>gcd,并且扩展后gcd不变,那么就可以扩,而向右边扩是只需要扩展后gcd不变就可以扩了;(主要是为了避免重复的情况,比如1 2 1 2 1 2,第一个数字的时候扩到了整个数组,然后第二个数扩展的时候如果没有限制也可以扩到整个数组,这样就会有重复计算的区间,所以我们加上一个大小限制)
2.如果上一个数等于当前这个数,他们的gcd肯定相等,所以我们可以合并把他们合并起来,事实上如果不加上这个合并的话确实会超时;
3.在扩散完之后是通过枚举区间每个点计算答案的,左边点的个数对于同一个区间只能计算一次(第二次就不能接触到左区间的数了,避免重复计算),右端点不变,但右边点的个数随着点的右移改变,最后左边点的个数*右边点的个数就是答案了(乘法原理,注意点的个数是区间长加1)
赛时代码:

点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
//#define int long long
#define LL long long
#define lowbit(x) (x&-x)
#define endl "\n"
using namespace std;
const int N=1e6+10,M=1010,mod=1e9+7,INF=0x3f3f3f3f;
//const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-6;
int dx[]= {1,0,-1,0},dy[]= {0,1,0,-1};
int n,m,k,kk,u,v;
struct node{
	int a;
	int num;
}nod[N];

void solve(){
    cin>>n;
    int idx=2,x;
    cin>>x;
    nod[1]={x,1};
    for(int i=2;i<=n;i++){
    	cin>>x;
		if(nod[idx-1].a==x) nod[idx-1].num++;
		else nod[idx++]={x,1};
	}
	LL ans=0;
	for(int i=1;i<idx;i++){
		int gcd=nod[i].a;
		int l=i,r=i;
		int cntl=0,cntr=0;
		while(l-1>0&&nod[l-1].a>gcd&&__gcd(gcd,nod[l-1].a)==gcd)
			cntl+=nod[l-1].num,l--;
		while(r+1<idx&&__gcd(gcd,nod[r+1].a)==gcd)
			cntr+=nod[r+1].num,r++;
			
//		cout<<cntl<<" "<<cntr<<endl;
		for(int j=1;j<=nod[i].num;j++){
			LL ll;
			if(j==1) ll=cntl;
			else ll=0;
			LL rr=cntr+nod[i].num-j;
			ans+=(ll+1)*(rr+1);
		}
	}
	cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
//    cin>>_;
    while(_--) solve();
}

F题链接

image

F题大意及做法:

先计算时间复杂度,遍历(x1,y1)到(x2,y2)最多也只有12*60=720次,一共也只有\(10^4\)次查询,O(720 * \(10^4\))不会超时,所以我们考虑枚举区间内每一分钟对应的时间,这里还要注意的是,最初和最后的时针的位置是根据分针的位置改变的,在过程中说了可以只动一根(时针可以不随分针改变),我们只需要计算初始分针和时针对应最终分针和时针的移动的角度,取最小的一个作为答案即可(我们被I题耗了太久了,最后这题边界情况没分清,可惜了)
补题代码如下:

点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
//#define int long long
#define LL long long
#define lowbit(x) (x&-x)
#define endl "\n"
using namespace std;
const int N=1e6+10,M=1010,mod=1e9+7,INF=0x3f3f3f3f;
//const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-6;
int n,m,k,kk,u,v;

void solve(){
    int x0,y0,x1,y1,x2,y2;
    cin>>x0>>y0>>x1>>y1>>x2>>y2;
    double dy=y0*6,dx=x0*30+y0*0.5;//时针的初始度数
    int hh,mm;
	double minv=INF;
    for(int i=x1*60+y1;i<=x2*60+y2;i++){
    	int h=i/60,m=i%60;
        double x=h*30+m*0.5;//时针的最终度数
		//总共移动的度数,我这里只是细分了顺逆移动的情况,
		//逆时针移动用360来减很妙
    	double res1=fabs(x-dx)+fabs(m*6-dy);

        double res2=360-fabs(x-dx)+fabs(m*6-dy);

        double res3=fabs(x-dx)+360-fabs(m*6-dy);

        double res4=360-fabs(x-dx)+360-fabs(m*6-dy);

        double res=min(min(res1,res2),min(res3,res4));
    	if(res<minv){
    		hh=h,mm=m;
    		minv=res;
		}
	}
	cout<<hh<<" "<<mm<<endl;
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int _=1;
    cin>>_;
    while(_--) solve();
}

H题链接

image

H题大意及做法:

这题过的人不多,我们硬磕的太入神了,没看到这题,这题是一道二分,结论也不会太难推(我一开始还想怎么填进去,但是只根据大的先填到小的里面无法保证犯冲遗留的问题)
1.总共要填进去的人数Sumb也是固定的,左侧一个城市的人数和右侧与之犯冲的城市人数之外的人数也是固定的(p[a[i]]=Sumb-b[i],每个城市的最多能收的人数为p[i],因为有犯冲的人不可能接受到,可以预处理),而每个城市最多可以填的人数随着mid改变(\(\left \lfloor \frac{mid}{Ci} \right\rfloor\)),并且随着mid增大,最多可以填的人数变多,答案是单调的,所以我们可以二分解决
2.我们二分的是能不能将右侧的所有人填到左边除了与它犯冲的城市之外的地方,这里的结论是:\(\sum_{1}^{n} min(p[i],\left \lfloor \frac{mid}{Ci} \right \rfloor )\)>=Sumb时,互相之间你能放我不能放的,我们也可以放你不能放的,这样各城市彼此之间就一定可以填满
补题代码如下:

点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define int long long
#define LL long long
#define lowbit(x) (x&-x)
#define endl "\n"
using namespace std;
const int N=1e6+10,M=1010,mod=1e9+7,INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-6;
int n,m,k,kk,u,v,sumb;
int a[N],b[N],c[N],p[N];
bool check(int x){
	int res=0;
	for(int i=1;i<=n;i++){
		res+=min(p[i],x/c[i]);
	}
	return res>=sumb;
}
void solve(){
    cin>>n;
	sumb=0;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i],sumb+=b[i];
	for(int i=1;i<=n;i++)cin>>c[i];
	
	for(int i=1;i<=n;i++)
		p[a[i]]=sumb-b[i];
	
	int l=0,r=1e18;
	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),cout.tie(0);
    int _=1;
    cin>>_;
    while(_--) solve();
}

不经一翻彻骨寒,怎得梅花扑鼻香。 ——宋帆

posted on 2025-05-31 17:25  accuber  阅读(136)  评论(0)    收藏  举报