【C++】JOISC 2020 Day4原题+翻译+解析+代码

T1 Capital City

原题

CSDN下载:https://download.csdn.net/download/Ljnoit/12263973

链接

LOJ-3280
UOJ-510
vjudge

翻译

题目描述

在 JOI 的国度有 N N N 个小镇,从 1 1 1 N N N 编号,并由 N − 1 N−1 N1 条双向道路连接。第 i i i 条道路连接了 A i A_i Ai B i B_i Bi 这两个编号的小镇。

这个国家的国王现将整个国家分为 K K K 个城市,从 1 1 1 K K K 编号,每个城市都有附属的小镇,其中编号为 j j j 的小镇属于编号为 C j C_j Cj 的城市。每个城市至少有一个附属小镇。

国王还要选定一个首都。首都的条件是该城市的任意小镇都只能通过属于该城市的小镇到达。

但是现在可能不存在这样的选址,所以国王还需要将一些城市进行合并。对于合并城市 x x x y y y ,指的是将所有属于 y y y 的小镇划归给 x x x 城。

你需要求出最少的合并次数。

输出格式

输入第一行两个整数 N , K N,K N,K,为小镇和城市的数量。

接下来的 N − 1 N−1 N1 行,每行两个整数 A i A_i Ai, B i B_i Bi,描述了 N − 1 N−1 N1 条道路。

扫描二维码关注公众号,回复: 11650340 查看本文章

再接下来的 N N N 行,每行一个整数 C j C_j Cj,表示编号为 j j j 的小镇属于编号为 C j C_j Cj 的城市。

输出格式

输出一行一个整数为最少的合并次数。

样例输入 1

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

样例输出 1

1

样例说明 1

你可以对城市 1 和 3 进行合并,然后选定 1 为首都,因为最初任何城市都无法作为首都。总花费为 1。

这个样例满足子任务 1,2,4。

样例输入 2

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

样例输出 2

1

样例说明 2

这个样例满足子任务 1,2,3,4。

样例输入 3

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

样例输出 3

2

样例说明 3

这个样例满足子任务 1,2,4。

数据范围

对于 100% 的数据,1≤N≤2×105,保证:

  • 1 ≤ K ≤ N 1≤K≤N 1KN
  • 1 ≤ A i , B i ≤ N ( 1 ≤ i ≤ N − 1 ) 1≤A_i,B_i≤N(1≤i≤N−1) 1Ai,BiN(1iN1)
  • 从任何一个小镇出发都能到达其他任何小镇;
  • 1 ≤ C j ≤ K ( 1 ≤ j ≤ N ) 1≤C_j≤K(1≤j≤N) 1CjK(1jN)
  • 对于每一个 k ( 1 ≤ k ≤ K ) k(1≤k≤K) k(1kK),存在一个 j ( 1 ≤ j ≤ N ) j(1≤j≤N) j(1jN),使得 C j = k C_j=k Cj=k

详细子任务及附加限制如下表所示:

子任务编号 附加限制 分值
1 N ≤ 20 N≤20 N20 1
2 N ≤ 2000 N≤2000 N2000 10
3 每个小镇最多可通过公路与两个小镇直接相连 30
4 无附加限制 59

解析

法一:
倍增。

如果颜色i的虚树上有颜色j的点,i->j连一条边。
建出图后缩强联通分量,出度为0且点数最小的分量就是答案。
用倍增或者树链剖分优化这个建图即可做到 O ( n l o g n ) O(n log n) O(nlogn)

法二:
点分治。

考虑点分治,每次强制选分治中心,求选它的答案。
选它的答案可以用bfs实现,一开始加入分治重心的颜色到队列里,每次取队列头的颜色,枚举这个颜色的所有点x,开始跳父亲,把路过的颜色加入队列,直到跳到一个已经在虚树上的点。

