dp的练习总结(9)

P8766 异或三角

1.由题干给出的条件可知:

        (1)1≤a,b,c≤n,可得上限和枚举的范围

        (2)a⊕b⊕c=0,只有当前位相同的二进制数字异或才是 0,所以由此可知,当前位 a,b,c 都选 0,或 a,b,c 中任意两个数选择 1。同时又因为三个数字每个都需要选择一次 1,所以需要操作两次。所以 a,b,c 中有两数首位 1 的位置不同,另外一数则在两数只有一个 1 的基础上有两个 1。所以三数互不相同。

        (3)长度为 a,b,c 的三条边能组成一个三角形,由三角形的性质可得三角形两边之和大于第三边,若b+c>a,必须存在一次b,c选1 a选0

2.可知有4种状态:

        a,b,c 在之前包括现在的二进制位都选 0。

        a,b 选过了 1。

        a,b 和 a,c 选过了 1。

        a,b 和 a,c 与 b,c 都选过 1。

3.对于 a 来说,若之前的二进制位选择的数字与 n 相等的话,若 n 的当前位为 0,则当前位 a 不能选 1,否则就比 n 大了。所以我们可以将 dp 增加一维,分两种情况:

        a 在之前都与 n 相等。

        a 在之前有一次 a<n。

4.用dp(i,j,k)表示在第 i 位二进制位;a 的状态为 j,j=1 的时候代表 a=n,j=2 的时候代表 a<n;k 代表四种状态。

所以答案ans=( f(idx,0,4)+f(idx,1,4) )*6

经过漫长的思考,最后代码奉上:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=35;
int T,n;
int f[N][5][10],now[N];
inline int read(){
	int t=0,f=1;
	register char c=getchar();
	while (c<48||c>57) f=(c=='-')?(-1):(f),c=getchar();
	while (c>=48&&c<=57)t=(t<<1)+(t<<3)+(c^48),c=getchar();
	return f*t;
}
void solve(){
	memset(f,0,sizeof(f));
	memset(now,0,sizeof(now));
	int idx=0;
	while(n){
		now[++idx]=n&1;
		n>>=1;
	}
	f[idx+1][1][1]=1;
	for(int i=idx;i>=1;i--){
		f[i][1][1]=(now[i]==1)?(0):(f[i+1][1][1]); 
		f[i][1][2]=(now[i]==1)?(f[i+1][1][2]+f[i+1][1][1]):(f[i+1][1][2]); 
		f[i][1][3]=(now[i]==1)?(f[i+1][1][2]+f[i+1][1][3]*2):(f[i+1][1][3]);
		f[i][1][4]=(now[i]==1)?(f[i+1][1][4]*2):(f[i+1][1][4]*2+f[i+1][1][3]);
		f[i][0][1]=1;
		f[i][0][2]=((now[i]==1)?(f[i+1][1][2]):(0))+f[i+1][0][1]+f[i+1][0][2]*2;
		f[i][0][3]=((now[i]==1)?(f[i+1][1][3]):(0))+f[i+1][0][2]+f[i+1][0][3]*3;
		f[i][0][4]=((now[i]==1)?(f[i+1][1][4]*2+f[i+1][1][3]):(0))+f[i+1][0][3]+f[i+1][0][4]*4;
	}
	cout<<f[1][1][4]*6+f[1][0][4]*6<<endl;
}
signed main(){
	T=read();
	while(T--){
		n=read();
		solve();
	}
	return 0;
}

P6499 Burza

1.需要证明k≥\sqrt{n}时,一定是 Daniel 赢。

根的深度为 0,显然我们不需要考虑所有深度 ≥k 的节点以及没有深度 ≥k 的子孙的节点。我们现在要标记上 k 个节点使得从根到任何一个叶子的路径上都有点被标记。

根据贪心可以知道,第 i 次一定会标记第 i 层的某个节点,同时每一次至少会额外使得一个叶子到根的路径上有节点被标记(否则就说明所有的叶子到根的路径都被标记过了)。

所有节点数 ≤{_k{}}^{2}+1 的树一定是 Daniel 赢,也就是 n≤{_k{}}^{2}+1,即 k≥\sqrt{n}​ 的时候,Daniel 一定赢。

2.所有深度 >k 的节点是无用的,而标记 i 相当于标记所有 i 子树里深度为 k 的节点,所以我们把树用 dfs 序重新编号,并且只记录深度为 k 的节点,设 Li,Ri 为 dfs 序中 i 的子树里新编号最小和最:大的深度为 k 的节点,di​ 是 i 的深度。转化问题:

        问题变成了给定 Li​,Ri​,di​,第 i 个节点可以覆盖 [Li​,Ri​],求是否可以选出一些节点,使得它们覆盖整个区间,且没有深度重复的节点。

这样可以状压 dp,设 fi,S​ 是是否可以用 S 中节点覆盖新编号前 i 个节点(是为 1,否为 0)。

#include <bits/stdc++.h>
using namespace std;
const int N = 405, K = 20;
int n, k, u, v, cnt, ans, L[N], R[N], fa, depth[N], f[N][1 << K];
vector<int> G[N], qwq[N];
void dfs(int u, int fa, int dep) {
	L[u] = cnt, depth[u] = dep;
	if (dep == k - 1) { L[u] = cnt ++, R[u] = cnt; return; }
	for (int v : G[u])
		if (v != fa) dfs(v, u, dep + 1);
	R[u] = cnt;
}
int main() {
	cin >> n >> k;
	if (n <= k * k) puts("DA"), exit(0);
	for (int i = 1; i < n; i ++)
		cin >> u >> v, G[u].push_back(v), G[v].push_back(u);
	dfs(1, 0, -1), f[0][0] = 1;
	for (int i = 2; i <= n; i ++) qwq[L[i]].push_back(i);
	for (int i = 0; i <= cnt; i ++)
		for (int j = 0; j < 1 << k; j ++) {
			if (!f[i][j]) continue;
			for (int v : qwq[i])
				if (!(j & 1 << depth[v])) f[R[v]][j | 1 << depth[v]] |= f[i][j];
		}
	for (int i = 0; i < 1 << k; i ++)
		if (f[cnt][i]) ans = 1;
	puts(ans ? "DA" : "NE");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值