【香蕉OI】 chy2003 Contest 1

chy2003 大爷出的题。

本来这套题一眼看上去对我这种蒟蒻挺友好的,但是,反正每次只要是我自我感觉蛮良好的时候,最后成绩都挺惨的。

期望得分 240 ,实际得分 175 。

T1 night

题意

有一些 m ( m ≤ 20 ) m(m\le 20) m(m20) 位二进制数 a i a_i ai 和一个 b b b ,假如 b ∗ 2 k b*2^k b2k 在二进制下是 1 的位 a i a_i ai 都是 1,那么 a i a_i ai 可以减去 b b b

有多组询问,每次问有多少个 a i a_i ai 能够通过一些减 b ∗ 2 k b*2^k b2k 的操作得到 x x x

思路

首先确定这题是一个状压 DP。 d p [ i ] dp[i] dp[i] 表示 i i i 能由多少个数操作得到。

那么现在问题在于去重,因为有的 i i i 可能可以由 j j j 通过多次减操作得到,但是 j j j 只能对 i i i 有 1 的贡献。

先讲我的 O ( m 2 ∗ 2 m ) O(m^2*2^m) O(m22m) 的做法,非常 naive。记 d p [ i ] [ j ] dp[i][j] dp[i][j] ,在 d p [ i ] dp[i] dp[i] 的基础上多记一位 j j j 表示这个 i i i 是由 i + b ∗ 2 j i+b*2^j i+b2j 减去 b ∗ 2 j b*2^j b2j 得到的。然后卡常能卡到 2 秒 真是可惜

然后是 O ( m ∗ 2 m ) O(m*2^m) O(m2m) 做法。只需要记 d p [ i ] dp[i] dp[i] ,然后改变循环的顺序。先枚举 b ∗ 2 k b*2^k b2k 中的 k k k ,也就是强行规定先做 k k k 小的操作。那么操作的顺序问题显然就迎刃而解了。

代码

#include<bits/stdc++.h>
using namespace std;
const int M = 21, S = 1<<20;
int T, n, m, c, b, q;
int f[S];

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

int main()
{
	for (read(T); T--; ){
		read(n); read(m);
		memset(f, 0, sizeof(f));
		for (int i = 1; i <= n; ++ i){
			int x; read(x);
			f[x]++;
		}
		b = 1;
		for (read(c); c--; ){
			int x; read(x);
			b |= 1<<x;
		}
		for (; b < (1<<m); b <<= 1)
			for (int i = (1<<m)-1; i >= 1; -- i)
				if ((i&b) == b)
					f[i^b] += f[i];
		for (read(q); q--; ){
			int x; read(x);
			printf("%d\n", f[x]);
		}
	}
	return 0;
}

T2 dawn

题意

有一棵树,求 k k k 条路径的排列,使这 k k k 条路径的交为 x x x y y y 的简单路径。

注意:假如选两条路径,那么 ( 1 , 2 ) , ( 1 , 3 ) (1,2),(1,3) (1,2),(1,3) ( 1 , 3 ) , ( 1 , 2 ) (1,3),(1,2) (1,3),(1,2) 是两种不同的方案,但是 ( 1 , 2 ) , ( 1 , 3 ) (1,2),(1,3) (1,2),(1,3) ( 2 , 1 ) , ( 3 , 1 ) (2,1),(3,1) (2,1),(3,1) 是两种相同的方案。

思路

相当于在两棵子树各选 k k k 个点,两两对应形成路径,答案为 ( s i z [ x ] ∗ s i z [ y ] ) k (siz[x]*siz[y])^k (siz[x]siz[y])k s i z [ x ] siz[x] siz[x] x x x 的子树大小。

然后发现假如 k k k 个点都在 x x x 的同一个儿子的子树里,那么路径的交会比 ( x , y ) (x,y) (x,y) 要长,那就把这些答案减掉。重复减的容斥一下。

最后只需要对每个点记录子树大小的 k k k 次方,所有儿子子树大小的 k k k 次方和,然后还要记录子树外的那棵子树的信息,用来特判 x , y x,y x,y 某一个是另一个的祖先。

注意

检查取模!!!

检查取模!!!

检查取模!!!

不检查 CSP 爆零活该。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = N<<1, E = 20, K = 100 + 5, mod = 998244353;
int T, n, q;
int h[N], ecnt, nxt[M], v[M]; 
int f[N][E], dep[N];
int siz[N][K], sum[N][K], tsiz[N][K];

template<class T>inline void add(T &x, T y){x += y; if (x >= mod) x -= mod;}

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

void _add(int x, int y){
	nxt[++ecnt] = h[x]; v[ecnt] = y;
	h[x] = ecnt;
}

void dfs(int u, int fa){
	dep[u] = dep[fa]+1;
	f[u][0] = fa;
	for (int i = 1; i < E; ++ i)
		f[u][i] = f[f[u][i-1]][i-1];
	siz[u][1] = 1;
	for (int i = 1; i < K; ++ i)
		sum[u][i] = 0;
	for (int i = h[u]; i; i = nxt[i])
		if (v[i] != fa){
			dfs(v[i], u);
			add(siz[u][1], siz[v[i]][1]); // 没有取模!!!
			for (int j = 1; j < K; ++ j)
				add(sum[u][j], siz[v[i]][j]); // 没有取模!!!
		}
	for (int i = 2; i < K; ++ i)
		siz[u][i] = 1LL * siz[u][i-1] * siz[u][1] % mod;
	if (u == 1) tsiz[u][1] = 0;
	else tsiz[u][1] = n-siz[u][1];
	for (int i = 2; i < K; ++ i)
		tsiz[u][i] = 1LL * tsiz[u][i-1] * tsiz[u][1] % mod;
}

