[选拔赛2 NOIP2018雅礼集训 Day3 u,v,w]玩个三角形(二维差分),玩个球(状压DP+map),玩个树(树上DP)

T1:玩个三角形

title

题目描述

考虑一个 n ∗ n 的矩阵 A,初始所有元素均为 0。

执行 q 次如下形式的操作:给定 4 个整数 r, c, l, s,对于每个满足 x ∈ [r, r+l), y ∈ [c, x-r+c] 的元素 (x, y),将权值增加 s。也就是,给一个左上顶点为 (r, c)、直角边长为 l 的下三角区域加上 s。

输出最终矩阵的元素异或和。
输入格式

第一行两个整数 n, q。

接下来 q 行,每行四个整数 r, c, l, s,代表一次操作。
输出格式

输出一行,一个整数,代表答案。
样例
样例输入

10 4
1 1 10 1
5 5 4 4
1 9 4 3
3 3 5 2

样例输出

0

样例解释

1 0 0 0 0 0 0 0 3 0
1 1 0 0 0 0 0 0 3 3
1 1 3 0 0 0 0 0 3 3
1 1 3 3 0 0 0 0 3 3
1 1 3 3 7 0 0 0 0 0
1 1 3 3 7 7 0 0 0 0
1 1 3 3 7 7 7 0 0 0
1 1 1 1 5 5 5 5 0 0
1 1 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 1 1

数据范围与提示

对于100%的数据,保证 n ∈ [1, 103],q ∈ [0, 3 ∗ 105],r, c, l ∈ [1, n],s ∈ [1, 109]。

无标题.jpg

solution

考虑用二维树组差分维护,如果是个矩形就是特别简单的了,可惜是个直角三角形
那么就拆一下,用两个二维数组,一个专门维护单列,一个专门维护斜边
在这里插入图片描述

code

#include <cstdio>
#define MAXN 2005
#define ll long long
int n, Q;
ll ans;
ll col[MAXN][MAXN], slope[MAXN][MAXN], matrix[MAXN][MAXN];

int main() {
	scanf( "%d %d", &n, &Q );
	for( int i = 1, r, c, l, s;i <= Q;i ++ ) {
		scanf( "%d %d %d %d", &r, &c, &l, &s );
		col[r][c] += s, col[r + l][c] -= s;
		slope[r][c + 1] += s, slope[r + l][c + l + 1] -= s;
	}
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= n;j ++ )
			col[i][j] += col[i - 1][j];
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= n;j ++ )
			slope[i][j] += slope[i - 1][j - 1];
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= n;j ++ )
			matrix[i][j] += matrix[i][j - 1] + col[i][j] - slope[i][j];
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= n;j ++ )
			ans ^= matrix[i][j];
	printf( "%lld", ans );
	return 0;
}

T2:玩个球

title

有 n 个球排成一行,每个球的颜色为黑或白。

执行 k 次操作,第 i(1 ≤ i ≤ k) 次操作形式如下:

• 从 [1, n-i+1] 中,等概率随机选择一个整数 x。

• 移除从左往右数的第 x 个球,或从右往左数的第 x 个球(也就是从左往右数的第 n-i+2-x 个)。之后,所有右侧的球的编号减 1。

给定每个球的颜色信息,希望最大化移除的白球数量。

输出在最优策略下,期望的移除白球数量。误差在1e-6范围内,即算正确

输入格式

第一行,两个整数 n, k。 第二行,一个长度为 n、仅由 ‘W’ 和 ‘B’ 组成的字符串,第 i 个字符代表第 i 个球的颜色, ‘W’ 为白色, ‘B’ 为黑色。
输出格式

输出一行,一个浮点数,代表答案。
样例
样例输入1

3 1
BWW

样例输出1

1.0000000000

样例解释1

如果 x = 1,从右侧操作,如果 x = 2 或 3,从左侧操作,均可以移除一个白球。

样例输入2

4 2
WBWB

样例输出2

1.5000000000

数据范围与提示

对于100%的数据,保证 1 ≤ n ≤ 30,0 ≤ k ≤ n
在这里插入图片描述

solution

n n n怎么这么小?!!准定要搞事!!
老子反手就是一个状压,压不死你
在这里插入图片描述


但是仔细一想, 2 30 2^{30} 230好像逼近 i n t int int极限值
在这里插入图片描述
不要担心,一般这种大数据数组存不下,就找蓝翔技术学校 S T L STL STL,这些容器关键时刻还是不会掉链子的,这里可以采取 m a p map map与数组进行分工合作,数组存不下的就用 m a p map map去存
在这里插入图片描述

这道题仔细一看,就是个披着期望皮的记忆化搜索羊
在这里插入图片描述
为什么呢?很简单,你想嘛~
当后面剩下的颜色状态一样时,操作的概率与期望计算方式与之前肯定是一样的,答案也肯定一样
那我们为什么要傻逼兮兮的怼上去再来一遍??