注意是只在分治子树内bfs,因为如果出去了,就说明要经过更高的分治重心,那么在那个时候就统计答案了。

这个复杂度也是 O ( n l o g n ) O(n log n) O(nlogn),常数更小。

代码

倍增算法:

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>

#define R                  register int
#define re(i,a,b)          for(R i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

namespace IO {
    
    
	#include <cctype>

	template <typename T>
	inline void read(T &x){
    
    
		x=0; 
		char c=0; 
		T w=0;  
		while (!isdigit(c)) w|=c=='-',c=getchar();  
		while (isdigit(c)) x=x*10+(c^48),c=getchar();  
		if(w) x=-x;  
	}
	
	template <typename T>
	inline void write(T x) {
    
    
	    if(x<0) putchar('-'),x=-x;
	    if(x<10) putchar(x+'0');
	        else write(x/10),putchar(x%10+'0');
	}
	
	template <typename T>
	inline void writeln(T x) {
    
    
	    write(x);
	    putchar('\n');
	}
} 

using IO::read;
using IO::write;
using IO::writeln;

const int N=2e5+5;

int n,k,clk,scc,ans;
int dfn[N],c[N],mn[N],rr[N],idfn[N],f[N],low[N],st[N],tp,sz[N],bel[N];
bool ins[N],ban[N];
vector<int> e[N],v[N];

inline void cmin(int &x,int y) {
    
    
	y < x ? x= y : 0;
}

void dfs(int x) {
    
    
    idfn[dfn[x]=++clk]=x;
    for(int i=0,y; i<e[x].size(); i++)
        if(!dfn[y=e[x][i]]) f[y]=x,dfs(y);
    rr[x]=clk;
}

void tarjan(int x) {
    
    
    dfn[x]=low[x]=++clk;
	st[++tp]=x;
	ins[x]=1;
    for(int i=0,y; i<v[x].size(); i++)
        if(!dfn[y=v[x][i]]) tarjan(y),cmin(low[x],low[y]);
        	else if(ins[y]) cmin(low[x],dfn[y]);
    if(dfn[x]==low[x]) {
    
    
        int y;
		scc++;
        do {
    
    
			y=st[tp--];
			ins[y]=0;
			bel[y]=scc;
			sz[scc]++;
		} while(y!=x);
    }
}

int main() {
    
    
	read(n);
	read(k);
	ans=k;
    for(int i=1; i<n; i++) {
    
    
    	int x,y;
    	read(x);
    	read(y);
        e[x].push_back(y);
		e[y].push_back(x);
    }
    dfs(1);
	memset(mn,0x3f,sizeof(mn));
    for(int i=1; i<=n; i++) {
    
    
		scanf("%d",&c[i]);
		cmin(mn[c[i]],dfn[i]);
	}
    for(int i=1; i<=n; i++) 
		if(dfn[i]>rr[idfn[mn[c[i]]]]) 
			mn[c[i]]=0;
    for(int i=1; i<=n; i++) 
		if(dfn[i]^mn[c[i]])
			v[c[i]].push_back(c[f[i]]);
    memset(dfn,0,sizeof(dfn));
	clk=0;
	for(int i=1; i<=k; i++) 
		if(!dfn[i]) tarjan(i);
    for(int i=1; i<=k; i++) 
		for(int j=0; j<v[i].size(); j++)
			if(bel[i]^bel[v[i][j]])
				ban[bel[i]]=1;
    for(int i=1; i<=scc; i++)
		if(!ban[i]) ans=MIN(ans,sz[i]);
    writeln(ans-1);
    return 0;
}

T2 Legendary Dango Maker

原题

CSDN下载:https://download.csdn.net/download/Ljnoit/12263973

链接

LOJ-3281
UOJ-511

翻译

题目描述

你是一位糯米团子大师,现在你正在串团子。