int lca(int x, int y){
	if (dep[x] < dep[y]) swap(x, y);
	for (int i = E-1; i >= 0; -- i)
		if (dep[f[x][i]] >= dep[y])
			x = f[x][i];
	if (x == y) return x;
	for (int i = E-1; i >= 0; -- i)
		if (f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
	return f[x][0];
}

int lca_son(int x, int y){
	if (dep[x] < dep[y]) swap(x, y);
	for (int i = E-1; i >= 0; -- i)
		if (dep[f[x][i]] > dep[y])
			x = f[x][i];
	return x;
}

int solve1(int x, int y, int k){
	LL ans = 0;
	add(ans, 1LL * siz[x][k] * siz[y][k] % mod);
	add(ans, mod - 1LL * sum[x][k] * siz[y][k] % mod);
	add(ans, mod - 1LL * sum[y][k] * siz[x][k] % mod);
	add(ans, 1LL * sum[x][k] * sum[y][k] % mod);
	return ans;
}

int solve2(int x, int y, int k){
	if (dep[x] > dep[y]) swap(x, y);
	LL ans = 0;
	int z = lca_son(x, y);
	int tmp = ((sum[x][k]-siz[z][k]+tsiz[x][k])%mod+mod)%mod;
	add(ans, 1LL * siz[y][k] * tsiz[z][k] % mod);
	add(ans, mod - 1LL * tmp * siz[y][k] % mod);
	add(ans, mod - 1LL * sum[y][k] * tsiz[z][k] % mod);
	add(ans, 1LL * tmp * sum[y][k] % mod);
	return ans;
}

int main()
{
	for (read(T); T--; ){
		read(n); read(q);
		ecnt = 1; memset(h, 0, sizeof(h));
		for (int i = 1; i < n; ++ i){
			int x, y;
			read(x); read(y);
			_add(x, y); _add(y, x);
		}
		dfs(1, 0);
		for (int i = 1; i <= q; ++ i){
			int k, x, y, z;
			read(k); read(x); read(y);
			z = lca(x, y);
			if (z == x || z == y) printf("%d\n", solve2(x, y, k));
			else printf("%d\n", solve1(x, y, k));
		}
	}
	return 0;
}

T3 light

题意

n n n 个格子,标号 1 1 1 n n n ,可以花费 i i i 的代价得到 i i i 号格子。有一些格子,在你花钱得到他的时候同时也会得到 [ i − k , i + k ] [i-k,i+k] [ik,i+k] 的所有格子。

现在给定 k k k 和这些特殊格子,求最小的代价得到所有 n n n 个格子。

思路

先写出 DP 方程: d p [ i ] = m i n ( d p [ j ] + w ) dp[i]=min(dp[j]+w) dp[i]=min(dp[j]+w) ,其中 w w w 号格子是特殊格子,并且满足
j ≥ w − k − 1 , i ≤ w + k j \ge w-k-1,i\le w+k jwk1,iw+k

贪心一波,对于某个 i i i ,显然会选 w + k ≤ i w+k\le i w+ki 的最小的 w w w 。然后用单调队列维护一个区间最小的 d p [ j ] dp[j] dp[j] 就完事了。

然后讲一下我的错误思路:对于每一个位置 i i i ,假如他是特殊位置,就拿他和
m i n ( d p [ j ] ) , ( i − k − 1 ≤ j < i ) min(dp[j]),(i-k-1\le j < i) min(dp[j]),(ik1j<i) 来更新 d p [ i + k ] dp[i+k] dp[i+k] 。显然这个思路漏了 d p [ j ] ( i ≤ j < i + k ) dp[j](i \le j< i+k) dp[j](ij<i+k) j j j ,也就是没有考虑两个选取的特殊位置相互覆盖的情况。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
const int N = 5e6 + 10;
const LL inf = 1e18 + 7;
int T, n, k;
char s[N];

namespace Solver3
{
	LL f[N];
	int que[N], h, t;
	int que1[N], h1, t1;
	void main(){
		f[0] = 0;
		que[h = t = 1] = 0;
		h1 = 1; t1 = 0;
		for (int i = 1; i <= n; ++ i){
			f[i] = f[i-1] + i;
			if (s[i] == '1') que1[++t1] = i;
			while (h1 <= t1 && que1[h1]+k < i) h1++;
			if (h1 <= t1){
				while (h <= t && que[h] < que1[h1]-k-1) h++;
				if (h <= t) f[i] = min(f[i], f[que[h]] + que1[h1]);
			}
			while (h <= t && f[que[t]] >= f[i]) t--;
			que[++t] = i;
		}
		printf("%lld\n", f[n]);
	}
}

signed main()
{
	for (scanf("%lld", &T); T--; ){
		scanf("%lld%lld%s", &n, &k, s+1);
		Solver3::main();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值