2018 NOIP 提高组 复赛 day2

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

T1 旅行

题目点击→洛谷 P5022 旅行

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

小Y了解到, X国的 n n n 个城市之间有 mm 条双向道路。每条双向道路连接两个城市。 不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路。并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。小 Y 只能通过这些 道路从一个城市前往另一个城市。

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

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

对于任意正整数 1 ≤ i &lt; x 1 ≤ i &lt; x 1i<x,序列 A A A 的第 i i i 个元素 A i A_i Ai 和序列 B B B 的第 i i i 个元素 B i B_i Bi 相同。
序列 A A A 的第 x x x 个元素的值小于序列 B B B 的第 x x x 个元素的值。
输入输出格式
输入格式:
输入文件共 m + 1 m + 1 m+1 行。第一行包含两个整数 n , m ( m ≤ n ) n,m(m ≤ n) n,m(mn),中间用一个空格分隔。

接下来 m 行,每行包含两个整数 u , v ( 1 ≤ u , v ≤ n ) u,v (1 ≤ u,v ≤ n) u,v(1u,vn) ,表示编号为 u u u v v v 的城市之 间有一条道路,两个整数之间用一个空格分隔。

输出格式:
输出文件包含一行, n n n 个整数,表示字典序最小的序列。相邻两个整数之间用一个 空格分隔。

输入输出样例
输入样例#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. m = n − 1 m = n-1 m=n1(即普通树), 60分
普通树的话就很简单了,因为当到达当前节点 n o w now now的时候,必须将这个点的子树全部遍历过才能返回,并且要求字典序最小,所以暴力dfs贪心就可以了,对当前节点 n o w now now选择最小的子节点 d f s ( m i n ( s o n [ n o w ] ) ) dfs( min(son[now]) ) dfs(min(son[now]))就可以了。
2. m = n m = n m=n (即基环树),40分

<基环树>是指一个有 m m m 条边的连通图,即可以看成是一棵树中多了一条边,也就是所谓的<环套树>,那么在环中删除一条边这个图就是一棵树了。

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

当然虽然题目本身是存在难度的,但是如果仔细分析的话并没有那么复杂,因为 n ≤ 5000 n≤5000 n5000 (毕竟还是T1…不会太夸张)
手写几组数据随便画一画就可以发现,虽然这张图存在 m m m 条边,但是实际遍历的时候,环上一定有一条边不会被经过,这个随便画一画想清楚就好了。也就是说依旧只会经过 m − 1 m-1 m1 条边。
那么就比较简单了,暴力遍历一遍环上的边,选择这条边不经过,那么对于剩下的 m − 1 m-1 m1条边,就是第一种情况了,直接dfs即可。求个最大的答案就可以了。
当然这里还可以发现一件事,因为数据只有 n = 5000 n = 5000 n=5000 ,所以其实不用求边,暴力删除 5000 5000 5000条边的复杂度也就是 O ( n 2 ) O(n^2) O(n2) ,不会 T L E TLE TLE 的。

#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 特别喜欢玩游戏。这一天,他在玩一款填数游戏。
这个填数游戏的棋盘是一个 n × m n \times m n×m的矩形表格。玩家需要在表格的每个格子中填入一个数字(数字 0 0 0 或者数字 1 1 1),填数时需要满足一些限制。
下面我们来具体描述这些限制。
为了方便描述,我们先给出一些定义:

  • 我们用每个格子的行列坐标来表示一个格子,即(行坐标,列坐标)。(注意: 行列坐标均从 0 0 0 开始编号) 合法路径
  • P P P:一条路径是合法的当且仅当:
  1. 这条路径从矩形表格的左上角的格子 ( 0 , 0 ) (0,0) (0,0)出发,到矩形的右下角格子 ( n − 1 , m − 1 ) (n - 1,m - 1) (n1,m1)结束;
  2. 在这条路径中,每次只能从当前的格子移动到右边与它相邻的格子,或者 从当前格子移动到下面与它相邻的格子。
    例如:在下面这个矩形中,只有两条路径是合法的,它们分别是 P 1 P_1 P1 ( 0 , 0 ) (0,0) (0,0) ( 0 , 1 ) (0,1) (0,1) ( 1 , 1 ) (1,1) (1,1) P 2 P_2 P2 ( 0 , 0 ) (0,0) (0,0) ( 1 , 0 ) (1,0) (1,0) ( 1 , 1 ) (1,1) (1,1)
    在这里插入图片描述

