文章目录

不得不说…noip的趋势越来越像是day1基础+思路稳定分数,基本确定你能在哪个获奖区,day2难度提高,给满分dalao拼技术和给day1二等的人一个拿一等的机会,给三等的摸一个二等的机会…
总感觉这个day2数据放大就是一套省选…

T1 旅行

​题目点击→洛谷 P5022 旅行​

题目描述
小 Y 是一个爱好旅行的 OIer。她来到 X 国,打算将各个城市都玩一遍。

小Y了解到, X国的 2018 NOIP 提高组 复赛 day2_提高组

小 Y 的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可 以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。当小 Y 回到起点时,她可以选择结束这次旅行或 继续旅行。需要注意的是,小 Y 要求在旅行方案中,每个城市都被访问到。

为了让自己的旅行更有意义,小 Y 决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来。她知道这样会形成一个长度为 2018 NOIP 提高组 复赛 day2_提高组 的序列。她希望这个序列的字典序 最小,你能帮帮她吗? 对于两个长度均为 2018 NOIP 提高组 复赛 day2_提高组 的序列 2018 NOIP 提高组 复赛 day2_noip_042018 NOIP 提高组 复赛 day2_day2_05,当且仅当存在一个正整数 2018 NOIP 提高组 复赛 day2_day2_06,满足以下条件时, 我们说序列 2018 NOIP 提高组 复赛 day2_noip_04 的字典序小于 2018 NOIP 提高组 复赛 day2_day2_05

对于任意正整数 2018 NOIP 提高组 复赛 day2_noip_09,序列 2018 NOIP 提高组 复赛 day2_noip_04 的第 2018 NOIP 提高组 复赛 day2_复赛_11 个元素 2018 NOIP 提高组 复赛 day2_复赛_12 和序列 2018 NOIP 提高组 复赛 day2_day2_05 的第 2018 NOIP 提高组 复赛 day2_复赛_11 个元素 2018 NOIP 提高组 复赛 day2_2018_15 相同。
序列 2018 NOIP 提高组 复赛 day2_noip_04 的第 2018 NOIP 提高组 复赛 day2_day2_06 个元素的值小于序列 2018 NOIP 提高组 复赛 day2_day2_05 的第 2018 NOIP 提高组 复赛 day2_day2_06 个元素的值。
输入输出格式
输入格式:
输入文件共 2018 NOIP 提高组 复赛 day2_复赛_20 行。第一行包含两个整数 2018 NOIP 提高组 复赛 day2_noip_21,中间用一个空格分隔。

接下来 m 行,每行包含两个整数 2018 NOIP 提高组 复赛 day2_2018_22 ,表示编号为 2018 NOIP 提高组 复赛 day2_复赛_232018 NOIP 提高组 复赛 day2_2018_24

输出格式:
输出文件包含一行,2018 NOIP 提高组 复赛 day2_提高组

输入输出样例
输入样例#1:
6 5
1 3
2 3
2 5
3 4
4 6
输出样例#1:
1 3 2 5 4 6
输入样例#2:
6 6
1 3
2 3
2 5
3 4
4 5
4 6
输出样例#2:
1 3 2 4 5 6

T1 分析

先分析数据范围
1.2018 NOIP 提高组 复赛 day2_day2_26(即普通树), 60分
普通树的话就很简单了,因为当到达当前节点2018 NOIP 提高组 复赛 day2_2018_27的时候,必须将这个点的子树全部遍历过才能返回,并且要求字典序最小,所以暴力dfs贪心就可以了,对当前节点2018 NOIP 提高组 复赛 day2_2018_27选择最小的子节点2018 NOIP 提高组 复赛 day2_提高组_29就可以了。
2.2018 NOIP 提高组 复赛 day2_day2_30

<基环树>是指一个有 2018 NOIP 提高组 复赛 day2_2018_31

对于这部分,首先要注意一点,对于到达过的点,只能回退到这个点,不能从它的父亲节点再次到达这个节点。所以直接贪心用优先队列维护当前能到达的所有点,选择最小的到达,并把这个点能到达的点加入队列,这个做法是肯定错的。

当然虽然题目本身是存在难度的,但是如果仔细分析的话并没有那么复杂,因为 2018 NOIP 提高组 复赛 day2_2018_32 (毕竟还是T1…不会太夸张)
手写几组数据随便画一画就可以发现,虽然这张图存在 2018 NOIP 提高组 复赛 day2_复赛_33 条边,但是实际遍历的时候,环上一定有一条边不会被经过,这个随便画一画想清楚就好了。也就是说依旧只会经过 2018 NOIP 提高组 复赛 day2_noip_34 条边。
那么就比较简单了,暴力遍历一遍环上的边,选择这条边不经过,那么对于剩下的 2018 NOIP 提高组 复赛 day2_noip_34条边,就是第一种情况了,直接dfs即可。求个最大的答案就可以了。
当然这里还可以发现一件事,因为数据只有 2018 NOIP 提高组 复赛 day2_2018_36 ,所以其实不用求边,暴力删除2018 NOIP 提高组 复赛 day2_提高组_37条边的复杂度也就是 2018 NOIP 提高组 复赛 day2_复赛_38 ,不会 2018 NOIP 提高组 复赛 day2_day2_39

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
using namespace std;