接下来我将重点讲解代码中贼冗长的一行代码的意思,因为实在是太妙了!!
在这里插入图片描述

int sta1 = sta >> 1 & ~ ( ( 1 << i - 1 ) - 1 ) | sta & ( ( 1 << i - 1 ) - 1 );

如果不熟悉位运算的优先级的,看到这里基本上已经告退了吧哈哈哈哈
接下来让我们加几个括号

int sta1 = ( ( sta >> 1 ) & ( ~ ( ( 1 << ( i - 1 ) ) - 1 ) ) ) | ( sta & ( ( 1 << ( i - 1 ) ) - 1 ) );

我们来一一分析,提前声明因为我的 i i i是从 1 1 1开始枚举的,而二进制是从 0 0 0开始,故要 − 1 -1 1
如何才能把 i i i这一个球彻底删掉呢??让我们拭目以待吧!!


( 1 << ( i - 1 ) ) - 1

[ 0 , i ) [0,i) [0,i)位每一位都为 1 1 1然后其余位全为 0 0 0,好理解,下一个!
在这里插入图片描述


sta >> 1

全体右移一位,第 0 0 0位丢掉,好理解,下一个!
在这里插入图片描述


~ ( ( 1 << ( i - 1 ) ) - 1 ) )

~ : 一种单目运算符(位运算),对二进制每一位上的数全都进行取反操作,1变0,0变1

在这里插入图片描述


接下来进行结合操作!!吃俺老孙一棒

( ( sta >> 1 ) & ( ~ ( ( 1 << ( i - 1 ) ) - 1 ) ) )

这行语句的目的是保留了 [ i + 1 , t o t ] [i+1,tot] [i+1,tot]位上的每一个球的状态,即画圈部分
在这里插入图片描述注意 s t a > > 1 sta>>1 sta>>1整体右移 1 1 1位后再取 & \& &,而我们知道除了 [ 0 , i ) [0,i) [0,i)其余位置都为 1 1 1
故而 s t a sta sta右移 1 1 1位后,以 i i i为分水岭,原来的状态是什么,右移 1 1 1位的状态亦不变
i i i恰好右移成为 i − 1 i-1 i1,从 i − 1 i-1 i1位开始往右全都是 0 0 0
刚好把 i i i给卡掉了!


最后就是上面一堆的状态 [ 0 , i ) [0,i) [0,i)还是均为 0 0 0的,此时就需要得到原来这些位置上的状态

sta & ( ( 1 << ( i - 1 ) ) - 1 )

最后两个残缺状态取 ∣ | ,即是卡掉 i i i后的新状态
这是其中一种,另一种同理,不再赘述

code

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
map < int, double > mp;
int n, k;
char s[40];
double dp[1 << 25];
 
double dfs( int sta, int tot ) {
	if( tot == n - k ) return 0;
	if( tot > 24 && mp.find( sta ) != mp.end() ) return mp[sta];
	if( tot <= 24 && dp[sta] != -1 ) return dp[sta];
	double sum = 0;
	for( int i = 1;i <= ( tot >> 1 );i ++ ) {
		int color1 = ( sta >> ( i - 1 ) ) & 1;
		int color2 = ( sta >> ( tot - i ) ) & 1;
		int sta1 = ( sta >> 1 ) & ( ~ ( ( 1 << ( i - 1 ) ) - 1 ) ) | ( sta & ( ( 1 << ( i - 1 ) ) - 1 ) );
		int sta2 = ( sta >> 1 ) & ( ~ ( ( 1 << ( tot - i ) ) - 1 ) ) | ( sta & ( ( 1 << ( tot - i ) ) - 1 ) );
		sum += 2 * max( dfs( sta1, tot - 1 ) + color1, dfs( sta2, tot - 1 ) + color2 ) / tot;
	}
	if( tot & 1 ) {
		int i = ( tot + 1 ) >> 1; 
		int color = ( sta >> ( i - 1 ) ) & 1;
		int st = ( sta >> 1 ) & ( ~ ( ( 1 << ( i - 1 ) ) - 1 ) ) | ( sta & ( ( 1 << ( i - 1 ) ) - 1 ) );
		sum += ( dfs( st, tot - 1 ) + color ) / tot;
	}
	return ( tot > 24 ) ? mp[sta] = sum : dp[sta] = sum;
}

int main() {
	scanf( "%d %d %s", &n, &k, s );
	int sta = 0;
	for( int i = 0;i < ( 1 << 25 );i ++ )
		dp[i] = -1;
	for( int i = 1;i <= n;i ++ )
		sta |= ( s[i - 1] == 'W' ) << ( n - i );
	sta |= ( 1 << n );
	printf( "%.8f", dfs( sta, n ) );
	return 0;
}

T3:玩个树

title

有一棵 n 个节点的树,每条边长度为 1,颜色为黑或白。

可以执行若干次如下操作:选择一条简单路径,反转路径上所有边的颜色。

