CSP-S模拟赛3总结

T1

题面

虚假的一般图最小匹配

时间限制:1秒        内存限制:256M

题目描述

给定 n 个点的完全图,第 i 个的点权为 a​i​​ 。点 i 和 j 之间的边的边权为∣a​i​​−a​j​​∣。

小可想求出这张图的最小 m 匹配,最小 m 匹配的定义如下:

从图中选出 2*m 个点和 m 条边,每一条选中的边都恰好连接两个选中的点,每一个选中的点都恰好被一条选中的边相连。

匹配的权值定义为所有选中的边的权值和。

最小 m 匹配是找出如上描述中,匹配权值最小值。

输入描述

第一行:输入两个整数n,m,表述如题。

第二行:输入n个数字,表示每个点的点权。

输出描述

最小 m 匹配的值。

输入样例1

  1. 4 1
  2. 2 4 7 3

输出样例1

  1. 1

输入样例2

  1. 8 3
  2. 9 2 3 12 11 7 6 5

输出样例2

  1. 3

数据描述

20%的数据:1≤n≤10

40%的数据:1≤n≤100

另有10%的数据:1≤m≤50

另有20%的数据:1≤a​i​​≤5000

对于100%的数据:1≤2∗m≤n≤5000,1≤a​i​​≤10​e9​​
A

初次思路

将原数组排序,两两分割为相邻的实数对,取差值,将差值再次排序,将该序列中前m个差值相加得到答案

为啥不对?

排序后的数组不仅仅可以相邻两两分割,也可以胡乱分,考虑不全面

复盘思路

先将数组排序,随后

DP,启动

dp[ i ][ j ]:在前i个数中选j对数的最小代价

对于每一个i、j,有选/不选两种状态

                                         👇少选了一对的状态是j-1

        选:代价=dp[ i - 2 ][ j - 1 ]+(a[ i ]-a[ i - 1 ])👈增加上的权值

                                👆因为i代表的是对的数量,所以应该-2 

        不选:代价=dp[ i ][ j - 1 ]👈少选了一对的状态是j-1

以上两种取min即可

code

#include<bits/stdc++.h>
using namespace std;
long long a[5005],dp[5005][2505];
int main(){
	//freopen("match.in","r",stdin);
	//freopen("match.out","w",stdout); 
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	sort(a+1,a+1+n);
	for(int i=1;i<=m;i++){
		dp[0][i]=dp[1][i]=(long long)0x3f3f3f3f;
	}
	for(int i=2;i<=n;i++){
	    dp[i][0]=0;
		for(int j=1;j<=m;j++){
			dp[i][j]=dp[i-1][j];
			dp[i][j]=min(dp[i][j],dp[i-2][j-1]+abs(a[i]-a[i-1]));
		}
	}
	cout<<dp[n][m];
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

T2

题面

序列删除与填充

时间限制:2秒        内存限制:256M

题目描述

给定一个长度为 n 的正整数序列,其中有些位置为空,另一些位置则已经填好了数字。

你需要删去序列中的一个位置(可以是空的位置,也可以不是),然后在剩余的位置中填入数字,满足:

1.所有的数字均为 [1,n] 的数字,且两两不同,数据保证已经填充的数字满足该条件。
2.该序列的字典序最小。

输入描述

第一行:输入一个正整数T,表示多组输入的组数。

对于每组测试样例:

​ 第一行:输入一个正整数 n,表示序列长度。

​ 第二行:输入n个整数,代码该序列a,如果 a​i​​=0,表示这个位置为空,尚未填入数字。

输出描述

对于每组样例,输出一行n-1个数字,表示经过删除和填入之和的最小字典序的序列。

输入样例

  1. 2
  2. 3
  3. 2 0 3
  4. 4
  5. 4 0 0 2

输出样例

  1. 1 3
  2. 1 3 2

输入样例

  1. 2
  2. 7
  3. 3 0 4 0 1 0 2
  4. 6
  5. 1 3 4 5 2 6

输出样例

  1. 3 1 4 5 6 2
  2. 1 3 4 2 6

数据描述

对于20%的数据:2≤n≤8,1≤T≤5

对于30%的数据:2≤n≤100,1≤T≤5

对于50%的数据:2≤n≤2000,1≤T≤10

对于另外5%的数据:不存在空的位置

对于另外15%的数据:空的位置数量≤50≤50

对于100%的数据:2≤n≤2∗10​5​​,2≤∑n≤10​6​​,1≤T≤100

B

初次思路

对于(对于另外5%的数据:不存在空的位置)的部分,用

剩下的枚举,抠20分走人(其实没抠到)

复盘思路

比(su)较(per)细节的贪心,考虑先删后填

删:

先记录每个数的位置及后缀最小值,备用

将未出现的数放入优先队列中,备用

每遇到一个为零的数,如果后面有比优先队列中待填的数更小的,就在他后面删一个数填到这里

每遇到一个数的下一个数不为零的,是拐点则删

每遇到一个数的下一个数为零的,该数比优先队列中待填的数更大则删(本质上也是拐点)

填:

将剩余优先队列中待填的数填入即可

code

#include<bits/stdc++.h>
#define MAXN 100
#define ull unsigned long long int
#define ll long long int
using namespace std;
int t,n,a[1000005],pos[1000005],vis[100005],tmp[105],ans[105],fl;

int main(){
	scanf("%d",&t);
	int T=t;
	while(t--){
		scanf("%d",&n);
		memset(vis,0,sizeof vis);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			vis[a[i]]++;
			pos[a[i]]=i;
		}
		for(int i=1;i<=n;i++){
			if(vis[i]==0){
				qu.push(i);
			}
		}
		bmin[n+1]=inf;
		for(int i=n;i>=1;i--){
			bmin[i]=//后缀最小值
		}
		int P=n;
		for(int i=1;i<n;i++){
			if(!a[i]){
				if(bmin[i]>qu.top()){
					a[i]=qu.top();
					qu.pop();
				}
				else{
					a[i]=bmin[i],P=pos[a[i]];
					break;
				}
			}
			if(a[i+1]){
				if(a[i]>a[i+1]){
					P=i;
					qu.push(a[i]);
					break;
				}
			}
			else{
				if(a[i]>qu.top()){
					P=i;
					qu.push(a[i]);
					break;
				}
			}
		}
		for(int i=1;i<=n;i++){
			if(a[i]||i==P){
				continue;
			}
			a[i]=qu.top();
			qu.pop();
		}
		//cout
		printf("\n");
	}
	return 0;
}


T3

:(

T4

:)

骗到15分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值