vector<int>f[5010];
int n,m,del_a,del_b,len;
int edge[5010][2];
int ans[5010];
bool vis[5010];
int tmp[5010];


void dfs(int now){
tmp[len++] = now;
vis[now] = true;
int l = f[now].size();
for (int i = 0 ; i < l ; ++i){
int go = f[now][i];
if (!vis[go] && !((now == del_a && go == del_b) || (now == del_b && go == del_a)))
dfs(go);
}

return;
}

void check(){
if (len != n) return;
for (int i = 0 ; i < n ; ++i)
if (tmp[i] != ans[i]){
if (tmp[i] > ans[i]) return;
break;
}
for (int i = 0 ; i < n ; ++i)
ans[i] = tmp[i];
}


int main()
{
scanf("%d%d",&n,&m);
for (int i = 0 ; i < m ; ++i){
int a,b;scanf("%d%d",&a,&b);
f[a].push_back(b);
f[b].push_back(a);
edge[i][0] = a;
edge[i][1] = b;
}

memset(ans,0x3f,sizeof(ans));
for (int i = 1 ; i <= n ; ++i)
sort(f[i].begin(),f[i].end());
if (n == m){
for (int i = 0; i < m ; ++i)
{
len = 0;
memset(vis,0,sizeof(vis));
del_a = edge[i][0];
del_b = edge[i][1];
dfs(1);
check();
}
}else{
del_a = del_b = -1;
dfs(1);
check();
}
for (int i = 0 ; i < n ; ++i)
printf("%d%c",ans[i],i!=n-1?' ':'\n');
return 0;
}

T2 填数游戏