对于一条合法的路径 P P P,我们可以用一个字符串 w ( P ) w(P) w(P)来表示,该字符串的长度为 n + m − 2 n + m - 2 n+m2,其中只包含字符“ R R R”或者字符“ D D D”, 第 i i i 个字符记录了路径 P P P 中第 i i i 步的移动方法,“ R R R”表示移动到当前格子右边与它相邻的格子,“ D D D”表示移动到当前格子下面 与它相邻的格子。例如,上图中对于路径 P 1 P_1 P1,有 w ( P 1 ) = w(P_1) = w(P1)=" R D RD RD";而对于另一条路径 P 2 P_2 P2, 有 w ( P 2 ) = w(P_2) = w(P2)=" D R DR DR"。

同时,将每条合法路径 P P P 经过的每个格子上填入的数字依次连接后,会得到一个长 度为 n + m − 1 n + m - 1 n+m1 01 01 01 字符串,记为 s ( P ) s(P) s(P)。例如,如果我们在格子 ( 0 , 0 ) (0,0) (0,0) ( 1 , 0 ) (1,0) (1,0)上填入数字 0 0 0,在格子 ( 0 , 1 ) (0,1) (0,1) ( 1 , 1 ) (1,1) (1,1)上填入数字 1 1 1(见上图红色数字)。那么对于路径 P 1 P_1 P1,我们可以得 到 s ( P 1 ) = s(P_1) = s(P1)=" 011 011 011",对于路径 P 2 P_2 P2,有 s ( P 2 ) = s(P_2) = s(P2)=" 001 001 001"。

游戏要求小 D D D 找到一种填数字 0 0 0 1 1 1 的方法,使得对于两条路径 P 1 P_1 P1 P 2 P_2 P2,如果 w ( P 1 ) &gt; w ( P 2 ) w(P_1) &gt; w(P_2) w(P1)>w(P2),那么必须 s ( P 1 ) ≤ s ( P 2 ) s(P_1) ≤ s(P_2) s(P1)s(P2)
我们说字符串 a a a 比字符串 b b b 小,当且仅当字符串 a a a 的字典序小于字符串 b b b 的字典序,字典序的定义详见第一题。但是仅仅是找一种方法无法满 足小 D D D 的好奇心,小 D D D 更想知道这个游戏有多少种玩法,也就是说,有多少种填数字 的方法满足游戏的要求?

D D D 能力有限,希望你帮助他解决这个问题,即有多少种填 0 0 0 1 1 1 的方法能满足题目要求。由于答案可能很大,你需要输出答案对 1 0 9 + 7 10^9 + 7 109+7取模的结果。

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

输出格式:
输出共一行,包含一个正整数,表示有多少种填 0 0 0 1 1 1 的方法能满足游戏的要求。 注意:输出答案对 1 0 9 + 7 10^9+7 109+7取模的结果。

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

T2 分析

oi一大经典类型题…暴力打表找规律。
先化简一下题意:
给定一个 n ∗ m n*m nm 网格棋盘,要求用 0 / 1 0/1 0/1填充每一个格子.
使得任意两条从点 ( 0 , 0 ) (0,0) (0,0) ( n − 1 , m − 1 ) (n−1,m−1) (n1,m1) 的不完全相交路径,先向右(字典序大)的路径的 01 01 01 路径字典序相对小.
首先确定一件事,对于任意一种填法,棋盘中填的数字一定满足 a [ i ] [ j ] ≥ a [ i − 1 , j + 1 ] a[i][j]≥a[i-1,j+1] a[i][j]a[i1,j+1],即从左下到右上的任意对角线中的数字一定是非递增序列。
当然这种题目…一看数据那么小,而且是固定答案的,遇事不决先暴力 …只要把 8 ∗ 8 8*8 88的表打出来基本就能过了…
当然对于dalao来说,一看 n ≤ 8 n≤8 n8,第一反应应该是状压才对…不应该是暴力…

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