对于某些边,要求在操作结束时为某一种颜色。

给定每条边的初始颜色,求最小操作数,以及满足操作数最小时,最小的操作路径长度和。
输入格式

第一行,一个正整数 n。

接下来 n-1 行,每行四个整数 a, b, c, d:

• 树中有一条边连接 a 和 b。

• c = 0, 1 表示初始颜色为白色、黑色。

• d = 0, 1, 2 表示最终要求为白色、要求为黑色、没有要求。
输出格式

输出一行,两个整数,表示最小操作数、操作数最小时的最小路径长度和。
样例
样例输入1

5
2 1 1 0
1 3 0 1
2 4 1 2
5 2 1 1

样例输出1

1 2

样例解释1

操作路径 {2, 1, 3}。

样例输入2

3
1 3 1 2
2 1 0 0

样例输出2

0 0

样例输入3

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

样例输出3

1 4

数据范围与提示

对于100%的数据,保证给出的图为一棵树,有 n ∈ [1,1e5],a, b ∈ [1, n],c ∈ {0, 1},d ∈ {0, 1, 2}
在这里插入图片描述

solution

若操作的边集为 E E E,那么操作次数则为 E E E中度数为奇数的点的个数 / 2 /2 /2,称这种点为端点
对于点 i i i而言
若为奇数,除去与父亲相连的边则变成了偶数,儿子可以两两配对
那么它就孤了下来,成为一条有可能被操作的路径的一端
若为偶数,除去与父亲相连的边则变成了奇数,儿子两两配对后则还剩一个儿子
那么此时 i i i称当一个中转站的作用,衔接着儿子与父亲
反正无论如何它都只可能是一条可能被操作的路径上的一个点,仅此而已

此时赤裸裸的思想,先控制操作数最少,其次控制路径最小


d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示以 i i i为根的子树( i i i与父亲 f a fa fa相连的那条边是否翻转,翻转为 1 1 1,不翻转为 0 0 0)的最小操作数以及最小路径和
再设 w 1 w1 w1表示 i i i与父亲 f a fa fa相连的边需要翻转, w 2 w2 w2表示 i i i与父亲 f a fa fa相连的边不需要翻转
w 1 = m i n ( d p [ v ] [ 0 ] + w 1 , w 2 + d p [ v ] [ 1 ] ) w1=min(dp[v][0]+w1,w2+dp[v][1]) w1=min(dp[v][0]+w1,w2+dp[v][1])
①儿子不需要翻转, i i i与父亲的边已经翻转
i i i与父亲的边尚未翻转,儿子需要翻转
w 2 = m i n ( w 2 + d p [ v ] [ 0 ] , w 1 + d p [ v ] [ 1 ] ) w2=min(w2+dp[v][0],w1+dp[v][1]) w2=min(w2+dp[v][0],w1+dp[v][1])
①儿子不翻转,父亲也不翻转
②儿子翻转,父亲再翻转回来
在这里插入图片描述

code

#include <cstdio>
#include <vector>
using namespace std;
#define MAXN 100005
#define inf 0x3f3f3f3f
struct node {
	int opt, len;
	node(){}
	node( int Opt, int Len ) {
		opt = Opt, len = Len;
	}
	node operator + ( const node &t ) const {
		return node( opt + t.opt, len + t.len );
	}
}dp[MAXN][2];
vector < pair < int, int > > G[MAXN];
int n;

node Min( node x, node y ) {
	if( x.opt < y.opt ) return x;
	else if( x.opt == y.opt && x.len < y.len ) return x;
	else return y;
}

void dfs( int u, int fa, int type ) {
	node w1 = node( inf, inf ), w2 = node( 0, 0 );
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i].first, flag = G[u][i].second;
		if( v == fa ) continue;
		dfs( v, u, flag );
		node tmp1 = Min( w1 + dp[v][0], w2 + dp[v][1] );
		node tmp2 = Min( w1 + dp[v][1], w2 + dp[v][0] );
		w1 = tmp1, w2 = tmp2;
	}
	if( type == 1 || type == 2 )
		dp[u][1] = Min( node( w1.opt, w1.len + 1 ), node( w2.opt + 1, w2.len + 1 ) );
	else
		dp[u][1] = node( inf, inf );
	if( type == 0 || type == 2 )
		dp[u][0] = Min( node( w1.opt + 1, w1.len ), w2 );
	else
		dp[u][0] = node( inf, inf );
}

int main() {
	scanf( "%d", &n );
	for( int i = 1, a, b, c, d;i < n;i ++ ) {
		scanf( "%d %d %d %d", &a, &b, &c, &d );
		c = ( d == 2 ) ? d : c != d;
		G[a].push_back( make_pair( b, c ) );
		G[b].push_back( make_pair( a, c ) );
	}
	dfs( 1, 0, 2 );
	printf( "%d %d", dp[1][0].opt >> 1, dp[1][0].len );
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值