集训模拟赛7

Posted vocanda

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集训模拟赛7相关的知识,希望对你有一定的参考价值。

前言

今天超级大水题,只是可惜了我之前的关路灯题解,白写了那么详细,结果考试还是没想起来……下边来分析一下今天的一些题目。

No.1 侦查

题目描述

身为火影的纲手大人,当然不能眼睁睁地看着斑等一伙人胡作非为,木叶的全体忍者都信任身兼死神与忍者双重身份的你,相信你可以拯救世界,但是作为资深忍者的卡卡西同学向纲手大人提出建议,他想考验你作为忍者的基本能力---侦查。
斑在木叶周围建设了许多聚点,每一个聚点内都会藏有斑的手下。有些聚点是可以连通的。阴险的斑把所有连通的聚点作为他的一个基地,以便发动对木叶的总攻。卡卡西会告诉你每个聚点的藏敌人数和聚点的连通情况,他让你找出包含聚点数最多的基地,与包含敌人数目最多的基地。

输入格式

(n,m)((n) 为据点数,聚点编号为(1...n,m)为边数,(n,m le 500));
接下的一行为(n)个整数,为每个聚点的藏敌人数,用空格相隔,敌数(le 1000)
接下来(m)行,每行两个数(u,v),表示(u)(v)有边相连。

输出格式

第一行为包含聚点数最多的基地内的聚点编号,以升序输出。
第二行为藏敌人数最多的基地内的聚点编号,以升序输出。
注意:若求得的两个基地包含的聚点数相同或藏敌数相同,则输出字典序最小的。

样例

样例输入

12 11
10 11 2 3 4 5 1 1 1 1 1 1
1 2
2 3
1 3
4 5
5 6
6 7
8 9
9 12
11 12
10 11
8 10

样例输出

8 9 10 11 12
1 2 3

分析

其实没啥好分析的,直接双向建边(Tarjan)求强联通分量,然后记录一下每个分量的人数和大小就行