你面前有一个 R 行 C 列的网格,每格里面放着一个粉、白、绿三色之一的团子。你每次会横向、竖向或斜向选三个连续的团子并将他们按顺序串到一起。其中,按顺序指竖直方向的团子只能以上、中、下或下、中、上的顺序串,而不能以中、上、下或中、上、下的顺序串,其他顺序以此类推。这样,你就获得了一串团子。

当且仅当一串团子的颜色顺序是绿、白、粉或粉、白、绿时,我们把这串团子称为美丽串,请求出串取最多的美丽串的方法。

输入格式

本题共六组数据。

数据的第一行两个以空格分隔的整数 R , C R, C R,C

接下来 R R R 行每行一个仅含字符 P , W , G P,W,G PWG 的字符串 D i D_i Di,第 j j j 个字符 D i D_i Di, j j j 表示第 i i i j j j 列的团子颜色。

输出格式

输出 R R R 行,每行一个长度为 C C C 的仅含字符 PWG|-\/ 的字符串 S i S_i Si,第 j j j 个字符 S i , j S_{i,j} Si,j 表示第 i i i j j j 列团子的串法。

如果 S i , j S_{i,j} Si,j|,表示你想把该格及其上、下方一格的团子串起来。
如果 S i , j S_{i,j} Si,j-,表示你想把该格及其左、右方一格的团子串起来。
如果 S i , j S_{i,j} Si,j\,表示你想把该格及其左上、右下方一格团子串起来。
如果 S i , j S_{i,j} Si,j/,表示你想把该格及其右上、左下方一格团子串起来。
否则, S i , j S_{i,j} Si,j 应与 D i , j D_{i,j} Di,j 相同。

样例输入 1

3 4
PWGP
WGPW
GWPG

样例输出 1

P-GP
WGP|
G-PG

样例解释 1

本样例中,你做了三串美丽的团子。

注意,在本样例中的 W G P 不是一种美丽的团子串。

样例输入 2

3 4
PWWP
WWWW
PGGP

样例输出 2

PWWP
W\/W
PGGP

样例解释 2

本样例中,你做了两串美丽的团子。

数据范围

对于 100% 的数据,有 3≤R, C≤500,|Di|=C, Di,j∈P, W, G。

评分方式

本题的得分以以下方式计算。

对于每个测试点,我们定义四个参数 S, X, Y, Z。其中,S 表示该测试点的分值。各测试点的参数值如下:

测试点 S X Y Z
1 15 44000 47000 47220
2 15 39000 41700 41980
3 15 45000 51000 51390
4 15 18000 19000 19120
5 20 43000 48200 48620
6 20 44000 46000 46500

对于每个测试点,令 N 表示你提交中所做出来的美丽团子串数,你的分值由以下方式计算得出:

如果 N < X N<X N<X,你的分数为 0 0 0
如果 X ≤ N < Y X≤N<Y XN<Y,你的分数为 N − X 2 ( Y − X ) × S \frac{N−X}{2(Y−X)}×S 2(YX)NX×S
如果 Y ≤ N < Z Y≤N<Z YN<Z,你的分数为 ( 1 2 + N − Y 2 ( Z − Y ) ) × S (\frac{1}{2}+\frac{N−Y}{2(Z−Y)})×S (21+2(ZY)NY)×S
如果 Z ≤ N Z≤N ZN,你的分数为 S S S
你本题的分数为各测试点分数之和四舍五入到整数后的结果。

但是,如果你的输出无效,例如按照你的输出中的 |,-,\,/ 字符无法做出美丽的团子串,或者 P,W,G 与输入不一致,或者输出格式错误,将被判为 0 分。

可视化工具

本题附加文件中提供了一个可视化工具,可以将输入数据和输出数据可视化。

如果要使用可视化工具,请用浏览器打开 visualizer.html 并选取文件(注:此处应该指输入、输出文件)。注意,可视化工具不检查你所选取文件的格式是否正确。如果格式不正确,可能无法正常执行可视化操作。另外,R 和 C 过大时也不会执行可视化操作。

