雅礼集训10.24小结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ha_ing/article/details/102727583

T1:中央集权(emperor)

Description
今天是猪国国王的 inf 岁生日,所以国王希望将自己所统治的星球上所有官员(每个星球上都有 1 个官员)都召回到自己所在的星球(猪星)上。星球与星球之间的交通依靠传送阵来维系,而这些传送阵的开启需要灵石(某种神秘的能源)。很不幸,出于种种原因,所有的官员都身无分文(受到了猪王的剥削),所以传送的费用都需要猪王来支付。猪王看着即将空了的国库以及里面最后的 1e666 颗灵石(国库还真是空啊),觉得自己需要精打细算了,于是请求聪明的你快速地算出让所有官员都到达猪星最少需花费多少灵石,以便安排。值得注意的是,传送阵的传送不限人数,即一个人传送与两个人传送所需灵石相同。

Input Format
从文件 emperor.in中读入数据。
​ 第 1行 3 个正整数:n,m,s
​ n表示星球数(星球由 1 到 n 编号),m 表示传送阵数,s
表示猪星所在位置。
​ 第 2 至 m+1行,每行 3 个正整数:x,y,z
​ 表示有一座从 x星到 y 星的双向传送阵,需花费 z 块灵石。

Output Format
输出到文件 emperor.out中。
​ 如无法使所有官员都到达猪星,输出−1。
​ 否则第 1行输出 1 个正整数,为最小灵石花费。

Sample Input 1

3 3 1
1 2 3
2 3 2
1 3 1

Sample Output 1

3

【样例 1 解释】
​ 先将 2号星球的 1 个官员传送到 3 号星球,花费为 2。再将 3 号星球上的 2 个官员传送到 1 号星球,花费为 1。故总共花费 3 块灵石。
Sample Input 2

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

Sample Output 2

11

Constraints
对于 30%的数据:n≤100,m≤500
对于 60%的数据:n≤3000,m≤10000
对于 100%的数据:n≤50000,m≤500000,0<z≤10000
数据保证所有正整数都在 int范围内,可能存在重边与自环。

简要思路:这道题确实很水,只是一个最小生成树的模板题,只要按照题意将官员从叶子节点运到根节点,并带上路过节点的官员即可。这题有一点要注意,就是本题可能根本就不能生成最小生成树,要统计树边来判断一下。

扫描二维码关注公众号,回复: 7590511 查看本文章
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 50005
#define M 500005
#define ll long long
using namespace std;
int f[N];
struct node{
	int x;
	int y;
	int val;
}str[M];
int n , m , s;
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline int find( int x ) {
	if ( x == f[x] ) {
		return x;
	} else {
		return f[x] = find( f[x] );
	}
}
inline bool cmp( node a , node b ) {
	return a.val < b.val;
}
int main () {
	freopen( "emperor.in" , "r" , stdin );
	freopen( "emperor.out" , "w" , stdout );
	read(n);
	read(m);
	read(s);
	for ( int i = 1 ; i <= n ; ++i ) {
		f[i] = i;
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		read(str[i].x);
		read(str[i].y);
		read(str[i].val);
	}
	sort( str + 1 , str + 1 + m , cmp );
	ll sum = 0;
	int ncnt = 1; 
	for ( int i = 1 ; i <= m ; ++i ) {
		int x = str[i].x;
		int y = str[i].y;
		int fx = find(x);
		int fy = find(y);
		if ( fx != fy ) {
			ncnt++;
			f[fx] = fy;
			sum += (ll)str[i].val;
		}
	}
	if ( ncnt < n ) {
		printf("-1");
	} else {
		printf("%lld",sum);
	}
	return 0;
}

T2:计算树(tree)

Description
这是一道卡常题
​ 由于数据规模过大,故减小规模,并压缩时限
​ 众所周知,(猪国)国王精通数学。这一天,他捣鼓出了一颗神奇的树。
国王构造出的树当然符合树的形态特点,树上有n
个节点,每个节点有一个编号,一个权值Vi,每条边上有一个运算符( + × +-\times 中的一个)。
​ 国王造好树之后很兴奋,向他的首辅CraZYali展示成果。
​ 国王提出了m个问题,即询问从树上节点A到节点B这条路径上所形成的表达式的值为多少,对19491001取模。
​ 首辅CraZYali当然知道怎么做,并且很快想出了O(nlogn)的做法。
​ 但他显然还没想到卡常的办法,所以希望这个复杂度能再好些。
​ 注意:在猪国的运算规则中, + × + \times 的运算优先级相同。​保证 19491001 ∤ v i 19491001 \not | \forall v_i

