JOISC 2020 Day4
T1 Capital City
原题
CSDN下载:https://download.csdn.net/download/Ljnoit/12263973
链接
翻译
题目描述
在 JOI 的国度有 N N N 个小镇,从 1 1 1 到 N N N 编号,并由 N − 1 N−1 N−1 条双向道路连接。第 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 N−1 行,每行两个整数 A i A_i Ai, B i B_i Bi,描述了 N − 1 N−1 N−1 条道路。
再接下来的 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 1≤K≤N;
- 1 ≤ A i , B i ≤ N ( 1 ≤ i ≤ N − 1 ) 1≤A_i,B_i≤N(1≤i≤N−1) 1≤Ai,Bi≤N(1≤i≤N−1);
- 从任何一个小镇出发都能到达其他任何小镇;
- 1 ≤ C j ≤ K ( 1 ≤ j ≤ N ) 1≤C_j≤K(1≤j≤N) 1≤Cj≤K(1≤j≤N);
- 对于每一个 k ( 1 ≤ k ≤ K ) k(1≤k≤K) k(1≤k≤K),存在一个 j ( 1 ≤ j ≤ N ) j(1≤j≤N) j(1≤j≤N),使得 C j = k C_j=k Cj=k。
详细子任务及附加限制如下表所示:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | N ≤ 20 N≤20 N≤20 | 1 |
2 | N ≤ 2000 N≤2000 N≤2000 | 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
链接
翻译
题目描述
你是一位糯米团子大师,现在你正在串团子。
你面前有一个 R 行 C 列的网格,每格里面放着一个粉、白、绿三色之一的团子。你每次会横向、竖向或斜向选三个连续的团子并将他们按顺序串到一起。其中,按顺序指竖直方向的团子只能以上、中、下或下、中、上的顺序串,而不能以中、上、下或中、上、下的顺序串,其他顺序以此类推。这样,你就获得了一串团子。
当且仅当一串团子的颜色顺序是绿、白、粉或粉、白、绿时,我们把这串团子称为美丽串,请求出串取最多的美丽串的方法。
输入格式
本题共六组数据。
数据的第一行两个以空格分隔的整数 R , C R, C R,C。
接下来 R R R 行每行一个仅含字符 P , W , G P,W,G P,W,G 的字符串 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 的仅含字符 P
,W
,G
,|
,-
,\
,/
的字符串
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
X≤N<Y,你的分数为
N
−
X
2
(
Y
−
X
)
×
S
\frac{N−X}{2(Y−X)}×S
2(Y−X)N−X×S。
如果
Y
≤
N
<
Z
Y≤N<Z
Y≤N<Z,你的分数为
(
1
2
+
N
−
Y
2
(
Z
−
Y
)
)
×
S
(\frac{1}{2}+\frac{N−Y}{2(Z−Y)})×S
(21+2(Z−Y)N−Y)×S。
如果
Z
≤
N
Z≤N
Z≤N,你的分数为
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
链接
翻译
题目描述
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(1≤i≤M) 个治疗方案的花费为 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
Li≤x≤Ri,且他感染了新冠病毒,那么他就会被治愈。
病毒按如下方式传染相邻的居民:
如果在某天的早晨,居民
x
(
1
≤
x
≤
N
)
x (1≤x≤N)
x(1≤x≤N) 被病毒感染,那么在同一天的中午,居民
x
−
1
x−1
x−1(如果
x
≥
2
x≥2
x≥2)和居民
x
+
1
x+1
x+1(如果
x
≤
N
−
1
x≤N−1
x≤N−1)就会被感染。
一个已经被治愈的居民可以再次被病毒感染。
你是 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 1≤N≤109,1≤M≤105,保证:
- 1 ≤ T i , C i ≤ 1 0 9 ( 1 ≤ i ≤ M ) 1≤T_i,C_i≤10^9 (1≤i≤M) 1≤Ti,Ci≤109(1≤i≤M)
- 1 ≤ L i ≤ R i ≤ N ( 1 ≤ i ≤ M ) 1≤L_i≤R_i≤N (1≤i≤M) 1≤Li≤Ri≤N(1≤i≤M)
详细子任务及附加限制如下表所示:
子任务编号 | 附加限制 | 分值 |
---|---|---|
1 | T i = 1 ( 1 ≤ i ≤ M ) T_i=1 (1≤i≤M) Ti=1(1≤i≤M) | 4 |
2 | M ≤ 16 M≤16 M≤16 | 5 |
3 | M ≤ 5 × 1 0 3 M≤5×10^3 M≤5×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 Tj≥Ti 且 R i − T j + T i ≥ L j − 1 R_i−T_j+T_i≥L_j−1 Ri−Tj+Ti≥Lj−1,或者
- 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+Ti−Ti≤Ri+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;
}