解析

模拟退火+随机化

把所有可能的团子串都取出来,如果两个团子串不可能同时出现(即它们所用的团子有重复),那么就在它们之间连一条边。这样我们构建出一张图。

我们就是要找这张图的一个大小尽可能大的独立集。

考虑模拟退火。初始所有点都没选进独立集,每次随机一个不在独立集内的点 u,然后试图把它加进独立集。当然加的同时要从集合中去掉与它相邻的点。

就像常见的模拟退火一样,如果新的解优于旧的解或者以一定的概率接受一个较劣的解,那么就把新的解保留。

不过对于同一个温度,可能需要多随机几个 u。而且由于解的大小比较大,温度的变化率要非常非常接近 1,例如 0.999999。当然,温度的初始值不一定要那么大(毕竟你算一个新的解的时间就够长的了)。

然后跑模拟退火就行了,很快就可以把前 4 个点过掉。然而我第 5,6 个点都差一点点(把分数四舍五入就到 100 了的那种),非常自闭。后来我又把代码魔改了一通,使得它能够在一个差一点点的解的基础上继续退火。后来换了几个种子终于把这两个点过掉了,而且答案正好是 Z。(并不知道神仙粉兔是如何退火出来更优的解的)

在跑退火的时候,有时我想要直接用 Ctrl-C 强制让它停下来,但这样就不会输出当前跑出的最优方案了。我的解决方法是定义一个 struct,并给它定义一个析构函数。这样在程序终止的时候,它会自动执行输出方案的部分。

答案

答案有点大,就不传上来了。

T3 Treatment Project

原题

CSDN下载:https://download.csdn.net/download/Ljnoit/12263973

链接

LOJ-3282
UOJ-512
vjudge

翻译

题目描述

JOI 国有 N N N 个房屋,并从 1 1 1 N N N 编号。这些房屋沿一条直线升序排列。每个房屋有一个居民住在里面。住在编号为 x x x 的房屋里的居民用居民 x x x 表示。

最近,新冠病毒出现了,并且所有居民都被感染了。为了解决这个问题,有 M M M 个治疗方案被提出。第 i ( 1 ≤ i ≤ M ) i (1≤i≤M) i(1iM) 个治疗方案的花费为 C i C_i Ci。如果执行计划 i i i,则会发生以下事件:

在第 T i T_i Ti 天的晚上,如果居民 x x x 满足 L i ≤ x ≤ R i L_i≤x≤R_i LixRi,且他感染了新冠病毒,那么他就会被治愈。
病毒按如下方式传染相邻的居民:

如果在某天的早晨,居民 x ( 1 ≤ x ≤ N ) x (1≤x≤N) x(1xN) 被病毒感染,那么在同一天的中午,居民 x − 1 x−1 x1(如果 x ≥ 2 x≥2 x2)和居民 x + 1 x+1 x+1(如果 x ≤ N − 1 x≤N−1 xN1)就会被感染。
一个已经被治愈的居民可以再次被病毒感染。

你是 JOI 国的首相,你需要选取某些方案,使得满足以下条件:

条件:在所有被选中的方案全部执行后,没有居民感染病毒。
在某一天可以执行多个计划。

写一个程序,给定房屋和治疗计划的信息,求出能否满足以上条件,若满足,求出最小可能花费。

输入格式

从标准输入中读取以下内容:

第一行两个整数 N , M N,M N,M

接下来 M M M 行,每行四个整数 T i , L i , R i , C i T_i,L_i,R_i,C_i Ti,Li,Ri,Ci,表示一个治疗方案。

输出格式

输出一行到标准输出。如果条件无法满足,则输出 −1,否则输出最小总花费。

样例输入 1

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

样例输出 1

7

样例说明 1

在样例 1 中,你可以按照如下方式执行计划:

