zoukankan      html  css  js  c++  java
  • 集训模拟赛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

  • 相关阅读:
    POJ 3041 Asteroids 最小点覆盖 == 二分图的最大匹配
    POJ 3083 Children of the Candy Corn bfs和dfs
    POJ 2049 Finding Nemo bfs 建图很难。。
    POJ 2513 Colored Sticks 字典树、并查集、欧拉通路
    POJ 1013 Counterfeit Dollar 集合上的位运算
    POJ 2965 The Pilots Brothers' refrigerator 位运算枚举
    无聊拿socket写的100以内的加法考试。。。
    POJ 1753 Flip Game
    初学socket,c语言写的简单局域网聊天
    汇编语言 复习 第十一章 标志寄存器
  • 原文地址:https://www.cnblogs.com/Vocanda/p/13236199.html
Copyright © 2011-2022 走看看