Input Format
第1行2个整数:n,m
​ 第2行n个整数: v i v_i 表示节点i的权值
​ 下n−1行每行3个整数:x,y,z,表示节点x,y之间有连边,
​ z=1:符号为 + +
​ z=2:符号为 -
​ z=3:符号为 × \times
​ 再下接m行,每行2个整数:x,y,表示国王询问路径x,y表达式的值

Output Format
m行,每行输出一个与计算结果同余的最小非负整数,表示对本次询问的回答。

Sample Input

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

Sample Output

12
22
90

【样例解释】
​ 从4号节点到5号节点路径形成的表达式为:(5−3)×6=12
​ 从5号节点到3号节点路径形成的表达式为:6×3×1+4=22
​ 从3号节点到5号节点路径形成的表达式为:(4+1)×3×6=90

Constraints
对于前10%的数据:n≤100,m≤100
​ 对于前30%的数据:n≤103,m≤103
​ 对于前50%的数据:n≤103,m≤105
​ 对于另外30%的数据:树是一条链
​ 对于100%的数据:n≤3×105,m≤4×10 ^ 5,−2×10 ^9≤∀Vi≤2×10 ^ 9且Vi≠0
​ 注意:本题数据规模较大,建议使用较快的读入方式。(或许需要优秀的lca求法与逆元求法)
​ 再次重申:本题时限0.6s

简要思路:根据出题人的意思,这题可用 O ( n ) O(n) 算法跑过,连 O ( n l g n ) O(nlgn) 都会被卡。
然而实际上,出题人的标程常数过大,所以……
好了,言归正传,由于在树边上,只会进行加减乘运算,不难发现,带上一定的初值经过一些边和点时,运算结果均可用 y = k x + b y=k*x+b 来表示,其中,x是初值。
接下来,我们要想办法来预处理k和b,不难发现我们要同时处理某个点向上到根节点以及根节点向下到达该点时得出的k与b,用结构体up和down来保存。

预处理一般是从上到下,因此处理时相对down是顺序,而相对up是倒序。
接下来,我们假设某点父节点保存的值为 y = k x + b y=k*x+b ,现在要处理到该点。
处理加法时,对于down,因为是顺序运算,直接让b加上该点的值即可。

对于up,要考虑先前处理得到的k的影响,因为实际运算是从下到上的,要让b加上k乘以该点父节点点值的结果。

处理减法时,原理同上,把加上改为减去即可。

处理乘法时,对于down,因为是顺序运算,要把k乘以该点点值,同时前面的b值也参与乘法,也是乘以该点点值。

对于up,逆序运算反而省事了,预先处理的b值实际上用在后面,与这次乘法运算无关,只要k值乘以该点父节点点值即可。