在第二天的晚上,执行计划 1,之后居民 5,6,7,8,9,10 被治愈了,现在只有居民 1,2,3,4 被病毒感染;
在第三天的中午,居民 5 被病毒感染。现在居民 1,2,3,4,5 被病毒感染;
在第四天的中午,居民 6 被病毒感染。现在居民 1,2,3,4,5,6 被病毒感染;
在第四天的晚上,执行计划 5,之后居民 1,2,3 被治愈了,现在只有居民 4,5,6 被病毒感染;
在第五天的中午,居民 3,7 被病毒感染。现在居民 3,4,5,6,7 被病毒感染;
在第五天的晚上,执行计划 3,之后居民 3,4,5,6,7 被治愈了,现在没有居民被感染了。
执行计划 1,3,5 的总花费为 7。并且没有比这个花费更少且满足条件的方案,所以输出 7。

样例输入 2

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

样例输出 2

-1

样例说明 2

因为无法满足条件,所以输出 −1。

样例输入 3

10 5
1 5 10 4
1 1 6 5
1 4 8 3
1 6 10 3
1 1 3 1

样例输出 3

7

样例说明 3

这组样例满足子任务 1 的限制。

数据范围

对于所有数据,满足 1 ≤ N ≤ 1 0 9 , 1 ≤ M ≤ 1 0 5 1≤N≤10^9,1≤M≤10^5 1N109,1M105,保证:

  • 1 ≤ T i , C i ≤ 1 0 9 ( 1 ≤ i ≤ M ) 1≤T_i,C_i≤10^9 (1≤i≤M) 1Ti,Ci109(1iM)
  • 1 ≤ L i ≤ R i ≤ N ( 1 ≤ i ≤ M ) 1≤L_i≤R_i≤N (1≤i≤M) 1LiRiN(1iM)

详细子任务及附加限制如下表所示:

子任务编号 附加限制 分值
1 T i = 1 ( 1 ≤ i ≤ M ) T_i=1 (1≤i≤M) Ti=1(1iM) 4
2 M ≤ 16 M≤16 M16 5
3 M ≤ 5 × 1 0 3 M≤5×10^3 M5×103 30
4 无附加限制 61

解析