当然比赛的时候写个暴力找规律没有任何问题… O ( 2 n m ) O(2^{nm}) O(2nm)直接上呗…
这时候会发现表很难打,打到 n = 7 n=7 n=7 以后就非常慢了,然后 n = 8 n=8 n=8基本上短时间内只能出一个 ( 8 , 8 ) (8,8) (8,8) 的答案
当然这里不管是打表还是画图,都可以发现第二件事,那就是对于 ( n , m ) (n,m) (n,m) 的答案和 ( m , n ) (m,n) (m,n) 是一样的,对称一下就行了。
这时候打一个以下的表…猜一下规律,会发现相邻的两项基本上都满足 ( i , j ) = ( i , j − 1 ) ∗ 3 (i,j)=(i,j-1)*3 (i,j)=(i,j1)3 的规律

  • n = 1 n=1 n=1时, A N S = 2 m ANS=2^m ANS=2m
  • n = 2 n = 2 n=2时, ( 2 , 2 ) = 12 , A N S = 4 ∗ 3 m − 1 (2,2)=12,ANS =4*3^{m-1} (2,2)=12,ANS=43m1
  • n = 3 n=3 n=3时, ( 3 , 3 ) = 112 , A N S = 112 ∗ 3 m − 3 (3,3)=112,ANS = 112*3^{m-3} (3,3)=112,ANS=1123m3
  • n = 4 n=4 n=4时, ( 4 , 4 ) = 912 (4,4)=912 (4,4)=912发现 ( 4 , 5 ) = ( 4 , 4 ) ∗ 3 − 3 ∗ 2 4 (4,5) =(4,4)*3-3 *2^4 (4,5)=(4,4)3324,之后的答案依旧满足上述的规律,即 A N S = 2688 ∗ 3 m − 5 ANS = 2688*3^{m-5} ANS=26883m5
  • n = 5 n=5 n=5时, ( 5 , 5 ) = 7136 (5,5)=7136 (5,5)=7136发现 ( 5 , 6 ) = ( 5 , 5 ) ∗ 3 − 3 ∗ 2 5 (5,6)=(5,5)*3-3*2^5 (5,6)=(5,5)3325,之后的答案依旧满足上述规律,即 A N S = 21312 ∗ 3 m − 6 ANS=21312*3^{m-6} ANS=213123m6
  • n = 6 n=6 n=6时, ( 6 , 6 ) = 56768 (6,6)=56768 (6,6)=56768发现 ( 6 , 7 ) = ( 6 , 6 ) ∗ 3 − 3 ∗ 2 6 (6,7)=(6,6)*3-3*2^6 (6,7)=(6,6)3326,之后的答案依旧满足上述规律,即 A N S = 170112 ∗ 3 m − 7 ANS=170112*3^{m-7} ANS=1701123m7
    其实这时候就算 n = 7 , 8 n=7,8 n=7,8后面的答案你的代码很难跑出来…猜也猜要猜一个规律上去…
    可以发现对于不满足常规规律是从 n = 4 n=4 n=4开始的,那么从 n = 4 n=4 n=4开始找规律,
    可以发现 ( 5 , 5 ) = ( 4 , 4 ) ∗ 8 − 5 ∗ 2 5 , ( 6 , 6 ) = ( 5 , 5 ) ∗ 8 − 5 ∗ 2 6 (5,5) = (4,4) * 8 -5*2^5,(6,6)=(5,5)*8-5*2^6 (5,5)=(4,4)8525,(6,6)=(5,5)8526
    那么可以猜测一下 ( 7 , 7 ) = ( 6 , 6 ) ∗ 8 − 5 ∗ 2 7 = 453504 , ( 8 , 8 ) = ( 7 , 7 ) ∗ 8 − 5 ∗ 2 8 = 3626752 (7,7)=(6,6)*8-5*2^7=453504,(8,8)=(7,7)*8-5*2^8=3626752 (7,7)=(6,6)8527=453504,(8,8)=(7,7)8528=3626752
  • n = 7 n=7 n=7时, ( 7 , 8 ) = ( 7 , 7 ) ∗ 3 − 3 ∗ 2 7 = 1360128 (7,8)=(7,7)*3-3*2^7=1360128 (7,8)=(7,7)3327=1360128,之后的答案即 A N S = 1360128 ∗ 2 m − 8 ANS=1360128*2^{m-8} ANS=13601282m8
  • n = 8 n=8 n=8时, ( 8 , 9 ) = ( 8 , 8 ) ∗ 3 − 3 ∗ 2 8 = 10879488 (8,9)=(8,8)*3-3*2^8=10879488 (8,9)=(8,8)3328=10879488,之后的答案即 A N S = 10879488 ∗ 2 m − 9 ANS=10879488*2^{m-9} ANS=108794882m9
    这样的话就做完了…并且可以用自己的暴力程序,开着代码一直跑,看看能不能满足自己找的规律,来告诉自己到底能估到几分,当然这个做法就是100分的…但是比赛时起码能保证自己可以获得65分,即 n ≤ 3 n≤3 n3的部分
    在这里插入图片描述
    这个表大概跑了半个小时左右吧, n = 8 n=8 n=8的情况暴力跑太慢了。
    /***********************************************/

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

  • ( n , m ) = ( m , n ) (n,m)=(m,n) (n,m)=(m,n)
  • 棋盘中填的数字一定满足 a [ i ] [ j ] ≥ a [ i − 1 , j + 1 ] a[i][j]≥a[i-1,j+1] a[i][j]a[i1,j+1],即从左下到右上的任意对角线中的数字一定是非递增序列;
  • 如果(i-1,j)和(i,j-1)的填数相同,那么以(i,j)为左上角、以(n,m)为右下角的子矩阵内所有对角线内的填数各自相等。

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

