【香蕉OI】 chy2003 Contest 1

chy2003 大爺出的題。

本來這套題一眼看上去對我這種蒟蒻挺友好的,但是,反正每次只要是我自我感覺蠻良好的時候,最後成績都挺慘的。

期望得分 240 ,實際得分 175 。

T1 night

題意

有一些 m(m20)m(m\le 20) 位二進制數 aia_i 和一個 bb ,假如 b2kb*2^k 在二進制下是 1 的位 aia_i 都是 1,那麼 aia_i 可以減去 bb

有多組詢問,每次問有多少個 aia_i 能夠通過一些減 b2kb*2^k 的操作得到 xx

思路

首先確定這題是一個狀壓 DP。 dp[i]dp[i] 表示 ii 能由多少個數操作得到。

那麼現在問題在於去重,因爲有的 ii 可能可以由 jj 通過多次減操作得到,但是 jj 只能對 ii 有 1 的貢獻。

先講我的 O(m22m)O(m^2*2^m) 的做法,非常 naive。記 dp[i][j]dp[i][j] ,在 dp[i]dp[i] 的基礎上多記一位 jj 表示這個 ii 是由 i+b2ji+b*2^j 減去 b2jb*2^j 得到的。然後卡常能卡到 2 秒 真是可惜

然後是 O(m2m)O(m*2^m) 做法。只需要記 dp[i]dp[i] ,然後改變循環的順序。先枚舉 b2kb*2^k 中的 kk ,也就是強行規定先做 kk 小的操作。那麼操作的順序問題顯然就迎刃而解了。

代碼

#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

題意

有一棵樹,求 kk 條路徑的排列,使這 kk 條路徑的交爲 xxyy 的簡單路徑。

注意:假如選兩條路徑,那麼 (1,2),(1,3)(1,2),(1,3)(1,3),(1,2)(1,3),(1,2) 是兩種不同的方案,但是 (1,2),(1,3)(1,2),(1,3)(2,1),(3,1)(2,1),(3,1) 是兩種相同的方案。

思路

相當於在兩棵子樹各選 kk 個點,兩兩對應形成路徑,答案爲 (siz[x]siz[y])k(siz[x]*siz[y])^ksiz[x]siz[x]xx 的子樹大小。

然後發現假如 kk 個點都在 xx 的同一個兒子的子樹裏,那麼路徑的交會比 (x,y)(x,y) 要長,那就把這些答案減掉。重複減的容斥一下。

最後只需要對每個點記錄子樹大小的 kk 次方,所有兒子子樹大小的 kk 次方和,然後還要記錄子樹外的那棵子樹的信息,用來特判 x,yx,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

題意

nn 個格子,標號 11nn ,可以花費 ii 的代價得到 ii 號格子。有一些格子,在你花錢得到他的時候同時也會得到 [ik,i+k][i-k,i+k] 的所有格子。

現在給定 kk 和這些特殊格子,求最小的代價得到所有 nn 個格子。

思路

先寫出 DP 方程: dp[i]=min(dp[j]+w)dp[i]=min(dp[j]+w) ,其中 ww 號格子是特殊格子,並且滿足
jwk1,iw+kj \ge w-k-1,i\le w+k

貪心一波,對於某個 ii ,顯然會選 w+kiw+k\le i 的最小的 ww 。然後用單調隊列維護一個區間最小的 dp[j]dp[j] 就完事了。

然後講一下我的錯誤思路:對於每一個位置 ii ,假如他是特殊位置,就拿他和
min(dp[j]),(ik1j<i)min(dp[j]),(i-k-1\le j < i) 來更新 dp[i+k]dp[i+k] 。顯然這個思路漏了 dp[j](ij<i+k)dp[j](i \le j< i+k)jj ,也就是沒有考慮兩個選取的特殊位置相互覆蓋的情況。

代碼

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章