(此解析来自Master_Yoda的博客原文链接
这题要的是最优解,我们首先观察出来一些性质:如果在某次治疗时,存在健康人的极长区间被这次治疗完全包含,那么这个健康人区间对应的治疗是无用的,可以去掉。

这个性质比较显然。它有一个也很显然的推论:最优解中,在某次治疗时,如果治疗区间内部存在健康人区间,那么这个区间要么会向左延伸到治疗区间以外,要么会向右延伸到治疗区间以外。

所以,当加入一个治疗方案时,它会把至多两个健康人区间合并在一起。

而我们可以发现,健康人区间的边界只会与某个治疗方案有关(左右边界所属的治疗方案可能不同)。我们按时间顺序扫过来,记录健康人区间的边界所属的治疗方案,就可以判断区间的合并了。我们的最终目标就是要在某个时刻让健康人区间的左右端点分别为 1,n。

进一步地,判断合并的区间其实不需要按照时间顺序。只要以任何顺序加入治疗方案,不断合并区间,最终合并出来 [ 1 , n ] [1,n] [1,n] 即可。

所以我们甚至可以从左到右扫区间。那么这样我们考虑这样一个最短路(dp)建图。所有左端点为 1 的治疗方案 i i i 是起点,距离就是其代价 C i C_i Ci

从点 i i i 到点 j j j 有边,当且仅当:

  • T j ≥ T i T_j≥T_i TjTi R i − T j + T i ≥ L j − 1 R_i−T_j+T_i≥L_j−1 RiTj+TiLj1,或者
  • T j < T i T_j<T_i Tj<Ti L j + T i − T i ≤ R i + 1 L_j+T_i−T_i≤R_i+1 Lj+TiTiRi+1

边权就是 C j C_j Cj

跑完最短路后,对于所有右端点为 n 的治疗方案,将他们的最短路取个 min 就是答案了。

考虑优化,显然可以按 T i T_i Ti 的顺序建出两棵可持久化线段树,然后在可持久化线段树上连边、跑最短路即可。

时间复杂度 O ( m l o g 2 m ) O(mlog2m) O(mlog2m)

代码

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
typedef pair<LL,LL> PLL;

namespace IO {
    
    
    #include <cctype>

    template <typename T>
    inline void read(T &x){
    
    
        x=0; 
        char c=0; 
        T w=0;  
        while (!isdigit(c)) w|=c=='-',c=getchar();  
        while (isdigit(c)) x=x*10+(c^48),c=getchar();  
        if(w) x=-x;  
    }
    
    template <typename T>
    inline void write(T x) {
    
    
        if(x<0) putchar('-'),x=-x;
        if(x<10) putchar(x+'0');
            else write(x/10),putchar(x%10+'0');
    }
    
    template <typename T>
    inline void writeln(T x) {
    
    
        write(x);
        putchar('\n');
    }
} 

using IO::read;
using IO::write;
using IO::writeln;

const int N=1e5+5;
const int inf=(1LL<<31)-1;

struct Node {
    
    
    int t,l,r,c;
} a[N];

int m,n;
int vis[N];
int v[N<<2][2];
LL f[N];

vector<int> tr;

inline void update(int u,int l,int r,int ql,int x,int y) {
    
    
    if(l==r) {
    
    
        return v[u][0]=x,v[u][1]=y,void(0);
    }
    int mid=(l+r)>>1;
    if(ql<=mid) update(u<<1,l,mid,ql,x,y);
        else update(u<<1^1,mid+1,r,ql,x,y);
    for(int k=0; k<=1; k++) v[u][k]=MIN(v[u<<1][k],v[u<<1^1][k]);
}

inline void Query(int u,int l,int r,int ql,int qr,int x,int opt) {
    
    
    if(ql>qr) return;
    if(v[u][opt]>x) return;
    if(l==r) {
    
    
        return tr.push_back(l),void(0);
    }
    int mid=(l+r)>>1;
    if(ql<=mid) Query(u<<1,l,mid,ql,qr,x,opt);
    if(qr>mid) Query(u<<1^1,mid+1,r,ql,qr,x,opt);
}

int main() {
    
    
    read(m);
    read(n);
    for(int i=1; i<=n; i++) {
    
    
        int t,l,r,c;
        read(t);
        read(l);
        read(r);
        read(c);
        a[i]=(Node){
    
    t,l,r,c};
    }
    sort(a+1,a+n+1,[](Node a,Node b){
    
    return a.t<b.t;});
    priority_queue<PLL> q;
    for(int i=1; i<=n; i++) {
    
    
        if(a[i].l==1) {
    
    
            f[i]=a[i].c;
            update(1,1,n,i,inf,inf);
            q.push(make_pair(-f[i],i));
        } else {
    
    
            f[i]=1e15;
            update(1,1,n,i,a[i].l+a[i].t,a[i].l-a[i].t);
        }
    }
    while(!q.empty()) {
    
    
        int u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1,tr.clear();
        Query(1,1,n,u+1,n,a[u].r+a[u].t+1,0);
        Query(1,1,n,1,u-1,a[u].r-a[u].t+1,1);
        for(auto v:tr) if(f[v]>f[u]+a[v].c) 
            q.push(make_pair(-(f[v]=f[u]+a[v].c),v));
        for(auto v:tr) update(1,1,n,v,inf,inf);
    }
    LL ans=1e15;
    for(int i=1; i<=n; i++)
        if(a[i].r==m) ans=MIN(ans,f[i]);
    if(ans==1e15) puts("-1");
        else writeln(ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Ljnoit/article/details/105506007