( 0 , 1 ) , ( 1 , 0 ) (0,1),(1,0) (0,1),(1,0) 两格相同:

  • 这种情况下,只需要使得 ( 1 , 1 ) → ( n , m ) (1,1)→(n,m) (1,1)(n,m)的所有对角线上数字相同.
  • 同时第一列和第一行会受其影响,画一画图,加上推一波式子可以发现,答案形如 4 x ∗ 3 y ∗ 2 z 4^x∗3^y∗2^z 4x3y2z

( 0 , 1 ) , ( 1 , 0 ) (0,1),(1,0) (0,1),(1,0) 两格不同:

  • ( 0 , 2 ) , ( 1 , 1 ) , ( 2 , 0 ) (0,2),(1,1),(2,0) (0,2),(1,1),(2,0) 相同:
    这种情况类似第一种情况
  • ( 0 , 2 ) , ( 1 , 1 ) , ( 2 , 0 ) (0,2),(1,1),(2,0) (0,2),(1,1),(2,0)其中一对相同:
    f [ i ] f[i] f[i] 表示到第 i i i 条对角线上时,在前两行的第 i − 1 i−1 i1 条斜线上及以前有过匹配的方案数。
    即在两行中,第二行的某一格与其斜上方的格相同,一旦出现这种情况接下来第二行的填数就会有限制.
    当第一行和第二行不存在这种情况的时候,当前的一条对角线实际上有 5 5 5 种填法,而当第 i i i 条对角线上出现这种情况的时候,则只有 4 4 4 种填法.
    那么考虑一下转移就可以写出:
    f [ i ] = f [ i − 1 ] ∗ 4 + 4 ∗ 5 f[i]=f[i−1]∗4+4∗5 f[i]=f[i1]4+45

    (在 i − 2 i−2 i2 条斜线上以前就匹配成功的方案 f [ i − 1 ] f[i−1] f[i1]) * (当前第 i i i条对角线的 4 4 4 种填法) + ( i − 1 i−1 i1 条斜线第一次匹配成功的 4 4 4 种填法) * (当前第 i i i 条对角线因不受任何影响所以有 5 5 5 种填法.)
    在这里插入图片描述

