YACS-2022.4-银组

https://www.iai.sh.cn/contest 2022.04 银组,理论上 100 + 100 + 30 + 100 100+100+30+100 100+100+30+100

T1 上锁的抽屉

题目描述

有一个抽屉柜里竖排了 n n n 只抽屉。每个抽屉有一把锁。若要把一只抽屉『锁死』,就必须锁上它自己,而且要把它的上一层抽屉也锁上。请问有多少种上锁的方法,可以恰好『锁死』 m m m 只抽屉?

由于答案可能很大,输出方案数模 1 0 9 + 7 10^9+7 109+7 的余数。

大体思路

计数类问题显然想到动态规划。

一般地,设 f [ i , j ] f[i,j] f[i,j] 表示前 i i i 个抽屉『锁死』 j j j 个的方案数。这样,我们只需要对第 i i i 个抽屉上锁是否影响『锁死』的个数即可。

此时我们发现,这还取决于第 i − 1 i-1 i1 个抽屉是否上锁。因此,我们规定 f [ i , j , 0 ] f[i,j,0] f[i,j,0] 表示前 i i i 个抽屉『锁死』 j j j 个,且第 i i i 个抽屉不上锁的方案数, f [ i , j , 1 ] f[i,j,1] f[i,j,1] 表示前 i i i 个抽屉『锁死』 j j j 个,且第 i i i 个抽屉上锁的方案数。

当第 i i i 个抽屉上锁时,我们可以借助第 i − 1 i-1 i1 个抽屉上锁,则之前『锁死』的个数为 j − 1 j-1 j1 个;也可能第 i − 1 i-1 i1 个抽屉不上锁,则前 i − 1 i-1 i1 个需要『锁死』 j j j 个。第 i i i 个抽屉不上锁时,必然完全依靠前 i − 1 i-1 i1 个抽屉『锁死』 j j j 个,由此可得状态转移方程:
f [ i , j , 0 ] = f [ i − 1 , j , 1 ] + f [ i − 1 , j , 0 ] f [ i , j , 1 ] = f [ i − 1 , j − 1 , 1 ] + f [ i − 1 , j , 0 ] f[i,j,0]=f[i-1,j,1]+f[i-1,j,0]\\ f[i,j,1]=f[i-1,j-1,1]+f[i-1,j,0] f[i,j,0]=f[i1,j,1]+f[i1,j,0]f[i,j,1]=f[i1,j1,1]+f[i1,j,0]
其边界条件为 f [ 1 , 1 , 1 ] = f [ 1 , 0 , 0 ] = 1 f[1,1,1]=f[1,0,0]=1 f[1,1,1]=f[1,0,0]=1,答案为 f [ n , m , 1 ] + f [ n , m , 0 ] f[n,m,1]+f[n,m,0] f[n,m,1]+f[n,m,0],注意取模。

这样,时空复杂度为 O ( n m ) O(nm) O(nm)。注意到 i i i 的决策只与 i − 1 i-1 i1 相关,可以使用滚动数组使得空间复杂度降至 O ( m ) O(m) O(m)

ll n, m, f[2][maxn][2];
/*
f[i, j, 0] 表示前i个抽屉锁死j个,i不锁死
f[i, j, 1] ......................i锁死

f[1,0,0]=f[1,1,1]=1
f[i,j,0]=f[i-1,j,1]+f[i-1,j,0]
f[i,j,1]=f[i-1,j-1,1]+f[i-1,j,0]
(j > 0)
*/
int main () {
	read(n); read(m);
	f[1][0][0] = f[1][1][1] = 1;
	rep(i, 2, n) {
		rep(j, 0, i) {
			f[i&1][j][0] = (f[(i-1)&1][j][1] + f[(i-1)&1][j][0]) % mod;
			f[i&1][j][1] = f[(i-1)&1][j][0];
			if(j > 0) (f[i&1][j][1] += f[(i-1)&1][j - 1][1]) %= mod;
		}
	}
	writeln((f[n&1][m][0] + f[n&1][m][1]) % mod);
	return 0;
}

T2 匹配括号(二)

题目描述

给定一个仅由 ()[] 构成的字符串,若它不是匹配的,请求出最少删去多少括号可以使它变成匹配的。匹配定义如下:

  • 空字符串是匹配的;
  • 如果字符串 s s s 是匹配的,那么 ( s ) (s) (s) [ s ] [s] [s] 也是匹配的;
  • 如果字符串 s s s t t t 都是匹配的,那么 s t st st 也是匹配的。

大体思路

原题链接 https://loj.ac/p/10150 。

由于匹配的括号串每一个子串都匹配,考虑区间 DP。设 f [ i , j ] f[i,j] f[i,j] 表示区间 [ i , j ] [i,j] [i,j] 最少删去的括号个数。边界 f [ i , i ] = 1 f[i,i]=1 f[i,i]=1