代码



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int head[maxn];
vector<int>scc[maxn];
struct Node{
	int v,next;
}e[maxn<<1];
int c[maxn];
int n,m,val[maxn];
int siz[maxn];
int sum[maxn],jl1[maxn],jl2[maxn];
int dfn[maxn],low[maxn],num,tot,cnt;
int sta[maxn],top;
int vis[maxn];
void Add(int x,int y){
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
}
void Tarjan(int x){
	dfn[x] = low[x] = ++num;
	vis[x] = 1;
	sta[++top] = x;
	for(int i=head[x];i;i=e[i].next){
		int v = e[i].v;
		if(!dfn[v]){
			Tarjan(v);
			low[x] = min(low[x],low[v]);
		}
		else if(vis[v]){
			low[x] = min(low[x],dfn[v]);
		}
	}
	if(dfn[x] == low[x]){
		cnt++;
		int y;
		while(1){
			y = sta[top--];
			c[y] = cnt;
			siz[cnt]++;
			sum[cnt]+=val[y];
			vis[y] = 0;
			scc[cnt].push_back(y);
			if(x == y)break;
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d",&val[i]);
	}
	for(int i=1;i<=m;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		Add(x,y);
		Add(y,x);
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i])Tarjan(i);
	}
	int jlsum,jljud;
	int maxsum = 0;
	int maxjud = 0;
	for(int i=1;i<=cnt;++i){
		if(maxsum<sum[i]){
			jlsum = i;
			maxsum = sum[i];
		}
		if(maxjud<scc[i].size()){
			jljud = i;
			maxjud = scc[i].size();
		}
	}
	int dd = 0,ff = 0;
	for(int i=0;i<scc[jlsum].size();i++){
		jl1[++dd] = scc[jlsum][i];
	}
	for(int i=0;i<scc[jljud].size();i++){
		jl2[++ff] = scc[jljud][i];
	}
	sort(jl1+1,jl1+dd+1);
	sort(jl2+1,jl2+ff+1);
	for(int i=1;i<=ff;i++){
		printf("%d ",jl2[i]);
	}
	printf("
");
	for(int i=1;i<=dd;++i){
		printf("%d ",jl1[i]);
	}
	printf("
");
	return 0;
}

No.2 借书

问题描述

(Dilhao)一共有(n)本教科书,每本教科书都有一个难度值,他每次出题的时候都会从其中挑两本教科书作为借鉴,如果这两本书的难度相差越大,(Dilhao)出的题就会越复杂,也就是说,一道题的复杂程度等于两本书难度差的绝对值。

这次轮到(ldxxx)出题啦,他想要管(Dilhao)(m)本书作为参考去出题,(Dilhao)想知道,如果(ldxxx)(Dilhao)给出的(m)本书里挑选难度相差最小的两本书出题,那么(ldxxx)出的题复杂程度最大是多少?

输入格式

第一行是(n)(m)

接下来的(n)行,每行一个整数(a_i)表示第(i)本书的难度。

输入格式

一个整数为(ldxxx)出的题复杂程度的最大值。

输入样例

6 3

5

7

1

17

13

10

输出样例

7

样例解释

(Dilhao)给了(ldxxx)难度为(1,10,17)的三本书,(ldxxx)挑选难度为(10)(17)的两本书,出题复杂度为(7)

如果(Dilhao)给出其他任何三本书,其中的两本书难度差的最小值都小于(7),所以(ldxxx)出题最大的复杂程度为(7)

数据说明

对于 (30\\%)的数据: (2le nle 20)

对于 (60\\%)的数据: (2le nle 1000)

对于 (100\\%)的数据: (2le nle 100000)(2 le mle n)(0le a_i le 1000000000)

分析

看到标志性的最小中的最大,(有得题是最大中的最小)肯定就是二分答案了,主要就是判断。
因为是要求出差值的最大,所以我们就使用差分数组(cf)来记录排序后的两两之间的难度差。然后判断一下能否选出来(m)个就好了。下边看代码

代码



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int Max,a[maxn];
int n,m;
int cf[maxn];//差分数组
bool check(int x){
	int cnt = 0,ch = 0;//cnt为几本书,ch为最大差值
	for(int i=1;i<=n;++i){//枚举每一本书
		ch += cf[i];//累加差值
		if(ch>=x){//差值比当前扫描到的答案大就书加一,差值置为0
			cnt++;
			ch = 0;
		}
	}
	if(cnt>=m-1)return true;//最终能够选出m个书就为真
	return false;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		Max = max(Max,a[i]);//记录最大值
	}
	sort(a+1,a+n+1);//排序
	for(int i=1;i<=n;++i){//记录差值
		cf[i] = a[i+1]-a[i];
	}
	int l=0,r=Max;//从0到最大值二分
	while(l<=r){
		int mid = (l+r)>>1;
		if(r-l == 1){//只差一就判断一下,不然会死循环
			if(check(r) == true){//r能行就变成把l变成r,因为最后答案为l
				l = r;
			}
			break;
		}
		if(check(mid) == true){//中间符合要求就向右转移
			l = mid;
		}
		else r=mid;//否则向左
	}
	printf("%d
",l);
}

No.3 搜城探宝

题目

(zhclk)已经坚信自己就是传说中的有缘人,于是,带着梦想,带着希冀,带着勇气,来到了神迹,寻找……

如下图,神迹的城堡是一个树形的结构,共有 (n)间屋子。每间屋子都有一把锁,并且每间屋子最多可以到另外的两个屋子里(它是一棵二叉树)。在城堡的每个房间都存在着不同的宝藏。现在 (zhclk) 站在城堡的大门口((1) 号屋子门口)拥有 (k)把万能钥匙,可以打开任意一把锁,但每把钥匙只能用一次,钥匙是拔不出来的。

问题哪有那么简单……,(Zhclk)还有一个传送门,可以在任何时候带他去任何一间屋子,但传送门也只能 使用一次。

地图上画出了宝藏的分布,只有获得最大价值的宝藏 (zhclk)的目的才能实现。
技术图片

Input

第一行:两个数 (n)(k)。为城堡的屋子总数和你拥有的万能钥匙数。
第二行到第 (n)行:每行两个数 (x_1)(x_2),为树上的 (n?1) 条边。(树保证以 (1)为根节点)。
(n+1)行:(n) 个数,第 (i) 个数为房间 (i) 的宝藏价值 (v_i)

Output

一个数,为最大宝藏价值 (maxv)

Sample Input

8 4
1 2
1 3
2 4
2 5
3 6
3 7
6 8
2 5 1 4 6 1 1 10

Sample Output

27

Hint

用钥匙依次开(1,2,4,5)号房间,再用传送门去 (8) 号房间,(27=2+5+6+4+10)

数据范围: (nle 20)

分析

题目中u有个传送门,所以裸的树规肯定是要爆掉的。假设使用传送门从 (x)(y),那么就有下边的结论:

(y)仅限于没有访问过的节点,当然更不是 (x)的祖先。可以将 (y)从整棵树中独立出来求值,并且这样做是正确的。
可以规定传送到 (y)之后不能再往祖先方向走,也就是把这部分断开。在 (x)节点使用传送门相当于回到 (x) 的任意一个祖先之后再使用传送门。因此,可以建立一个虚根(n+1)使用传送门,再令(1)(n+1)的左儿子,那么整棵树(除去(y))的值就都好计算了。
既然要把 (y)独立出去计算其值,那么可以令(y)(n+1)的右儿子。

有了以上结论,很容易就可以开展树规了。
首先令 (n+1)的左儿子为 (1),然后从 (2)(n)枚举 (y) (也就是传送到的节点),把 (y) 设置为 (n+1)的右儿子,对树(n+1)进行一次树形(dp)
其中还有许多小细节,代码注释见

代码



#include<bits/stdc++.h>
using namespace std;
const int maxn = 20+5;
int n,k,a[maxn],ans = 0;
int fa[maxn],ls[maxn],rs[maxn];
int dfs(int x,int sum){//树形dp
	if(x==0)return 0;//没有点就返回0
	if(sum==1)return a[x];//就一个钥匙了就返回权值,因为我们开一个传送门的时候加了一个边,也就是加了一个钥匙。
	int now=0;//答案值
	for(int i=0;i<sum;++i)
		now=max(now,dfs(ls[x],i)+dfs(rs[x],sum-1-i)+a[x]);//从左儿子和右儿子递归
	return now;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		if(!ls[x])ls[x] = y;//第一个点为左儿子
		else rs[x] = y;
		fa[y] = x;//记录每个点的父亲节点
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
	}
	ls[n+1]=1;//虚根n+1
	for(int i=2;i<=n;++i){
		rs[n+1] = i;//一一枚举传送门到哪个点,让他为右儿子
		if(ls[fa[i]] == i){//i父亲的左儿子是i就把左边断开,然后树归
			ls[fa[i]] = 0;
			ans = max(dfs(n+1,k+2),ans);
			ls[fa[i]] = i;//这次递归完以后再连上
		}
		else {//右儿子跟左儿子一样
			rs[fa[i]] = 0;
			ans = max(ans,dfs(n+1,k+2));
			rs[fa[i]] = i;
		}
	}
	cout<<ans<<endl;//输出最大值
}