搞定了上述状态转移,在去求询问时两点的LCA。这里,我们假设两点为u,v,LCA为w。设u到根节点的状态转移为 y = a x + b y=a*x+b ,w到根节点状态转移为 y = c x + d y=c*x+d ,则u到w的状态转移为 y = e x + f y=e*x+f ,那么原来的 y = a x + b = c ( e x + f ) + d = c e x + c f + d y=a*x+b=c*(e*x+f)+d=c*e*x+c*f+d
所以 a = c e a=c*e b = c f + d b=c*f+d
w到v的同理。
这里要用到逆元,线性处理1到mod-1的逆元复杂度过高,可以考虑处理1到 m o d 1 \sqrt{mod-1} 的逆元,剩下的记忆化搜索一步就可以到位了。
这样我们可用预处理得到的信息得出树上每一段的状态转移,最后将u到w,w到v的合并即可。
最后说一句,求LCA最好用tarjan(参见代码),不要倍增,满足算法 O ( n ) O(n) 的复杂度。
就这样,一个大常数完美 O ( n ) O(n) 算法出炉了。
还不懂就看代码吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#define mod 19491001
#define N 300005
#define M 400005
#define ll long long
using namespace std;
int n , m , bcnt , bcnt1;
struct node{
	int next;
	int to;
	int val;
}str[N << 1];
struct node1{
	int next;
	int to;
}str1[M << 1];
struct QUERY{
	int x;
	int y;
	int lca;
}q[M];
struct CAL{
	int a;
	int b;
}up[N] , down[N];
int head[N] , head1[N] , f[N] , vis[N] , va[N] , inv[mod + 1];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline void insert( int from , int to , int val ) {
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	str[bcnt].val = val;
	return;
}
inline void insert1( int from , int to ) {
	str1[++bcnt1].next = head1[from];
	head1[from] = bcnt1;
	str1[bcnt1].to = to;
	return;
}
inline int find( int x ) {
	if ( x == f[x] ) {
		return x;
	} else {
		return f[x] = find(f[x]);
	}
}
inline void pre() {
	inv[0] = 1;
	inv[1] = 1;
	for ( int i = 2 ; i <= 4415 ; ++i ) {
		inv[i] = 1ll * (-mod / i) * inv[mod % i] % mod;
	}
	return;
}
inline int getinv( int x ) {
	x = ( x % mod + mod ) % mod;
	if ( inv[x] ) {
		return inv[x];
	} else {
		return inv[x] = 1ll * (-mod / x) * getinv( mod % x ) % mod;
	}
}
inline void dfs( int cur , int fa ) {
	vis[cur] = 1;
	f[cur] = cur;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( vis[sn] ) {
			continue;
		}
		dfs( sn , cur );
		f[sn] = cur;
	}
	for ( int i = head1[cur] ; i ; i = str1[i].next ) {
		int sn = str1[i].to;
		if ( !vis[sn] ) {
			continue;
		}
		q[i >> 1].lca = find(sn);
	}
	return;
}
inline void dfs1( int cur , int fa ) {
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( sn ^ fa ) {//一个快速判断不等的技巧 
			up[sn] = up[cur];
			down[sn] = down[cur];
			switch( str[i].val ) {
				case 1 : {
					up[sn].b = ( up[sn].b + 1ll * va[cur] * up[cur].a % mod ) % mod;
					down[sn].b = ( down[sn].b + 1ll * va[sn] ) % mod;
					break;
				}
				case 2 : {
					up[sn].b = ( up[sn].b - 1ll * va[cur] * up[cur].a % mod ) % mod;
					down[sn].b = ( down[sn].b - 1ll * va[sn] ) % mod;
					break;
				}
				case 3 : {
					down[sn].a = ( 1ll * down[sn].a * va[sn] ) % mod;
					down[sn].b = ( 1ll * down[sn].b * va[sn] ) % mod;
					up[sn].a = ( 1ll * up[sn].a * va[cur] ) % mod;
					break;
				}
			}
			dfs1( sn , cur );
		}
	}
	return;
}
int main () {
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	read(n);
	read(m);
	bcnt1 = 1;
	pre();
	for ( int i = 1 ; i <= n ; ++i ) {
		read(va[i]);
		va[i] %= mod;
	}
	int u , v , w;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		read(u);
		read(v);
		read(w);
		insert( u , v , w );
		insert( v , u , w );
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		read(q[i].x);
		read(q[i].y);
		insert1( q[i].x , q[i].y );
		insert1( q[i].y , q[i].x );
	}
	dfs( 1 , 0 );
	up[1].a = 1;
	up[1].b = 0;
	down[1].a = 1;
	down[1].b = 0;
	dfs1( 1 , 0 );
	CAL tem1 , tem2;
	int a , b , c , d;
	for ( int i = 1 ; i <= m ; ++i ) {
		tem1 = up[q[i].x];
		tem2 = up[q[i].lca];
		a = 1ll * tem1.a * getinv( tem2.a ) % mod;
		b = 1ll * ( tem1.b - tem2.b ) % mod * getinv( tem2.a ) % mod;
		if ( q[i].x == q[i].lca ) {
			a = 1;
			b = 0;
		}
		tem1 = down[q[i].y];
		tem2 = down[q[i].lca];
		c = 1ll * tem1.a * getinv( tem2.a ) % mod;
		d = ( tem1.b - 1ll * tem2.b * c % mod ) % mod;
		if ( q[i].y == q[i].lca ) {
			c = 1;
			d = 0;
		}
		a = 1ll * a * c % mod;
		b = ( 1ll * b * c % mod + d ) % mod;
		printf("%lld\n",( ( 1ll * a * va[q[i].x] % mod + b % mod ) % mod + mod ) % mod );
	}
	return 0;
}

T3:解谜(puzzle)

Description
这是一道数学题
(猪国)国王在研究著名的《猪国算经》。发现了一道有趣的谜题。
​ 谜题的内容如下:
i = 1 n 2 j = i n i ( n i j ) \sum_{i=1}^{\lfloor \frac{n}{2} \rfloor} \sum _{j=i}^{n-i}\binom{n-i}{j}
本来有一个精彩的问题描述,可是版面太小写不下。

Input Format
​ 一行1个整数T,​ 下接T行,每行一个询问n。