PC画图太麻烦了,这里给出 ( 3 , 3 ) (3,3) (3,3) 的图
按照同样的方法可以画出接下去的几个图
( 3 , 3 ) = 4 1 ∗ 3 1 ∗ 2 2 + 4 1 ∗ 3 0 ∗ 2 4 (3,3)=4^1*3^1*2^2 + 4^1*3^0*2^4 (3,3)=413122+413024
( 4 , 4 ) = 5 1 ∗ 4 0 ∗ 3 0 ∗ 2 5 + 5 1 ∗ 4 0 ∗ 3 1 ∗ 2 4 + 5 0 ∗ 4 2 ∗ 3 0 ∗ 2 5 (4,4)=5^1 * 4^0 * 3^0 * 2^5 + 5^1*4^0*3^1*2^4 + 5^0 * 4^2*3^0*2^5 (4,4)=51403025+51403124+50423025
( 5 , 5 ) = 5 1 ∗ 4 1 ∗ 3 0 ∗ 2 7 + 5 1 ∗ 4 0 ∗ 3 1 ∗ 2 5 + 5 0 ∗ 4 3 ∗ 3 0 ∗ 2 6 (5,5)=5^1 * 4^1 * 3^0 * 2^7 + 5^1*4^0*3^1*2^5 + 5^0 * 4^3*3^0*2^6 (5,5)=51413027+51403125+50433026
( 6 , 6 ) = 5 1 ∗ 4 1 ∗ 3 2 ∗ 2 7 + 5 1 ∗ 4 0 ∗ 3 1 ∗ 2 6 + 5 0 ∗ 4 4 ∗ 3 0 ∗ 2 7 (6,6)=5^1 * 4^1 * 3^2 * 2^7 + 5^1*4^0*3^1*2^6 + 5^0 * 4^4*3^0*2^7 (6,6)=51413227+51403126+50443027

这样就可以计算出当 n = m n=m n=m的答案了.
m = n + 1 m=n+1 m=n+1 时,前两种情况是很容易推下去的。
按照第三种情况,根据最后一条对角线是否匹配成功进行讨论.
不难推出另外一种的答案实质上是
A N S = ( f [ n ] ∗ 3 + 4 ∗ 5 ) ∗ 2 n ANS=(f[n]∗3+4∗5)∗2^n ANS=(f[n]3+45)2n
当推过几次式子之后可以发现,也满足上面打表所说的规律, m m m 每次 + 1 +1 +1 会让答案 ∗ 3 *3 3

