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≥时,一定是 Daniel 赢。
根的深度为 0,显然我们不需要考虑所有深度 ≥k 的节点以及没有深度 ≥k 的子孙的节点。我们现在要标记上 k 个节点使得从根到任何一个叶子的路径上都有点被标记。
根据贪心可以知道,第 i 次一定会标记第 i 层的某个节点,同时每一次至少会额外使得一个叶子到根的路径上有节点被标记(否则就说明所有的叶子到根的路径都被标记过了)。
所有节点数 ≤+1 的树一定是 Daniel 赢,也就是 n≤+1,即 k≥ 的时候,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;
}