​题目点击→洛谷 P5023 填数游戏​​​ 小 D 特别喜欢玩游戏。这一天,他在玩一款填数游戏。
这个填数游戏的棋盘是一个2018 NOIP 提高组 复赛 day2_2018_40的矩形表格。玩家需要在表格的每个格子中填入一个数字(数字 2018 NOIP 提高组 复赛 day2_提高组_41 或者数字 2018 NOIP 提高组 复赛 day2_day2_42),填数时需要满足一些限制。
下面我们来具体描述这些限制。
为了方便描述,我们先给出一些定义:

  • 我们用每个格子的行列坐标来表示一个格子,即(行坐标,列坐标)。(注意: 行列坐标均从2018 NOIP 提高组 复赛 day2_day2_43
  • 2018 NOIP 提高组 复赛 day2_复赛_44:一条路径是合法的当且仅当:
  1. 这条路径从矩形表格的左上角的格子2018 NOIP 提高组 复赛 day2_复赛_45出发,到矩形的右下角格子2018 NOIP 提高组 复赛 day2_复赛_46结束;
  2. 在这条路径中,每次只能从当前的格子移动到右边与它相邻的格子,或者 从当前格子移动到下面与它相邻的格子。
    例如:在下面这个矩形中,只有两条路径是合法的,它们分别是2018 NOIP 提高组 复赛 day2_复赛_472018 NOIP 提高组 复赛 day2_复赛_452018 NOIP 提高组 复赛 day2_noip_492018 NOIP 提高组 复赛 day2_提高组_502018 NOIP 提高组 复赛 day2_day2_512018 NOIP 提高组 复赛 day2_复赛_452018 NOIP 提高组 复赛 day2_提高组_532018 NOIP 提高组 复赛 day2_提高组_50
  3. 2018 NOIP 提高组 复赛 day2_day2_55

对于一条合法的路径 2018 NOIP 提高组 复赛 day2_2018_56,我们可以用一个字符串2018 NOIP 提高组 复赛 day2_noip_57来表示,该字符串的长度为2018 NOIP 提高组 复赛 day2_提高组_58,其中只包含字符“2018 NOIP 提高组 复赛 day2_day2_59”或者字符“2018 NOIP 提高组 复赛 day2_noip_60”, 第 2018 NOIP 提高组 复赛 day2_复赛_11 个字符记录了路径 2018 NOIP 提高组 复赛 day2_2018_56 中第 2018 NOIP 提高组 复赛 day2_复赛_11 步的移动方法,“2018 NOIP 提高组 复赛 day2_day2_59”表示移动到当前格子右边与它相邻的格子,“2018 NOIP 提高组 复赛 day2_noip_60”表示移动到当前格子下面 与它相邻的格子。例如,上图中对于路径2018 NOIP 提高组 复赛 day2_提高组_66,有2018 NOIP 提高组 复赛 day2_复赛_67"2018 NOIP 提高组 复赛 day2_复赛_68";而对于另一条路径2018 NOIP 提高组 复赛 day2_提高组_69, 有2018 NOIP 提高组 复赛 day2_2018_70"2018 NOIP 提高组 复赛 day2_noip_71"。

同时,将每条合法路径 2018 NOIP 提高组 复赛 day2_2018_56 经过的每个格子上填入的数字依次连接后,会得到一个长 度为2018 NOIP 提高组 复赛 day2_提高组_732018 NOIP 提高组 复赛 day2_day2_74 字符串,记为 2018 NOIP 提高组 复赛 day2_复赛_75。例如,如果我们在格子2018 NOIP 提高组 复赛 day2_noip_762018 NOIP 提高组 复赛 day2_2018_77上填入数字 2018 NOIP 提高组 复赛 day2_提高组_41,在格子2018 NOIP 提高组 复赛 day2_提高组_792018 NOIP 提高组 复赛 day2_day2_80上填入数字 2018 NOIP 提高组 复赛 day2_day2_42(见上图红色数字)。那么对于路径2018 NOIP 提高组 复赛 day2_提高组_66,我们可以得 到2018 NOIP 提高组 复赛 day2_2018_83"2018 NOIP 提高组 复赛 day2_noip_84",对于路径2018 NOIP 提高组 复赛 day2_提高组_69,有2018 NOIP 提高组 复赛 day2_noip_86"2018 NOIP 提高组 复赛 day2_noip_87"。

游戏要求小 2018 NOIP 提高组 复赛 day2_noip_60 找到一种填数字 2018 NOIP 提高组 复赛 day2_提高组_412018 NOIP 提高组 复赛 day2_day2_42 的方法,使得对于两条路径2018 NOIP 提高组 复赛 day2_提高组_662018 NOIP 提高组 复赛 day2_提高组_69,如果2018 NOIP 提高组 复赛 day2_提高组_93,那么必须2018 NOIP 提高组 复赛 day2_2018_94
我们说字符串 2018 NOIP 提高组 复赛 day2_复赛_95 比字符串 2018 NOIP 提高组 复赛 day2_复赛_96 小,当且仅当字符串 2018 NOIP 提高组 复赛 day2_复赛_95 的字典序小于字符串 2018 NOIP 提高组 复赛 day2_复赛_96 的字典序,字典序的定义详见第一题。但是仅仅是找一种方法无法满 足小 2018 NOIP 提高组 复赛 day2_noip_60 的好奇心,小 2018 NOIP 提高组 复赛 day2_noip_60

2018 NOIP 提高组 复赛 day2_noip_60 能力有限,希望你帮助他解决这个问题,即有多少种填 2018 NOIP 提高组 复赛 day2_提高组_412018 NOIP 提高组 复赛 day2_day2_42 的方法能满足题目要求。由于答案可能很大,你需要输出答案对2018 NOIP 提高组 复赛 day2_复赛_104取模的结果。

输入输出格式
输入格式:
输入文件共一行,包含两个正整数 n,mn,m,由一个空格分隔,表示矩形的大小。其 中 nn 表示矩形表格的行数,mm 表示矩形表格的列数。

输出格式:
输出共一行,包含一个正整数,表示有多少种填 2018 NOIP 提高组 复赛 day2_提高组_412018 NOIP 提高组 复赛 day2_day2_42 的方法能满足游戏的要求。 注意:输出答案对 2018 NOIP 提高组 复赛 day2_2018_107取模的结果。

输入输出样例
输入样例#1:
2 2
输出样例#1:
12
输入样例#2:
3 3
输出样例#2:
112
输入样例#3:
5 5
输出样例#3:
7136

T2 分析

oi一大经典类型题…暴力打表找规律。
先化简一下题意:
给定一个2018 NOIP 提高组 复赛 day2_day2_108 网格棋盘,要求用2018 NOIP 提高组 复赛 day2_提高组_109填充每一个格子.
使得任意两条从点 2018 NOIP 提高组 复赛 day2_noip_762018 NOIP 提高组 复赛 day2_复赛_111 的不完全相交路径,先向右(字典序大)的路径的 2018 NOIP 提高组 复赛 day2_day2_74 路径字典序相对小.
首先确定一件事,对于任意一种填法,棋盘中填的数字一定满足2018 NOIP 提高组 复赛 day2_复赛_113,即从左下到右上的任意对角线中的数字一定是非递增序列。
当然这种题目…一看数据那么小,而且是固定答案的,遇事不决先暴力 …只要把2018 NOIP 提高组 复赛 day2_提高组_114的表打出来基本就能过了…
当然对于dalao来说,一看2018 NOIP 提高组 复赛 day2_复赛_115,第一反应应该是状压才对…不应该是暴力…

/***********************************************/
比赛时的做法可能是这样的:

当然比赛的时候写个暴力找规律没有任何问题…2018 NOIP 提高组 复赛 day2_noip_116直接上呗…
这时候会发现表很难打,打到 2018 NOIP 提高组 复赛 day2_noip_117 以后就非常慢了,然后2018 NOIP 提高组 复赛 day2_2018_118基本上短时间内只能出一个 2018 NOIP 提高组 复赛 day2_复赛_119 的答案
当然这里不管是打表还是画图,都可以发现第二件事,那就是对于 2018 NOIP 提高组 复赛 day2_2018_120 的答案和 2018 NOIP 提高组 复赛 day2_noip_121 是一样的,对称一下就行了。
这时候打一个以下的表…猜一下规律,会发现相邻的两项基本上都满足 2018 NOIP 提高组 复赛 day2_noip_122

  • 2018 NOIP 提高组 复赛 day2_提高组_123时,2018 NOIP 提高组 复赛 day2_2018_124
  • 2018 NOIP 提高组 复赛 day2_noip_125时,2018 NOIP 提高组 复赛 day2_2018_126
  • 2018 NOIP 提高组 复赛 day2_noip_127时,2018 NOIP 提高组 复赛 day2_提高组_128
  • 2018 NOIP 提高组 复赛 day2_noip_129时,2018 NOIP 提高组 复赛 day2_提高组_130发现2018 NOIP 提高组 复赛 day2_noip_131,之后的答案依旧满足上述的规律,即2018 NOIP 提高组 复赛 day2_2018_132
  • 2018 NOIP 提高组 复赛 day2_2018_133时,2018 NOIP 提高组 复赛 day2_noip_134发现2018 NOIP 提高组 复赛 day2_day2_135,之后的答案依旧满足上述规律,即2018 NOIP 提高组 复赛 day2_2018_136
  • 2018 NOIP 提高组 复赛 day2_复赛_137时,2018 NOIP 提高组 复赛 day2_复赛_138发现2018 NOIP 提高组 复赛 day2_2018_139,之后的答案依旧满足上述规律,即2018 NOIP 提高组 复赛 day2_day2_140
    其实这时候就算2018 NOIP 提高组 复赛 day2_提高组_141后面的答案你的代码很难跑出来…猜也猜要猜一个规律上去…
    可以发现对于不满足常规规律是从2018 NOIP 提高组 复赛 day2_noip_129开始的,那么从2018 NOIP 提高组 复赛 day2_noip_129开始找规律,
    可以发现2018 NOIP 提高组 复赛 day2_提高组_144
    那么可以猜测一下2018 NOIP 提高组 复赛 day2_day2_145
  • 2018 NOIP 提高组 复赛 day2_复赛_146时,2018 NOIP 提高组 复赛 day2_noip_147,之后的答案即2018 NOIP 提高组 复赛 day2_day2_148
  • 2018 NOIP 提高组 复赛 day2_2018_149时,2018 NOIP 提高组 复赛 day2_提高组_150,之后的答案即2018 NOIP 提高组 复赛 day2_noip_151
    这样的话就做完了…并且可以用自己的暴力程序,开着代码一直跑,看看能不能满足自己找的规律,来告诉自己到底能估到几分,当然这个做法就是100分的…但是比赛时起码能保证自己可以获得65分,即2018 NOIP 提高组 复赛 day2_2018_152的部分
  • 2018 NOIP 提高组 复赛 day2_2018_153

  • 这个表大概跑了半个小时左右吧,2018 NOIP 提高组 复赛 day2_2018_149的情况暴力跑太慢了。
    /***********************************************/

如果想要证明规律的话,需要发现以下条件

  • 2018 NOIP 提高组 复赛 day2_提高组_155
  • 棋盘中填的数字一定满足2018 NOIP 提高组 复赛 day2_2018_156,即从左下到右上的任意对角线中的数字一定是非递增序列;
  • 如果(i-1,j)和(i,j-1)的填数相同,那么以(i,j)为左上角、以(n,m)为右下角的子矩阵内所有对角线内的填数各自相等。

那么现在就可以分类讨论了:

2018 NOIP 提高组 复赛 day2_noip_157

  • 这种情况下,只需要使得2018 NOIP 提高组 复赛 day2_noip_158的所有对角线上数字相同.
  • 同时第一列和第一行会受其影响,画一画图,加上推一波式子可以发现,答案形如2018 NOIP 提高组 复赛 day2_noip_159

2018 NOIP 提高组 复赛 day2_noip_157

  • 2018 NOIP 提高组 复赛 day2_noip_161相同:
    这种情况类似第一种情况
  • 2018 NOIP 提高组 复赛 day2_noip_161其中一对相同:
    2018 NOIP 提高组 复赛 day2_noip_163表示到第2018 NOIP 提高组 复赛 day2_提高组_164条对角线上时,在前两行的第2018 NOIP 提高组 复赛 day2_提高组_165条斜线上及以前有过匹配的方案数。
    即在两行中,第二行的某一格与其斜上方的格相同,一旦出现这种情况接下来第二行的填数就会有限制.
    当第一行和第二行不存在这种情况的时候,当前的一条对角线实际上有2018 NOIP 提高组 复赛 day2_noip_166种填法,而当第2018 NOIP 提高组 复赛 day2_提高组_164条对角线上出现这种情况的时候,则只有2018 NOIP 提高组 复赛 day2_提高组_168种填法.
    那么考虑一下转移就可以写出:
    2018 NOIP 提高组 复赛 day2_noip_169

    (在2018 NOIP 提高组 复赛 day2_复赛_170条斜线上以前就匹配成功的方案2018 NOIP 提高组 复赛 day2_复赛_171) * (当前第2018 NOIP 提高组 复赛 day2_提高组_164条对角线的2018 NOIP 提高组 复赛 day2_提高组_168种填法) + (2018 NOIP 提高组 复赛 day2_提高组_165条斜线第一次匹配成功的2018 NOIP 提高组 复赛 day2_提高组_168种填法) * (当前第2018 NOIP 提高组 复赛 day2_提高组_164条对角线因不受任何影响所以有2018 NOIP 提高组 复赛 day2_noip_166种填法.)
  • 2018 NOIP 提高组 复赛 day2_提高组_178

PC画图太麻烦了,这里给出 2018 NOIP 提高组 复赛 day2_day2_179 的图
按照同样的方法可以画出接下去的几个图
2018 NOIP 提高组 复赛 day2_day2_180
2018 NOIP 提高组 复赛 day2_复赛_181
2018 NOIP 提高组 复赛 day2_2018_182
2018 NOIP 提高组 复赛 day2_复赛_183

这样就可以计算出当 2018 NOIP 提高组 复赛 day2_复赛_184的答案了.
2018 NOIP 提高组 复赛 day2_2018_185 时,前两种情况是很容易推下去的。
按照第三种情况,根据最后一条对角线是否匹配成功进行讨论.
不难推出另外一种的答案实质上是
2018 NOIP 提高组 复赛 day2_提高组_186
当推过几次式子之后可以发现,也满足上面打表所说的规律,2018 NOIP 提高组 复赛 day2_复赛_33 每次 2018 NOIP 提高组 复赛 day2_提高组_188 会让答案 2018 NOIP 提高组 复赛 day2_noip_189

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
using namespace std;
#define LL long long
const LL mod=1e9+7;
int n,m;
LL qpow(LL a,int n){
LL res=1;
while (n){
if(n&1) res=res*a%mod;
a = a * a % mod;
n>>=1;
}
return res%mod;
}
int main(){
scanf("%d%d",&n,&m);
if(n>m)swap(n,m);
if(n==1){
cout<<qpow(2,m)<<endl;
}
if(n==2){
cout<<4*qpow(3,m-1)%mod<<endl;
}
if(n==3){
cout<<112*qpow(3,m-3)%mod<<endl;
}
if(n==4){
if(m==4)puts("912");
else cout<<2688*qpow(3,m-5)%mod<<endl;
}
if(n==5){
if(m==5)puts("7136");
else cout<<21312*qpow(3,m-6)%mod<<endl;
}
if(n==6){
if(m==6)puts("56768");
else cout<<170112*qpow(3,m-7)%mod<<endl;
}
if(n==7){
if(m==7)puts("453504");
else cout<<1360128*qpow(3,m-8)%mod<<endl;
}
if(n==8){
if(m==8)puts("3626752");
else cout<<10879488*qpow(3,m-9)%mod<<endl;
}
}

T3 保卫王国

​题目点击→洛谷 P5024 保卫王国​

题目描述
Z 国有2018 NOIP 提高组 复赛 day2_提高组座城市,2018 NOIP 提高组 复赛 day2_提高组_191条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。
Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:
一座城市可以驻扎一支军队,也可以不驻扎军队。
由道路直接连接的两座城市中至少要有一座城市驻扎军队。
在城市里驻扎军队会产生花费,在编号为i的城市中驻扎军队的花费是2018 NOIP 提高组 复赛 day2_复赛_192
小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出 了mm个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一 给出回答。具体而言,如果国王提出的第2018 NOIP 提高组 复赛 day2_2018_193个要求能够满足上述驻扎条件(不需要考虑 第 2018 NOIP 提高组 复赛 day2_2018_193 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果 国王提出的第2018 NOIP 提高组 复赛 day2_2018_193个要求无法满足,则需要输出2018 NOIP 提高组 复赛 day2_2018_196。现在请你来帮助小 Z。

输入输出格式
输入格式:
2018 NOIP 提高组 复赛 day2_day2_42 行包含两个正整数2018 NOIP 提高组 复赛 day2_day2_198和一个字符串2018 NOIP 提高组 复赛 day2_复赛_199,分别表示城市数、要求数和数据类型。2018 NOIP 提高组 复赛 day2_复赛_199是一个由大写字母 2018 NOIP 提高组 复赛 day2_noip_042018 NOIP 提高组 复赛 day2_day2_052018 NOIP 提高组 复赛 day2_day2_203 和一个数字 2018 NOIP 提高组 复赛 day2_day2_422018 NOIP 提高组 复赛 day2_day2_2052018 NOIP 提高组 复赛 day2_复赛_206

2018 NOIP 提高组 复赛 day2_day2_2052018 NOIP 提高组 复赛 day2_提高组个整数2018 NOIP 提高组 复赛 day2_复赛_192,表示编号2018 NOIP 提高组 复赛 day2_复赛_11的城市中驻扎军队的花费。
接下来 2018 NOIP 提高组 复赛 day2_提高组_191 行,每行两个正整数2018 NOIP 提高组 复赛 day2_提高组_212,表示有一条2018 NOIP 提高组 复赛 day2_复赛_232018 NOIP 提高组 复赛 day2_2018_24的双向道路。
接下来 2018 NOIP 提高组 复赛 day2_复赛_33 行,第jj行四个整数2018 NOIP 提高组 复赛 day2_noip_216,表示第jj个要求是在城市2018 NOIP 提高组 复赛 day2_复赛_95驻扎2018 NOIP 提高组 复赛 day2_day2_06支军队, 在城市2018 NOIP 提高组 复赛 day2_复赛_96驻扎yy支军队。其中,2018 NOIP 提高组 复赛 day2_day2_062018 NOIP 提高组 复赛 day2_noip_221 的取值只有 2018 NOIP 提高组 复赛 day2_提高组_412018 NOIP 提高组 复赛 day2_day2_42:若 2018 NOIP 提高组 复赛 day2_day2_062018 NOIP 提高组 复赛 day2_提高组_41,表示城市 2018 NOIP 提高组 复赛 day2_复赛_95 不得驻 扎军队,若 2018 NOIP 提高组 复赛 day2_day2_062018 NOIP 提高组 复赛 day2_day2_42,表示城市 2018 NOIP 提高组 复赛 day2_复赛_95 必须驻扎军队;若 2018 NOIP 提高组 复赛 day2_noip_2212018 NOIP 提高组 复赛 day2_提高组_41,表示城市 2018 NOIP 提高组 复赛 day2_复赛_96不得驻扎军队, 若 2018 NOIP 提高组 复赛 day2_noip_2212018 NOIP 提高组 复赛 day2_day2_42,表示城市 2018 NOIP 提高组 复赛 day2_复赛_96

输入文件中每一行相邻的两个数据之间均用一个空格分隔。

输出格式:
输出共 2018 NOIP 提高组 复赛 day2_复赛_33 行,每行包含 2018 NOIP 提高组 复赛 day2_day2_42 个整数,第2018 NOIP 提高组 复赛 day2_2018_193行表示在满足国王第2018 NOIP 提高组 复赛 day2_2018_193个要求时的最小开销, 如果无法满足国王的第2018 NOIP 提高组 复赛 day2_2018_193个要求,则该行输出2018 NOIP 提高组 复赛 day2_noip_241

输入输出样例
输入样例#1:
5 3 C3
2 4 1 3 9
1 5
5 2
5 3
3 4
1 0 3 0
2 1 3 1
1 0 5 0
输出样例#1:
12
7
-1

T3 分析

黑科技题目…反正我不会… 动态dp模板题,甚至还能实现一下传说中的“全局平衡二叉树”…
简述一下题意:对一颗2018 NOIP 提高组 复赛 day2_提高组个点的树进行染色,对第2018 NOIP 提高组 复赛 day2_复赛_11个点染色的花费是2018 NOIP 提高组 复赛 day2_复赛_192,要求相邻的两个点之间必须有一个点被染色,然后给出2018 NOIP 提高组 复赛 day2_复赛_33组询问,每次询问强制对两个点固定状态(染色或不染色)

先考虑比赛时的做法:乍一看不会做怎么办…我们暴力一发O(nm)再说…
对于每个点可以选或不选,所以我们用:
2018 NOIP 提高组 复赛 day2_day2_246
同时,还要求相邻的两个节点至少有一个要选,所以如果 2018 NOIP 提高组 复赛 day2_复赛_11 不选,则 2018 NOIP 提高组 复赛 day2_复赛_11 的所有叶节点必须都选,如果 2018 NOIP 提高组 复赛 day2_复赛_11

以此得到状态转移( 2018 NOIP 提高组 复赛 day2_2018_1932018 NOIP 提高组 复赛 day2_复赛_11 的叶节点,2018 NOIP 提高组 复赛 day2_复赛_2522018 NOIP 提高组 复赛 day2_复赛_11 节点的权值):
2018 NOIP 提高组 复赛 day2_复赛_254
对于每个询问,若必选则将权值修改为2018 NOIP 提高组 复赛 day2_提高组_41,若不选则将权值修改为2018 NOIP 提高组 复赛 day2_提高组_256,最后输出2018 NOIP 提高组 复赛 day2_提高组_257
时间复杂度2018 NOIP 提高组 复赛 day2_2018_258,44分。

显然对于整个代码,进行dp是必须的,那么上面的代码慢在哪里呢?显然是对于每次询问都对整棵树重新做一次dp了,显然对于只修改两个点,可能对于dp值只需要修改几个关键点就可以了,很多点的值是不需要修改的,但是我们依旧做了一次dp,所以可以猜想优化应该在这里。
那么我们考虑修改两个值对整个dp数组会有多少影响。

假设修改x,y两个点,观察一下对整棵树的影响

2018 NOIP 提高组 复赛 day2_day2_42表示根节点,2018 NOIP 提高组 复赛 day2_day2_260表示2018 NOIP 提高组 复赛 day2_2018_261的最近公共祖先,2018 NOIP 提高组 复赛 day2_noip_04表示2018 NOIP 提高组 复赛 day2_day2_260不包含2018 NOIP 提高组 复赛 day2_2018_261的子树,2018 NOIP 提高组 复赛 day2_day2_05表示根节点2018 NOIP 提高组 复赛 day2_day2_42不包含2018 NOIP 提高组 复赛 day2_2018_261的子树,2018 NOIP 提高组 复赛 day2_day2_268分别表示2018 NOIP 提高组 复赛 day2_2018_261的子树

2018 NOIP 提高组 复赛 day2_day2_270


我们可以发现,对于x,y修改,对整棵树的影响只有2018 NOIP 提高组 复赛 day2_day2_271这两条路径上的值。其他点的值都是不会修改的。

所以其实我们对于每个询问需要做的就是修改2018 NOIP 提高组 复赛 day2_2018_261后,将2018 NOIP 提高组 复赛 day2_2018_261的值重新更新到根节点2018 NOIP 提高组 复赛 day2_day2_42

因为在结果上我们需要更新的路径可以分为三段:2018 NOIP 提高组 复赛 day2_提高组_275,即2018 NOIP 提高组 复赛 day2_2018_261到达2018 NOIP 提高组 复赛 day2_2018_277,再从2018 NOIP 提高组 复赛 day2_2018_277到达根节点2018 NOIP 提高组 复赛 day2_day2_42

所以考虑维护一个数组2018 NOIP 提高组 复赛 day2_提高组_280表示x不选/选,y不选/选时,x到y这条路径上能得到的最大的dp值.

接下去就可以对询问处理答案了,但是这里需要的空间是2018 NOIP 提高组 复赛 day2_复赛_281所以我们考虑修改一下记录方式。因为这个数组维护的目的其实也是使得2018 NOIP 提高组 复赛 day2_2018_261到达2018 NOIP 提高组 复赛 day2_2018_277,所以我们用倍增的方式来优化这个数组。

即将数组维护成2018 NOIP 提高组 复赛 day2_day2_284表示 2018 NOIP 提高组 复赛 day2_day2_06 不选/选,从 2018 NOIP 提高组 复赛 day2_day2_06 往上跳 2018 NOIP 提高组 复赛 day2_提高组_287 步的节点不选/选时,这条路上能得到的最大dp值。

接下去考虑一件事情,因为2018 NOIP 提高组 复赛 day2_提高组_288这条路径中,可能还存在一些点,这些点也存在它们的子树,而这个方程是没有办法考虑到这件事情的,所以我们需要重新定义方程:

2018 NOIP 提高组 复赛 day2_day2_289表示 2018 NOIP 提高组 复赛 day2_day2_06 不选/选,从 2018 NOIP 提高组 复赛 day2_day2_06 往上跳 2018 NOIP 提高组 复赛 day2_提高组_287 步的节点不选/选时,这条路上能得到的最大dp值 且计入其他子树的最终dp值。即在整个祖先节点的子树中减去 2018 NOIP 提高组 复赛 day2_day2_06 的子树的影响。

然后我们得到转移方程:

2018 NOIP 提高组 复赛 day2_noip_294

样我们可以直接把2018 NOIP 提高组 复赛 day2_2018_2612018 NOIP 提高组 复赛 day2_提高组_296 的路径上的 f 数组加起来,并加上 2018 NOIP 提高组 复赛 day2_2018_261 的子树的贡献,再加上从 2018 NOIP 提高组 复赛 day2_提高组_296 开始到根节点2018 NOIP 提高组 复赛 day2_day2_42的贡献,再加上子树2018 NOIP 提高组 复赛 day2_复赛_300的贡献,就是最终的答案了。

当然这里还存在一些小问题,比如我们要用新的2018 NOIP 提高组 复赛 day2_day2_062018 NOIP 提高组 复赛 day2_2018_302值更新出新的2018 NOIP 提高组 复赛 day2_day2_06的父亲2018 NOIP 提高组 复赛 day2_复赛_3042018 NOIP 提高组 复赛 day2_2018_302值时,要计算2018 NOIP 提高组 复赛 day2_复赛_304别的子树的贡献,它保存在2018 NOIP 提高组 复赛 day2_提高组_307中,但是2018 NOIP 提高组 复赛 day2_提高组_308即我们当前要替换的旧的2018 NOIP 提高组 复赛 day2_2018_302值也算在里面了
设新的2018 NOIP 提高组 复赛 day2_day2_062018 NOIP 提高组 复赛 day2_2018_302值为2018 NOIP 提高组 复赛 day2_复赛_312,新的2018 NOIP 提高组 复赛 day2_复赛_3042018 NOIP 提高组 复赛 day2_2018_302值即为
2018 NOIP 提高组 复赛 day2_day2_315
然后两个特殊情况:

  1. 2018 NOIP 提高组 复赛 day2_2018_316在一条链上时,倍增2018 NOIP 提高组 复赛 day2_day2_317之后已经在2018 NOIP 提高组 复赛 day2_提高组_318上了,此时直接倍增2018 NOIP 提高组 复赛 day2_提高组_318即可;
  2. 2018 NOIP 提高组 复赛 day2_day2_320为根节点时,显然不需要倍增2018 NOIP 提高组 复赛 day2_提高组_318,此时直接算出总答案。

还有个小问题…2018 NOIP 提高组 复赛 day2_提高组_256不要用2018 NOIP 提高组 复赛 day2_提高组_323…要用2018 NOIP 提高组 复赛 day2_复赛_324

#include <iostream>
#include <cstdio>
#include <cctype>
#define LL long long
#define INF 100000000000
using namespace std;
const int step[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};

int cnt = 0,n,m;
long long p[100050];
int father[100050][20],head[100050];
int dep[100050];
int log[100050];
LL dp[100050][3];
LL f[100050][3][20][3];
struct XX{
int to,nxt;
}edg[201000];

void dfs(int u){
dep[u] = dep[father[u][0]] + 1;
dp[u][1] = p[u];
f[u][0][0][0] = INF;
for(int i = 1 ; step[i] < dep[u] ; ++i)
father[u][i] = father[father[u][i-1]][i-1];
for(int i = head[u] ; i ; i = edg[i].nxt){
int v = edg[i].to;
if(v != father[u][0]) {
father[v][0] = u;
dfs(v);
dp[u][0] += dp[v][1];
dp[u][1] += min(dp[v][0],dp[v][1]);
}
}
}

void ddfs(int u){
f[u][1][0][0] = dp[father[u][0]][0] - dp[u][1];
f[u][0][0][1] = f[u][1][0][1] = dp[father[u][0]][1] - min(dp[u][0],dp[u][1]);
for(int i = 1 ; step[i] < dep[u] ; ++i){
int F = father[u][i-1];
f[u][0][i][0] = min(f[u][0][i-1][0] + f[F][0][i-1][0],f[u][0][i-1][1] + f[F][1][i-1][0]);
f[u][0][i][1] = min(f[u][0][i-1][0] + f[F][0][i-1][1],f[u][0][i-1][1] + f[F][1][i-1][1]);
f[u][1][i][0] = min(f[u][1][i-1][0] + f[F][0][i-1][0],f[u][1][i-1][1] + f[F][1][i-1][0]);
f[u][1][i][1] = min(f[u][1][i-1][0] + f[F][0][i-1][1],f[u][1][i-1][1] + f[F][1][i-1][1]);
}
for(int i = head[u] ; i ; i = edg[i].nxt)
if(edg[i].to != father[u][0])
ddfs(edg[i].to);
}
void work(){
int u,x,v,y;
scanf("%d%d%d%d",&u,&x,&v,&y);
if(dep[u] < dep[v]){
swap(u,v);
swap(x,y);
}

LL L,x0,x1,y0,y1,l0,l1,ans;
x0 = x1 = y0 = y1 = l0 = l1 = INF;
if (x) x1 = dp[u][1];
else x0 = dp[u][0];
if (y) y1 = dp[v][1];
else y0 = dp[v][0];
for(int i = log[dep[u] - dep[v]] ; i >= 0 ; --i)
if(dep[u] - step[i] >= dep[v]){
LL t0 = x0;
LL t1 = x1;
x0 = min(t0 + f[u][0][i][0],t1 + f[u][1][i][0]),
x1 = min(t0 + f[u][0][i][1],t1 + f[u][1][i][1]),
u = father[u][i];
} //u往上跳
if(u==v) {
L = u;
if (y) l1 = x1;
else l0 = x0;
}
else{
for(int i = log[dep[u]-1] ; i >= 0 ; --i)
if(father[u][i] != father[v][i]){
LL t0 = x0;
LL t1 = x1;
LL p0 = y0;
LL p1 = y1;
x0 = min(t0 + f[u][0][i][0],t1 + f[u][1][i][0]);
x1 = min(t0 + f[u][0][i][1],t1 + f[u][1][i][1]);
y0 = min(p0 + f[v][0][i][0],p1 + f[v][1][i][0]);
y1 = min(p0 + f[v][0][i][1],p1 + f[v][1][i][1]);
u = father[u][i];
v = father[v][i];
}
L = father[u][0];
l0 = dp[L][0] - dp[u][1] - dp[v][1] + x1 + y1;
l1 = dp[L][1] - min(dp[u][0],dp[u][1]) - min(dp[v][0],dp[v][1]) + min(x0,x1) + min(y0,y1);
}
if(L == 1) ans=min(l0,l1);
else{
for(int i = log[dep[L]-2]; i >= 0 ; --i)
if(dep[L] - step[i] > 1){
LL t0 = l0;
LL t1 = l1;
l0 = min(t0 + f[L][0][i][0],t1 + f[L][1][i][0]);
l1 = min(t0 + f[L][0][i][1],t1 + f[L][1][i][1]);
L = father[L][i];
}
ans=min(dp[1][0] - dp[L][1] + l1 , dp[1][1] - min(dp[L][0],dp[L][1]) + min(l0,l1));
}
if (ans < INF) printf("%lld\n",ans);
else puts("-1");
}
void init(){
for (int i = 2 ; i <= n ; ++i) log[i] = log[i >> 1] + 1;
dfs(1);
ddfs(1);
}
void add(int u,int v){
edg[++cnt] = XX{v,head[u]};
head[u] = cnt;
}

int main(){
scanf("%d%d%*s",&n,&m);
for (int i = 1 ; i <= n ; ++i) scanf("%lld",&p[i]);
for (int i = 0 ; i < n - 1 ; ++i){
int u,v;scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
init();
while(m--) work();
return 0;
}