#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 国有 n n n座城市, n − 1 n - 1 n1条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。
Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:
一座城市可以驻扎一支军队,也可以不驻扎军队。
由道路直接连接的两座城市中至少要有一座城市驻扎军队。
在城市里驻扎军队会产生花费,在编号为i的城市中驻扎军队的花费是 p i p_i pi
小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出 了mm个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一 给出回答。具体而言,如果国王提出的第 j j j个要求能够满足上述驻扎条件(不需要考虑 第 j j j 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果 国王提出的第 j j j个要求无法满足,则需要输出 − 1 ( 1 ≤ j ≤ m ) -1 (1 ≤ j ≤ m) 1(1jm)。现在请你来帮助小 Z。

输入输出格式
输入格式:
1 1 1 行包含两个正整数 n , m n,m n,m和一个字符串 t y p e type type,分别表示城市数、要求数和数据类型。 t y p e type type是一个由大写字母 A A A B B B C C C 和一个数字 1 1 1 2 2 2 3 3 3 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。这个参数的含义在【数据规模与约定】中 有具体的描述。

2 2 2 n n n个整数 p i p_i pi,表示编号 i i i的城市中驻扎军队的花费。
接下来 n − 1 n - 1 n1 行,每行两个正整数 u , v u,v u,v,表示有一条 u u u v v v的双向道路。
接下来 m m m 行,第jj行四个整数 a , x , b , y ( a ≠ b ) a,x,b,y(a ≠ b) a,x,b,y(a̸=b),表示第jj个要求是在城市 a a a驻扎 x x x支军队, 在城市 b b b驻扎yy支军队。其中, x x x y y y 的取值只有 0 0 0 1 1 1:若 x x x 0 0 0,表示城市 a a a 不得驻 扎军队,若 x x x 1 1 1,表示城市 a a a 必须驻扎军队;若 y y y 0 0 0,表示城市 b b b不得驻扎军队, 若 y y y 1 1 1,表示城市 b b b 必须驻扎军队。

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

输出格式:
输出共 m m m 行,每行包含 1 1 1 个整数,第 j j j行表示在满足国王第 j j j个要求时的最小开销, 如果无法满足国王的第 j j j个要求,则该行输出 − 1 -1 1

输入输出样例
输入样例#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模板题,甚至还能实现一下传说中的“全局平衡二叉树”…
简述一下题意:对一颗 n n n个点的树进行染色,对第 i i i个点染色的花费是 p i p_i pi,要求相邻的两个点之间必须有一个点被染色,然后给出 m m m组询问,每次询问强制对两个点固定状态(染色或不染色)

先考虑比赛时的做法:乍一看不会做怎么办…我们暴力一发O(nm)再说…
对于每个点可以选或不选,所以我们用:
{ d p [ i ] [ 1 ] 表 示 以 i 为 根 的 子 树 , 在 i 节 点 选 择 时 的 最 小 费 用 d p [ i ] [ 0 ] 表 示 以 i 为 根 的 子 树 , 在 i 节 点 不 选 时 的 最 小 费 用 \begin{cases} dp[i][1]表示以 i 为根的子树,在 i 节点选择时的最小费用\\ dp[i][0]表示以 i 为根的子树,在 i 节点不选时的最小费用 \end{cases} {dp[i][1]iidp[i][0]ii
同时,还要求相邻的两个节点至少有一个要选,所以如果 i i i 不选,则 i i i 的所有叶节点必须都选,如果 i i i 选择,则叶节点没有限制。

以此得到状态转移( j j j i i i 的叶节点, a [ i ] a[i] a[i] i i i 节点的权值):
{ d p [ i ] [ 1 ] = ∑ m i n ( d p [ j , 1 ] , d p [ j , 0 ] ) + a [ i ] d p [ i ] [ 0 ] = ∑ d p [ j , 1 ] \begin{cases} dp[i][1]=\sum min(dp[j,1],dp[j,0])+a[i]\\ dp[i][0]=\sum dp[j,1] \end{cases} {dp[i][1]=min(dp[j,1],dp[j,0])+a[i]dp[i][0]=dp[j,1]
对于每个询问,若必选则将权值修改为 0 0 0,若不选则将权值修改为 I N F INF INF,最后输出 m i n ( d p [ 1 , 1 ] , d p [ 1 , 0 ] ) min(dp[1,1],dp[1,0]) min(dp[1,1],dp[1,0])
时间复杂度 O ( n m ) O(nm) O(nm),44分。

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

假设修改x,y两个点,观察一下对整棵树的影响
1 1 1表示根节点, F F F表示 x , y x,y x,y的最近公共祖先, A A A表示 F F F不包含 x , y x,y x,y的子树, B B B表示根节点 1 1 1不包含 x , y x,y x,y的子树, C , D C,D C,D分别表示 x , y x,y x,y的子树
在这里插入图片描述
我们可以发现,对于x,y修改,对整棵树的影响只有 x → 1 和 y → 1 x→1和y→1 x1y1这两条路径上的值。其他点的值都是不会修改的。
所以其实我们对于每个询问需要做的就是修改 x , y x,y x,y后,将 x , y x,y x,y的值重新更新到根节点 1 1 1
因为在结果上我们需要更新的路径可以分为三段: x → L , y → L , L → 1 x→L,y→L,L→1 xLyLL1,即 x , y x,y x,y到达 L C A ( x , y ) LCA(x,y) LCA(x,y),再从 L C A ( x , y ) LCA(x,y) LCA(x,y)到达根节点 1 1 1
所以考虑维护一个数组 f [ x ] [ 0 / 1 ] [ y ] [ 0 / 1 ] f[x][0/1][y][0/1] f[x][0/1][y][0/1]表示x不选/选,y不选/选时,x到y这条路径上能得到的最大的dp值.
接下去就可以对询问处理答案了,但是这里需要的空间是 ( n 2 ) (n^2) (n2)所以我们考虑修改一下记录方式。因为这个数组维护的目的其实也是使得 x , y x,y x,y到达 L C A ( x , y ) LCA(x,y) LCA(x,y),所以我们用倍增的方式来优化这个数组。
即将数组维护成 f [ x ] [ 0 / 1 ] [ i ] [ 0 / 1 ] f[x][0/1][i][0/1] f[x][0/1][i][0/1]表示 x x x 不选/选,从 x x x 往上跳 2 i 2^i 2i 步的节点不选/选时,这条路上能得到的最大dp值。
接下去考虑一件事情,因为 x → L x→L xL这条路径中,可能还存在一些点,这些点也存在它们的子树,而这个方程是没有办法考虑到这件事情的,所以我们需要重新定义方程:
f [ u ] [ 0 / 1 ] [ i ] [ 0 / 1 ] f[u][0/1][i][0/1] f[u][0/1][i][0/1]表示 x x x 不选/选,从 x x x 往上跳 2 i 2^i 2i 步的节点不选/选时,这条路上能得到的最大dp值 且计入其他子树的最终dp值。即在整个祖先节点的子树中减去 x x x 的子树的影响。
然后我们得到转移方程:
{ f [ u ] [ 0 ] [ i ] [ 0 ] = m i n ( 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 ] = m i n ( 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 ] = m i n ( 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 ] = m i n ( f [ u ] [ 1 ] [ i − 1 ] [ 0 ] + f [ F ] [ 0 ] [ i − 1 ] [ 1 ] , f [ u ] [ 1 ] [ i − 1 ] [ 1 ] + f [ F ] [ 1 ] [ i − 1 ] [ 1 ] ) ; \begin{cases} 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]); \end{cases} f[u][0][i][0]=min(f[u][0][i1][0]+f[F][0][i1][0],f[u][0][i1][1]+f[F][1][i1][0]);f[u][0][i][1]=min(f[u][0][i1][0]+f[F][0][i1][1],f[u][0][i1][1]+f[F][1][i1][1]);f[u][1][i][0]=min(f[u][1][i1][0]+f[F][0][i1][0],f[u][1][i1][1]+f[F][1][i1][0]);f[u][1][i][1]=min(f[u][1][i1][0]+f[F][0][i1][1],f[u][1][i1][1]+f[F][1][i1][1]);
样我们可以直接把 x , y x,y x,y L L L 的路径上的 f 数组加起来,并加上 x , y x,y x,y 的子树的贡献,再加上从 L L L 开始到根节点 1 1 1的贡献,再加上子树 A 和 B A和B AB的贡献,就是最终的答案了。

当然这里还存在一些小问题,比如我们要用新的 x x x d p dp dp值更新出新的 x x x的父亲 f a t h e r father father d p dp dp值时,要计算 f a t h e r father father别的子树的贡献,它保存在 d p [ 0 / 1 ] [ f a ] dp[0/1][fa] dp[0/1][fa]中,但是 d p [ 0 / 1 ] [ u ] dp[0/1][u] dp[0/1][u]即我们当前要替换的旧的 d p dp dp值也算在里面了
设新的 x x x d p dp dp值为 N e w New New,新的 f a t h e r father father d p dp dp值即为
{ l 0 = d p [ 0 ] [ L ] − d p [ 1 ] [ u ] − d p [ 1 ] [ v ] + u 1 + v 1 ; l 1 = d p [ 1 ] [ L ] − m i n ( d p [ 0 ] [ u ] , d p [ 1 ] [ u ] ) − m i n ( d p [ 0 ] [ v ] , d p [ 1 ] [ v ] ) + m i n ( u 0 , u 1 ) + m i n ( v 0 , v 1 ) ; \begin{cases} l0 = dp[0][L] - dp[1][u] - dp[1][v] + u1 + v1;\\ l1 = dp[1][L] - min(dp[0][u],dp[1][u]) - min(dp[0][v],dp[1][v]) + min(u0,u1) + min(v0,v1); \end{cases} {l0=dp[0][L]dp[1][u]dp[1][v]+u1+v1;l1=dp[1][L]min(dp[0][u],dp[1][u])min(dp[0][v],dp[1][v])+min(u0,u1)+min(v0,v1);
然后两个特殊情况:

  1. x , y x,y x,y在一条链上时,倍增 x x x之后已经在 L L L上了,此时直接倍增 L L L即可;
  2. L C A ( x , y ) LCA(x,y) LCA(x,y)为根节点时,显然不需要倍增 L L L,此时直接算出总答案。

还有个小问题… I N F INF INF不要用 0 x 3 f 3 f 3 f 3 f 3 f 3 f 3 f 3 f 0x3f3f3f3f3f3f3f3f 0x3f3f3f3f3f3f3f3f…要用 100000 ∗ 100000 再 多 补 个 0 100000*100000再多补个0 1000001000000

#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;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值