2025.5.24 JYU_GDCPC训练赛1(A、I、L、G、F题)
本次比赛以虚拟方式参与2025ICPC 国际大学生程序设计竞赛全国邀请赛(武汉)比赛链接
赛时情况:
1.先跑去做了A题  [A题链接]

A题大意:
给定一个长度为n的数组a,及q次操作,每次操作让我们把第p个数字变成[l,r]范围内的数字k,若变成k,那么所需的时间增加|a[p]-k|,并且每次操作后不能与其他矛盾(即如果让你把a[p]变到[1,5],又让你变到[6,7],所有建议无法得到满足,输出-1)
A题做法:复杂度 O(n + q)
我们只要用一个PII数组,先初始化为-1,记录符合条件的区间,更新该区间的最大l和最小r,并判断是否符合要求,出现矛盾(l>r)直接标记,最后判断a[i]是离左区间近(l-a[i])、还是离右区间近(a[i]-r)、或者在区间内(0),或者不用修改(0),将1-n遍历一遍把各情况的值加起来就可以了
代码如下:
点击查看代码
#include <bits/stdc++.h>
#define int long long 
#define PII pair<int,int>
#define endl '\n'
using namespace std;
const int N=110,M=2010;
int n,m,k;
int a[N];
struct Q{
	int l,r;
}tr[N];
void solve(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	memset(tr,-1,sizeof tr);
	int l,r,p;
	bool flag=0;
	while(k--){
		cin>>p>>l>>r;
		if(tr[p].l==-1) tr[p].l=l,tr[p].r=r;//第一次更新区间
		else {/第二次更新区间取最大l和最小r
			int minr=min(r,tr[p].r);
			int maxl=max(l,tr[p].l);
			if(minr>=maxl) tr[p].l=maxl,tr[p].r=minr;
			else flag=1;//新区间l>r了,注意这里不能直接输出,因为还要读入剩下的内容
		} 
	}
	if(flag) cout<<-1<<endl;
	else{
		int ans=0;
		for(int i=1;i<=n;i++){
			int l=tr[i].l,r=tr[i].r;
			if(l==-1) continue;
			if(a[i]<l||a[i]>r) ans+=min(abs(l-a[i]),abs(r-a[i]));
		} 
		cout<<ans<<endl;
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int t=1;
	cin>>t;
	while(t--)solve();
} 
2.然后就是I题了   I题链接

I题大意
“我们说一个整数x是这个网格的"宾果整数",如果至少满足以下两个条件之一:至少有一行,该行单元格中的所有整数都小于或等于 x;至少有一列,该列单元格中的所有整数都小于或等于 x 。”
构造题,给定一个正方形矩阵的长度n,和一个最小宾果整数k,我们要构造一个由整数1~n*n组成的矩阵,且符合一列或一行最大整数小于等于k,且其他行或列的宾果整数不能比k小,如果无法构造出来,则输出-1
I题做法
我们只需要将第一行构造成k,k-1,k-2,…,k-n+1,然后在位置(i,i)填上(k+i-1),其他位置按照剩下的数字顺序填完就可以了,这样第一行是满足宾果整数k的,位置(i,i)到位置(i,n)都会满足(k+i-1),位置(i,i)到位置(n,i)都会满足(k+i-1),即影响是呈十字架型的。最后我们要判断构造出来的数组会不会超限(k<n,第一排都无法满足最大小于k),(k+n-1>n*n,预先填入的数字超限,无法构造),最后按题目要求输出即可
//可惜赛时我们填的是(i,i-1)而不是(i,i),这样最后一列会出问题,WA了一发。从第2列开始填肯定没有问题,因为第一列的第一个数已经满足k了
代码如下:
点击查看代码
#include <bits/stdc++.h>
#define int long long 
#define PII pair<int,int>
#define endl '\n'
using namespace std;
const int N=110,M=2010;
int n,m,k;
int a[55][55],st[3010];
struct Q{
	int l,r;
}tr[N];
void solve(){
	cin>>n>>k;
	if(k<n||k+n-1>n*n){
		cout<<"No"<<endl;
		return;
	}
	memset(st,0,sizeof st);
	memset(a,0,sizeof a);
	int flag=0;
	for(int i=1,j=k;i<=n;i++,j--){//填第一行
		a[1][i]=j;
		st[j]=1;
	}
	int idx=1;
	for(int i=2;i<=n;i++){//填位置(i,i)
		a[i][i]=k+i-1;
		st[k+i-1]=1;
	}
	//将剩下的填完
	for(int i=2;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(a[i][j]==0){
				while(st[idx])idx++;
				a[i][j]=idx;
				st[idx]=1;
			}
	cout<<"Yes"<<endl;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int t=1;
	cin>>t;
	while(t--)solve();
} 
3.接下来就是L题,这题我们卡了好久		L题链接

L题大意
“如果最小元素和最大元素的平均值等于中位数,我们说长度k的整数序列C=c1,c2,⋯,ck是good。长度k序列的中位数定义为序列中的第⌈k/2⌉个最小元素,其中⌈x⌉是大于或等于x的最小整数。”
“给定一个整数序列 A=a1,a2,⋯,an,计算其最长的good子序列的长度。回想一下,序列B是A的子序列,可以通过从中删除一些元素或没有元素来获得B,而无需更改其余元素的顺序。”
1.注意这里的中位数不是原序列正中间的数,而是排序后的第\(⌈k/2⌉\)个最小元素;
2.我们要求的子序列是可以不连续的,中位数也是排序后的,所以我们可以直接排序a数组;
3.对于每个中位数,它的两倍需要等于最小的数+最大的数
L题 做法 O(n^2)
为了找到最长的满足(中位数的两倍等于最小的数+最大的数)的子序列,我们可以先枚举每一个中位数,对于每一个中位数,我们可以初始化l=1,r=n,随后根据(中位数的两倍与最小的数+最大的数的关系,具体看代码)将区间缩小,找到第一次相等的地方,i-l即为中位数左边有多少个数,r-i为右边有多少个数,根据该题中位数的定义,我们可以根据左右数字个数分配最大长度,我们用nn来表示min(i-l,r-i),当左边的满足的数较多时,最大长度为2nn+1(左右等长,i在中间);当相等时,最大长度为2nn+1,(左右等长,i在中间);当右边的满足的数较多时,最大长度为2*nn+2(长度为偶数,i的左边有nn个数,右边有nn+1个数,中位数靠左),枚举完中位数后最大的长度即为ans
因为超时问题WA了5发,最后俞同学是我们的大功臣!
代码如下:
点击查看代码
#include <bits/stdc++.h>
//#define int long long 
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second 
#define endl '\n'
using namespace std;
const int N=110,M=2010;
int n,m,k;
int a[3010],st[3010];
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	if(a[1]==a[n]){
		cout<<n<<endl;
		return;
	}
	int ans=1;
	for(int i=1;i<=n;i++){//枚举每一个中位数
		int l=1,r=n;//初始化为整个区间大小,随后将区间缩小
		//a[l]+a[r]如果太大了,把右区间减小可以使得a[l]+a[r]变小;如果太小了,将左区间增大可以使得a[l]+a[r]变大,第一次相等就跳出
		while(l<r){
			if(a[l]+a[r]>2*a[i]) r--;
			else if(a[l]+a[r]<2*a[i]) l++;
			else break;
		}
		if(a[l]+a[r]==2*a[i]){
			int nn=min(r-i,i-l);
			if(i-l>=r-i) ans=max(ans,2*nn+1);
			else ans=max(ans,2*nn+2);
		}
	}
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int t=1;
	cin>>t;
	while(t--)solve();
} 
4.最后去写G题,可惜我们赛时不知道如何去重,只想到了容斥原理的方法,没想到也可以用dp递推来求。(根号分治)G题链接

G题大意
给定 n×m 的网格,每个格子有个颜色a[i,j]。考虑所有从左上角走到右下角的路径,每次只能往右或往下走,路径的价值是它经过的不同颜色种数。求所有路径价值之和。
G题做法
1.考虑贡献法:格子(i,j) 的贡献,是从 (1,1) 走到(i,j)的过程中,没有经过颜色a[i,j]的路径数。如果把同种颜色的格子看成障碍,就是从(1,1)走到(i,j)不经过障碍的路径数。
2.对于每个点的贡献,有两种计算方法:容斥做法:O(\(k^2\)) 其中k是障碍数;DP做法: O(n*m)
若只使用其中一种做法,极端情况下的复杂度都是O(\((n*m)^2\)),所以我们要根据数据范围进行根号分治
补题如下:
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long
#define LL long long
#define pb push_back
#define fi first
#define se second
#define endl '\n'
using namespace std;
const int N=1e5+10,M=1010,mod=998244353;
const int INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,k;
int fact[N],infact[N];
int qmi(int a,int b){
	int c=1;
	while(b){
		if(b&1) c=c*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return c;
}
void init(){
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++) fact[i]=fact[i-1]*i%mod;
	infact[N-1]=qmi(fact[N-1],mod-2);
	for(int i=N-2;i>=1;i--) infact[i]=infact[i+1]*(i+1)%mod;
}
int C(int a, int b){
	if(b<0||a<b) return 0;
	return fact[a]*infact[b]%mod*infact[a-b]%mod;
}
bool cmp(PII a,PII b){
	if(a.first!=b.first) return a.first<b.first;
	return a.second<b.second;
}
int fun1(vector<PII>& a,int c){
	//此时的a对应的是mp[c],mp[c]存储了值为c的点下标,len为该值的数量 
	int len=a.size();
	vector<int> f(len,0);
	//f[i]数组是来计算(1,1)到第i个点的不重复路径的数量
	int res=0;
	 
	for(int i=0;i<len;i++){//设第i个该类型的点为(l,r),第j个为(ll,rr) 
		//C(l+r-2,l-1)为从(1,1)到(l,r)不管前面是否重复的路径总数 
		f[i]=C(a[i].fi+a[i].se-2,a[i].fi-1);
		for(int j=0;j<i;j++){
			//(ll<=l&&rr<=r)点j应该在点i的矩形内才会影响到该点
			if(a[j].fi<=a[i].fi&&a[j].se<=a[i].se){ 
				/*
				公式分析:f[i]={f[i]-f[j]*C(l+r-ll-rr,l-ll)%mod+mod}%mod;
				1. C(l+r-ll-rr,l-ll): 计算i点到j点有多少条路径
				2. f[j]*C(l+r-ll-rr,l-ll):前半段路径数*后半段路径数即为(1,1)到(ll,rr)的路径数
				3. f[i]-f[j]*C(l+r-ll-rr,l-ll): 用i点前的符合条件的j点来更新f[i],
				   最后获得的就是(1,1)到(l,r)的路径数量了 
				*/
				f[i]=(f[i]-f[j]*C(a[i].fi+a[i].se-a[j].fi-a[j].se,a[i].fi-a[j].fi)%mod+mod)%mod;
			}
		}
		//num=f[i]*C(n+m-i-j,n-i)%mod;与(公式分析2)同理,num为点(l,r)对(1,1)到(n,m)的路径数贡献 
		int num=f[i]*C(n+m-a[i].fi-a[i].se,n-a[i].fi)%mod;
		res=(res+num)%mod;
	}
	
	return res;
}
int fun2(vector<vector<int>>& g,int c){
	//dp来递推去掉所有值为c的点后的路径总数 
	vector<vector<int>> dp(n+10,vector<int>(m+10,0));
	dp[1][1]=(g[1][1]==c)?0:1;
	//数字三角形模型计算路线总数 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(g[i][j]==c) continue;//去掉该值为c的点 
			if(i==1&&j==1) continue;
			//该位置的贡献是由它上面和左边递推得到的 
			dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod; 
		}
	}
	//(1,1)到(n,m)的路径总数 减去 去掉所有值为c的点后的路径总数 即为值为c的点对路径数的贡献 
	int res=(C(n+m-2,n-1)-dp[n][m]+mod)%mod;
	return res;
}
 