Output Format
T行,对于每个询问,输出答案 mod 998244353 的值。

Sample Input

5
1
2
3
4
5

Sample Output

0
1
3
8
19

Constraints
对于前10%的数据:n≤10
​ 对于前30%的数据:n≤10 ^ 3
​ 对于前60%的数据:n≤10 ^ 7
​ 对于100%的数据:n≤10 ^ 18,T≤10 ^ 4

简要思路:这题就是一道考验灵感 手气的数学题了。不过这题其实有一个比较容易理解的推法。
图1
不难发现,当n=6时,答案就是以上杨辉三角黑线以下的数字总和。
整个杨辉三角的和为 2 + 1 1 2^{层数+1}-1 ,这里层数为5,所以和为 2 n 1 2^n-1
接下来,就要求解杨辉三角上半部分之和,不难发现用红线划分后,刚好形成斐波那契数列。
证明也很简单,杨辉三角三角上任意的一个数已知为 ( i j ) \binom{i}{j} ,在某条红线上,可表示为 ( i 1 j 1 ) + ( i 1 j ) \binom{i-1}{j-1}+\binom{i-1}{j} ,第一个数为上上条红线的数,第二个为上条红线的数,不难看出这就是斐波那契数列。
接下来不难得出递推公式 A n = 2 n 1 S ( n ) A_n=2^n-1-S(n) ,其中 S ( n ) S(n) 为斐波那契数列的前n项和。
这里有一个定理: S ( n ) = f ( n + 2 ) 1 S(n)=f(n+2)-1
证明:数学归纳法,当n=1时,式子成立;
当n=k时成立,则 S ( k + 1 ) = S ( k ) + f ( k + 1 ) = f ( k + 2 ) 1 + f ( k + 1 ) = f ( k + 3 ) 1 S(k+1)=S(k)+f(k+1)=f(k+2) - 1+f(k+1)=f(k+3)-1 ,所以n=k+1也成立。
最终得出 A n = 2 n 1 ( f ( n + 2 ) 1 ) = 2 n f ( n + 2 ) A_n=2^n-1-(f(n+2)-1)=2^n-f(n+2)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define mod 998244353
#define N 10005
using namespace std;
struct M{
	int a[3][3];
	friend M operator * ( M x , M y ) {
		M c;
		memset( c.a , 0 , sizeof(c.a) );
		for ( int i = 1 ; i <= 2 ; ++i ) {
			for ( int j = 1 ; j <= 2 ; ++j ) {
				for ( int k = 1 ; k <= 2 ; ++k ) {
					c.a[i][j] = ( ( (ll)c.a[i][j] + (ll)x.a[i][k] * y.a[k][j] ) % mod + mod ) % mod;
				}
			}
		}
		return c;
	}
	friend M operator ^ ( M tem , ll x ) {
		M c;
		memset( c.a , 0 , sizeof(c.a) );
		c.a[1][1] = c.a[2][2] = 1;
		while ( x ) {
			if ( x & 1 ) {
				c = c * tem;
			}
			tem = tem * tem;
			x >>= 1;
		}
		return c;
	}
}matrix;
int t;
ll num , ans;
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline ll qpow( ll a , ll b ) {
	int ans = 1;
	while ( b ) {
		if ( b & 1 ) {
			ans = ( ans * a ) % mod;
		}
		a = ( a * a ) % mod;
		b >>= 1;
	}
	return ans;
}
void cc() {
	for ( int i = 1 ; i <= 2 ; ++i ) {
		for ( int j = 1 ; j <= 2 ; ++j ) {
			cout << matrix.a[i][j] << " ";
		}
		cout << endl;
	}
}
int main () {
	freopen( "puzzle.in" , "r" , stdin );
	freopen( "puzzle.out" , "w" , stdout );
	read(t);
	matrix.a[1][1] = 1;
	matrix.a[1][2] = 1;
	matrix.a[2][1] = 1;
	matrix.a[2][2] = 0;
	for ( int i = 1 ; i <= t ; ++i ) {
		read(num);
		ll tem = num + 1;//这里是从第二项开始的,所以只加一
		matrix = matrix ^ tem;
		//cc();
		ans = matrix.a[1][1];
		printf("%lld\n",( ( 1ll * qpow( 2ll , num ) - ans ) % mod + mod ) % mod );
		matrix.a[1][1] = 1;
		matrix.a[1][2] = 1;
		matrix.a[2][1] = 1;
		matrix.a[2][2] = 0;
	}
	return 0;
}

应该差不多了

猜你喜欢

转载自blog.csdn.net/ha_ing/article/details/102727583