括号匹配是经典的讨论边界的区间 DP。如果 s i , s j s_i,s_j si,sj 互相匹配,则有初始值 f [ i , j ] = f [ i + 1 , j − 1 ] f[i,j]=f[i+1,j-1] f[i,j]=f[i+1,j1],否则初始为 ∞ \infty 。然后枚举分割点 k ∈ [ i , j ) k\in [i,j) k[i,j) f [ i , j ] = min ⁡ { f [ i , k ] + f [ k + 1 , j ] } f[i,j]=\min\{f[i,k]+f[k+1,j]\} f[i,j]=min{f[i,k]+f[k+1,j]}。最终答案即为 f [ 1 , n ] f[1,n] f[1,n],时间复杂度 O ( n 3 ) O(n^3) O(n3)

T3 末日生存

题目描述

小爱正在参加一个末日生存比赛,每过一天要消耗一个单位的物资,最后一天到来之前,如果她没有物资,就输了。游戏一共要持续 d d d 天,出发前,小爱有 c c c 个单位的物资。

游戏过程中有 n n n 次补给机会,第 i i i 次补给机会在第 x i x_i xi 天 ,当天可以补给 a i a_i ai 个单位的物资。

请设计一个方案,使得小爱能够坚持到最后一天,且补给的次数最小。如果无论如何都到不了终点,输出 Impossible

大体思路

考场上没有思路,感觉和之前某个旅行商问题很像,但显然不一样。于是直接使用 d f s dfs dfs,记 d f s ( N , C ) dfs(N,C) dfs(N,C) 表示到达第 N N N 个补给站时还有 C C C 的物资的次数,然后对第 N N N 个补给站是否补充物资进行讨论,时间复杂度 O ( 2 n ) O(2^n) O(2n),期望得分 30 30 30

T4 狼人游戏

题目描述

n n n 个人在一起玩狼人游戏,游戏中有一些玩家的身份是狼人,剩下玩家的身份是平民。狼人知道彼此之间的身份,而平民对其他人的身份信息一无所知。

天亮时,每名玩家要指证另一名玩家是狼人。狼人一定会做伪证,指证某个平民为狼人,而平民可能指证某个狼人,也可能指证另一个平民。

给定每名玩家的指证对象,请分析场面上最多可能有多少名狼人?注意游戏规定至少需要有一名平民。

大体思路

原题 [COCI2014-2015#1] MAFIJA,正好做到过。

将指认关系进行连边, n n n 个节点 n n n 条边形成基环树(基环森林)。

用并查集维护环,对于每一个环,将其断开后变为一棵树,然后将这两个节点 u , v u,v u,v 分别作为根跑树形 DP 即可,答案累加 max ⁡ ( f [ u , 0 ] , f [ v , 0 ] ) \max(f[u,0],f[v,0]) max(f[u,0],f[v,0])。动态规划的复杂度为 O ( n ) O(n) O(n),总复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),瓶颈在于并查集。

此题由于所有点权均为 1 1 1,还有 O ( n ) O(n) O(n) 的贪心做法,但由于推广性不强,在此不介绍。

/*
f[u][0]+=max(f[v][0], f[v][1])
f[u][1] = 1 + f[v][0]
max f[u][0], f[v][0]
*/
int n, id[maxn], f[maxn][2], ans;
int tot = 1, hd[maxn], ver[maxn * 2], nxt[maxn * 2];
inline void add(int u, int v) {
	ver[++tot] = v; nxt[tot] = hd[u]; hd[u] = tot;
	ver[++tot] = u; nxt[tot] = hd[v]; hd[v] = tot;
}
PII p[maxn]; // cir
bool vis[maxn * 2];
inline void dfs(int u, int fa) {
	f[u][0] = 0, f[u][1] = 1;
	for(int i = hd[u]; i; i = nxt[i]) {
		int v = ver[i];
		if(vis[i] || vis[i ^ 1] || v == fa) continue;
		dfs(v, u);
		f[u][0] += max(f[v][0], f[v][1]);
		f[u][1] += f[v][0];
	}
}
int fa[maxn];
inline int find(int k) {
	return (k == fa[k] ? k : fa[k] = find(fa[k]));
}
int main () {
	read(n);
	rep(i, 1, n) fa[i] = i;
	rep(u, 1, n) {
		int v; read(v);
		add(u, v);
		int f1 = find(u), f2 = find(v);
		if(f1 != f2) fa[f1] = f2;
		else {
			id[++id[0]] = tot;
			p[id[0]] = {u, v};
		}
	}
	rep(i, 1, id[0]) {
		int u = p[i].first, v = p[i].second;
		vis[id[i]] = vis[id[i] ^ 1] = 1;
		dfs(u, 0);
		int tmp = f[u][0];
		dfs(v, 0);
		ans += max(tmp, f[v][0]);
		vis[id[i]] = vis[id[i] ^ 1] = 0;
	}
	writeln(ans);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值