回路计数
思路:
由于楼的个数很少,我们考虑用状压dp来做。即用长度为21的二进制数表示21个楼的状态,第x位为1表示第x+1号楼已经被走过了。
f[ j ][ i ]表示当前所在的楼号为 j,且当前楼的状态为 i 时的种类数。
对于每个确定的 i,首先要判断这个 j 是否合法,即在状态 i 下 j 号楼有没有被走过,因为我们当前的楼只能是走过的楼。
然后就枚举下一个走哪栋楼,过程中判断是否能走到以及是否没走过。
详情请见代码
代码:
这是道填空题,只用输出答案即可。
网站上貌似开不了这么大的空间,本地跑完直接输出答案就好啦。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 22, M = 1 << 21;
int f[N][M];
bool e[N][N];
signed main(){
for(int i = 1; i <= 21; i ++ ){
for(int j = 1; j <= 21; j ++ ){
if(__gcd(i, j) == 1)
e[i - 1][j - 1] = 1;
}
}
f[0][1] = 1; //从1号教学楼出发
for(int i = 1; i < (1 << 21); i ++ ){
for(int j = 0; j < 21; j ++ ){ //枚举当前所在楼
if(!((i >> j) & 1)) //若当前楼在状态i下没有走到
continue;
//从j号楼走到k号楼
for(int k = 0; k < 21; k ++ ) {
if((i >> k) & 1 || !e[j][k]) //若k走过了或者j走不到k
continue;
f[k][i | (1 << k)] += f[j][i];
}
}
}
int ans = 0;
for(int i = 0; i < 21; i ++ ){
ans += f[i][(1 << 21) - 1];
}
cout << ans << "\n";
}
糖果
思路:
类似背包,对于每个糖包我们可以选择吃或者不吃
中间用长度为m的二进制数来表示各种口味的状态,第x位为1表示第x+1种口味吃过了,为0表示没有吃过。
f[ i ] 表示口味状态为 i 时最少需要吃的糖包数,剩下部分的和01背包问题一样
代码:
空间貌似只能开到1 << 21,再加一维的话空间会爆掉,所以实现时用滚动数组降维
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 110, M = 21;
int f[1 << M];
signed main(void){
int n, m, k;
cin >> n >> m >> k;
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int i = 1; i <= n; i ++ ){
int x = 0; //第i包糖果的状态
for(int j = 1; j <= k; j ++ ){
int t;
cin >> t;
x |= (1 << (t - 1));
}
//各种口味的糖果状态
for(int j = 0; j < (1 << m); j ++){
int t = x | j; //吃了这包糖果后当前糖果口味的状态
f[t] = min(f[t], f[j] + 1); //选择:吃与不吃
}
}
int ans = f[(1 << m) - 1];
if(ans > n) ans = -1;
cout << ans << "\n";
return 0;
}
生命之树
思路:
树形dp的套路就是先处理子树,处理完之后再层层往上。
f[ i ]表示以 i 为根节点的子树的最大权值和,其中 i 号点一定包含于以 i 为根节点的子树中
对于 i 号点,为了找到 f[ i ],我们贪心的选择其大于0的子树
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
vector<int> e[N];
int f[N], a[N], ans;
void dfs(int u, int fa){
f[u] = a[u];
for(auto v : e[u]){
if(v == fa) continue;
dfs(v, u);
if(f[v] > 0) //贪心选择,大于0的我们都要
f[u] += f[v];
}
ans = max(ans, f[u]);
}
signed main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++ ){
cin >> a[i];
}
for(int i = 1; i < n; i ++ ){
int x, y;
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0);
cout << ans << "\n";
}
没有上司的舞会
思路:
由于每个节点有选与不选两种选择,且子节点的选择会影响到当前节点,所以我们定义状态时需要多一维来表示节点的选择:
f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值
f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值
对于当前节点x,如果我们不选,那么我们就可以选择x的子节点,当然我们也可以选择不选,所以 f[u][0] += max(f[v][1], f[v][0]);
如果我们选择x节点,那么对于x的子节点,我们只能不选,所以f[u][1] += f[v][0];
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
vector<int> e[N];
int f[N][2], a[N];
bool vis[N]; //判断是否为根
void dfs(int u){
f[u][0] = 0; //f[u][0]表示u不选
f[u][1] = a[u]; //f[u][1]表示u选
for(auto v : e[u]){
dfs(v); //先处理子树
f[u][0] += max(f[v][1], f[v][0]);
f[u][1] += f[v][0];
}
}
signed main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++ ){
cin >> a[i];
}
for(int i = 1; i < n; i ++ ){
int x, y;
cin >> x >> y;
e[y].push_back(x);
vis[x] = 1;
}
int root = 1;
while(vis[root]) root ++;
dfs(root);
cout << max(f[root][0], f[root][1]);
}
二进制问题
思路:
可以参考https://blog.csdn.net/qq_46117575/article/details/125081583
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 60;
int n, K;
int f[N][N];
signed main()
{
cin >> n >> K;
for (int i = 0; i < N; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) f[i][j] = 1;
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
vector<int> nums;
while (n) nums.push_back(n % 2), n /= 2;
int res = 0;
int last = 0;
for (int i = nums.size() - 1; i >= 0; i -- )
{
int x = nums[i];
if (x)
{
/*当第i位选0时*/
res += f[i][K - last];
/*当第i位选1时*/
if (x == 1)
{
last ++ ;
/*如果已经消耗1的数量大于规定的数量时,结束算法*/
if (last > K) break;
}
}
/*当为最后一位时,如果所消耗1的数量恰好等于规定数量时,即最后一位符合要求*/
if (!i && last == K) res ++ ;
}
cout << res;
return 0;
}
异或三角
思路:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int m, k;
LL n;
int a[N], cnt;
LL f[31][2][8];
LL dfs(int u, int limit, int state) {
if (!u) return state == 7;
LL &t = f[u][limit][state];
if (~t) return t;
LL res = 0;
int up = limit ? a[u] : 1;
for (int i = 0; i <= up; i ++)
if (!i) {
res += dfs(u - 1, limit && i == up, state);
if (state >= 6)
res += dfs(u - 1, limit && i == up, state | 1);
}
else {
res += dfs(u - 1, limit && i == up, state | 2);
res += dfs(u - 1, limit && i == up, state | 4);
}
return t = res;
}
LL dp(LL x) {
cnt = 0;
memset(f, -1, sizeof f);
while (x) {
a[++cnt] = x & 1;
x >>= 1;
}
return dfs(cnt, 1, 0) * 3;
}
int main() {
int T;
cin >> T;
while (T -- ) {
cin >> n;
cout << dp(n) << '\n';
}
return 0;
}
忠诚
思路:
不难发现这道题就是个RMQ问题,如果我们对于每次查询都使用暴力来做的话肯定会超时的,所以我们可以使用st表来优化。
st[ i ][ j ]表示的是区间[i, i + 2^j - 1]的最小值,即一个长度为2^j的区间的最值,它利用的是倍增思想,就能比较快(log)的查询出区间最值。
st建立的过程比较像区间dp,然后查询时由于k是取了log可能会有些损失,所以我们既要从左往右st[l][k],也要从右往左st[r - (1 << k) + 1][k]才能覆盖住整个查询区间。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int st[N][20], lg[N];
int n, m;
signed main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++ )
cin >> st[i][0];
lg[0] = -1;
for(int i = 1; i <= n; i ++ ) lg[i] = lg[i >> 1] + 1;
for(int j = 1; j <= 20; j ++ ){
for(int i = 1; i + (1 << j) - 1 <= n; i ++ ){
st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
}
while(m -- ){
int l, r;
cin >> l >> r;
int k = lg[r - l + 1];
cout << min(st[l][k], st[r - (1 << k) + 1][k]) << " ";
}
}
机房
思路:
我们这里可以把1号点当做树根root,然后使用sum[x]表示x点发送消息到root所需要的时间。 很显然,x发送出去的时候经过自身的延迟后到达另外一个点,另外一个点在经过延迟到达下一个点,以此类推。。。
那么任意两个点之间的距离一般就是使用sum[x] + sum[y] - 2 * sum[lca(x, y)] 但是对于本题而言,当减去两倍的最近公共祖p先到root的距离时,就会把自身的一个延迟多减了一次,所以还需要加上p的出度。 所以本题正确计算距离的公式应该为: d = (sum[x] + sum[y] - 2 * sum[p] + dg[p]) 其中p表示x和y的最近公共祖先,dg[p]表示p点的出度
代码:
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 5e5 + 5;
vector<int> g[N];
int n, m, root;
int dep[N], fa[N][20];
int sum[N];
void dfs(int u, int p) {
dep[u] = dep[p] + 1;
fa[u][0] = p;
sum[u] = sum[p] + (int)g[u].size();
for (int i = 1; i < 20; i ++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(auto v : g[u]){
if (v == p) continue;
dfs(v, u);
}
}
int LCA(int x, int y) {
if(dep[x] < dep[y])
swap (x, y);
for (int i = 19; i >= 0; i --)
if (dep[fa[x][i]] >= dep[y])
x = fa[x][i];
if (x == y) return x;
for (int i = 19; i >= 0; i --)
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
cin >> n >> m;
for (int i = 1; i < n; i ++) {
int u, v;
cin >> u >> v;
g[u].push_back(v), g[v].push_back(u);
}
dfs(1, 0);
while (m --) {
int u, v;
cin >> u >> v;
int p = LCA(u, v);
cout << sum[u] + sum[v] - 2 * sum[p] + (int)g[p].size() << "\n";
}a
return 0;
}