void solve(){
	cin>>n>>m;
	vector<vector<int>> a(n+10,vector<int>(m+10,0));//原图 
	set<int> v;//不同整数的集合 
	map<int,vector<PII>> mp;//不同整数的集合 与相同种类整数的下标  
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			v.insert(a[i][j]);
			mp[a[i][j]].pb({i,j});
		}
	} 
	
	for(auto& it:v) sort(mp[it].begin(),mp[it].end(),cmp);//将各点按大小排序 
	
	int ans=0;
	int mid=sqrt(n*m);//(根号分治)
	
	for(auto it:v){
		int x,len=mp[it].size();
		
		if(len<=mid) x=fun1(mp[it],it);//当点数较少时用容斥原理来算 
		else x=fun2(a,it);//点数较多时用dp来递推去掉所有值为it的点后的路径总数 
		
		ans=(ans+x)%mod;//各点到终点的路径之和即为答案 
	}
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	init();
	
	int _=1;
	cin>>_;
	while(_--) solve();
	
	return 0;
}
5.赛时看了一眼F题,以为就是dp,没想到很好的方法,因为数据范围较大,感觉二分也不太可行,过的人也不多,就没有去想了,赛后看到师兄的方法发现也不会特别难写。所以说一般出不来题的时候不一定要硬嗑多人过的题(但是这么多人过肯定是有道理的,是数据弱了还是想复杂了?也有可能就是自己的问题//虽然一般情况下少人过也肯定不好写就是了

F题大意
F题链接
有n种物品,第i种有ai个,每个的重量是\(2^{bi}\)。现在把所有物品放进m个承重相同的背包,问背包的最小承重k。我们的目标是找到最小的k值,使得所有物品能够被放入这m个背包中,并且每个背包内的总重量不超过k。特别注意,每个物品必须放置到恰好一个背包中。
F题做法
贪心。从1e9到0考虑每个p,考虑所有重量大等于\(2^p\)的物品,并把每个物品拆成重量为\(2^p\)的若干份,然后动态维护剩余的可用容量。通过将剩余容量转换为不同重量单位下的等效值,避免直接模拟或二分查找。
定义当前剩余容量为 $ k $ (注意:这里的 $ k $ 并非原始的背包容量,而是通过不断转化得到的相对容量),对于每种重量为 $ 2^b $ 的物品,我们需要判断当前剩余容量是否能够容纳这类物品。具体步骤如下:
- 判断当前剩余容量:检查现有的相对容量k是否足以容纳所有重量为\(2^b\)的物品。
- 新增背包需求:如果当前剩余容量不足以容纳这些物品,则需要计算需新增多少个背包,并确定这些新增背包能提供的总容量。
- 容量转换:将新增背包所提供的总容量转化为下一个较小的重量单位(即 $ 2^{b_{2}}$),其中\(2^{b_{2}}< 2^b\))下的等效容量。这一步骤相当于将剩余的空间“压缩”到下一个更小的重量单位上。
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define int long long
#define LL long long
#define lc (p<<1)
#define rc (p<<1|1)
#define lowbit(x) (x&-x)
#define endl "\n"
using namespace std;
const int N=1e6+10,M=1010,mod=998244353,INF=0x3f3f3f3f;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,k,kk,u,v;
int qpow(int a,int b){
    int res=1;
    a%=mod;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
void solve(){
    cin >> n >> m;
    map<int, int> mp; //mp[i]存储每种重量为2^i的物品总数
    for (int i = 0; i < n; ++i) {
        int a,b;
        cin>>a>>b;
        mp[b]+=a; // 合并相同重量为2^b的物品数量
    }
    vector<int> keys;//存储所有不同的b值
    for (auto &it: mp)keys.push_back(it.first);
    sort(keys.begin(), keys.end(), greater<int>());
    
    if (keys.empty()) { //特判没有物品的情况
        cout << "0\n";
        return;
    }
    int ans = 0,k = 0,pre = keys[0];
    // k为当前剩余容量,pre为上一个处理的 b 值
    for (int b:keys) { //遍历每个不同的重量单位 b
        if (k > 0) { // 如果还有剩余容量,则进行容量单位转换
            int diff = pre - b; // 计算前后两个b值之间的差值
            // 如果差值太大或转换后会溢出,则直接退出循环
            if (diff >= 50 || k > (1LL << (50 - diff))) {
                break;
            }
            // 将上一个单位下的容量转换为当前单位下的等效容量
            k *= (1LL << diff);
        }
    
        pre = b; // 更新前一个 b 值
        // 判断当前容量是否足够容纳该类物品
        if (k >= mp[b]) {
            k -= mp[b]; // 足够的话直接扣除已使用的容量
            continue;
        }
        // 容量不足时,需要新增背包
        mp[b] -= k,k=0; // 把可以放的当前重量为2^b的物品放进去,然后清空当前容量
        // 计算所需背包数量(向上取整)
        int x = (mp[b] + m - 1) / m;
        // 累加贡献:x 个背包 × 2^b 的价值
        ans = (ans + x % mod *qpow(2, b) % mod) % mod;
        // 更新剩余容量,用于下一个更小的重量单位
        k = x * m - mp[b];
    }
    cout<<ans<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
    int t=1;
    cin>>t;
    while(t--) solve();
}
不经一翻彻骨寒,怎得梅花扑鼻香。 ——宋帆
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号