No.4 MM不哭

这个题的名字真的是没谁了,也不知道谁想的。原题(虽然当时我忘了),具体分析见之前我的博客:关路灯。

题目

在一个数轴上,有 (n)(MM) 在哭泣(5555~一直哭)。

(tcboy)也在这个数轴上,并恰好看到了这一幕,由于每个(MM)哭都会让(tcboy)损失一定的(rp),于是(tcboy)有必要去安慰她们(真命苦啊T.T)。

开始时,(tcboy)站在 (k)(MM)的旁边。

现在知道第 (i)(MM) 哭泣每秒钟会使(tcboy) 降低 (w_i)(rp) (单位 (rp)/(s))。而 (tcboy)的行走速度很慢,只有(1m)/(s)(tcboy) 安慰 (MM)的方式很特别,不需要花费时间。请计算(tcboy)安慰完所有 (MM),会消耗掉的 (rp)的最小值。

Input

第一行包含一个整数 (N,2le Nle 1000),表示 (MM)的数量。
第二行包含一个整数 (V,1le Vle N),表示开始时 (tcboy) 站在几号 (MM)的旁边。
接下来的 (N)行中,每行包含两个用空格隔开的整数 (D)(W),用来描述每个 (MM),其中(0le Dle 1000,0le Wle 1000)(D) 表示 (MM) 在数轴上的位置(单位: (m)),(W) 表示每秒钟会使 (tcboy) 降低(W)(rp)

Output

输出只有一行:一个整数,即消耗 (rp)之和的最小值。
结果不超过 (10^9)

Sample Input

4
3
2 2
5 8
6 1
8 7

Sample Output

56

分析

之前博客链接:https://www.cnblogs.com/Vocanda/p/13184264.html











































以上是关于集训模拟赛7的主要内容,如果未能解决你的问题,请参考以下文章

7.4集训模拟赛7

集训模拟赛7

7.2集训模拟赛(莫名其妙的比赛......)

7.1集训模拟赛5(......)

2017冬季24集训模拟-4.排座椅

集训之6-26模拟赛一