本周总结~
作者:互联网
本周学习内容如下
1 动态压缩 中的简单状态压缩dp(十进制表示二进制) 树状dp 与记忆化搜索
2 简单的数论知识 筛质数 欧拉函数 筛欧拉函数
3 打了小白月赛34 河南省icpc 61儿童节的比赛 全是牛客
一 动态规划(部分)
1 状态压缩
核心是用十进制的数来表示二进制的状态
题目如下(acwing网站)
以蒙德里安的梦想为例子
求把 N×MN×M 的棋盘分割成若干个 1×21×2 的的长方形,有多少种方案。
例如当 N=2,M=4N=2,M=4 时,共有 55 种方案。当 N=2,M=3N=2,M=3 时,共有 33 种方案。
如下图所示:
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 NN 和 MM。
当输入用例 N=0,M=0N=0,M=0 时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
1≤N,M≤11,1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
以十进制来代表二进制的路径
核心是: 只用把所有的横向格子放好 ,那么竖向的格子就是一定
横向放是这样的绿色 竖向放是红色 最终要把这个矩阵填满。
1 为什么空着的位置的长度要是偶数个?
因为当放完横向的小方块那么我们要保证竖向小方块的一定能够放到矩阵当中去
如果是奇数个横向小方块的话放竖向的小方块一定放不进去因为每一个小方块竖着的高度是2
2 为何是(j&k==0)
因为要保证i-2列深出来到i-1列的小方快保证不与i-1列伸到第i列的小方块重叠
枚举第i列的状态是i-1列放的横向小方块伸出来的
枚举第i-1列状态是i-2列的横向的小方快伸出来的
而二进制里面的j&k如果在相同的下标下都是1 那么该位置一定是1
比如 11011&10011=10011;这个状态就代表在如果这样放横向小方块那么i-1列肯定会重合
代码如下
#include<iostream>
#include<cstring>
using namespace std;
const int N=12,M=1<<N;
long long f[N][M];
bool st[M];
int main()
{
int n,m;
while(cin>>n>>m,n||m)
{
// 预处理 保证竖着一定能放下空着的连续位置必须是偶数
for(int i=0;i<1<<n;i++) // 枚举状态 1<<n 比如n=5 1<<5=32 而00000~11111 就是 0~31的二进制;
{
st[i]=true;;
int cnt=0; //
for(int j=0;j<n;j++) // 竖着的每一位
{
if(i>>j&1) // 按位比较 比如i=11011 i>>0=1 ,1&1=1 ,如果j=2 ,i>>j=0 0&1=0;
{
if(cnt&1)// 判断是不是奇数
{
st[i]=false;
break;
}
cnt=0;//重置一下
}
else
cnt++;
}
if(cnt&1) st[i]=false; // 枚举最后的状态 比如 00111 cnt=2;01111 cnt=1;
}
memset(f,0,sizeof f);
f[0][0]=1; // 前一列没有一个小方块伸到第0列 也就是说这一列只能竖着放
for(int i=1;i<=m;i++)// 枚举列
for(int j=0;j<1<<n;j++)
for(int k=0;k<1<<n;k++)
if((j&k)==0&&st[j|k])
f[i][j]+=f[i-1][k];
// 这里面的 st[j|k]是保证是偶数 比如 11011|10011=11011中间有1
// 个0 代表空着的小方格是奇数就不行 在之前我就处理过了 11011不行
cout<<f[m][0]<<endl;// 为什么不是f[m-1][0] 看前面的定义是啥 f[m][0]代表放m-1列没有
// 小方块伸出来
// 棋盘一共有0~m-1列
// f[i][j]表示 前i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
// f[m][0]表示 前m-1列的方案数已经确定,从m-1列伸出,并且第m列的状态是0的所有方案数
// 也就是m列不放小方格,前m-1列已经完全摆放好并且不伸出来的状态
}
return 0;
}
总结:然后这个状态压缩dp 如果能够理解与接受这个思想的话代码上不是很难
2 树状dp (简单的树状dp)
大致套路就是 f[u][0] ,f[u][1] j就是以u为根节点 0就是选择这个根节点 1就是不选这个根节点
然后dfs
例题如下:
Ural 大学有 N 名职员,编号为 1∼N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入格式
第一行一个整数 N。
接下来 N 行,第 i行表示 i 号职员的快乐指数 HiHi。
接下来 N−1 行,每行输入一对整数 L,表示 K 是 L 的直接上司。
输出格式
输出最大的快乐指数。
数据范围
1≤N≤6000
−128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
const int N=6100;
int happy[N];
bool st[N];
int h[N],ne[N],idx,e[N];
int f[N][2];
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dfs(int root)
{
f[root][0]=0;
f[root][1]=happy[root];
for(int i=h[root];i!=-1;i=ne[i])
{
int j=e[i];
dfs(j);
f[root][1]=f[root][1]+f[j][0];
f[root][0]=f[root][0]+max(f[j][1],f[j][0]);
}
}
int main()
{
int n;
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
cin>>happy[i];
for(int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;
add(b,a);
st[a]=true;
}
int root=1;
while(st[root]) root++;
dfs(root);
int res=max(f[root][1],f[root][0]);
cout<<res<<endl;
return 0;
}
f[u][0]:所有以u为根的子树中选择,并且不选u这个点的方案
f[u][1]:所有以u为根的子树中选择,并且选u这个点的方案
属性:Max
这个代码里面用了记忆化搜索
记忆化就是将之前的需要用到值存下来
3 记忆化搜索
防止重复计算
例题如下
给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。
矩阵中第 i 行第 j 列的点表示滑雪场的第 i行第 j 列区域的高度。
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
下面给出一个矩阵作为例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
在给定矩阵中,一条可行的滑行轨迹为 24−17−2−1
在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1沿途共经过 25 个区域。
现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。
输入格式
第一行包含两个整数 R 和 C。
接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。
输出格式
输出一个整数,表示可完成的最长滑雪长度。
数据范围
1≤R,C≤300
0≤矩阵中整数≤10000
输入样例:
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出样例:
25
记忆化最大的好处就是能够保存路径,不用重复计算
#include<iostream>
#include<cstring>
using namespace std;
const int N=310;
int f[N][N];
int q[N][N];
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int n,m;
int dp(int x,int y)
{
int &v=f[x][y];
if(v!=-1) return v; // 这个就是记忆化 就是之前走到f[x][y]能够走的最大长度直接返回v就好了
v=1;//假如一步也走不动 那么就是本身自己算一步;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a>=1&&a<=n&&b>=1&&b<=m&&q[x][y]>q[a][b])
{
v=max(v,dp(a,b)+1);
}
}
return v;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>q[i][j];
memset(f,-1,sizeof f);
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
res=max(res,dp(i,j)); // 这里的dp(i,j)是在a[i][j]这个点所能走的最大长度
cout<<res<<endl;
return 0;
}
二 简单部分数论
1 筛质数
自己掌握了有2种方法 一种是埃式筛 一种线性筛
埃式筛
void prime(int n)
{
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!st[i]) p[cnt++]=i;
for(int j=i+i;j<=n;j+=i)
st[j]=true;
}
cout<<cnt<endl;// 就是从1到n的质数个数
}
线性筛
void prime()
{
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!st[i])p[cnt++]=i;
for(int j=0;p[j]<=n/i;j++)
{
st[p[j]*i]=true;
if(i%p[j]==0)
break;
}
}
cout<<cnt<<endl;
}
这两种筛法 比较快的是线性筛 因为 线性筛是把每一个合数都只筛了一遍
核心是 用合数的最小质因数去筛合数
埃式筛 可能会重复筛 比如 6 被2筛 一次又被3筛一次
2 欧拉函数与筛欧拉函数
欧拉函数的基本定义是
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
ϕ(N)=N*(1-1/p1)*(1-1/p1)*......(1-1/pk);
这个公式的推法是由容斥原理推出来的
至于容斥原理下一周再说
至于筛欧拉函数跟筛质数很像
代码如下
#include<iostream>
using namespace std;
const int N=1e6+10;
typedef long long LL;
LL p[N];
LL phi[N];
bool st[N];
LL sss(int n)
{
int cnt =0;
phi[1]=1;
LL res=0;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
p[cnt++] = i;
phi[i]=i-1;
}
for(int j=0;p[j]<=n/i;j++)
{
st[i*p[j]]=true;
if(i%p[j]==0)
{
phi[p[j]*i]=phi[i]*p[j];
break;
}
phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
for(int i=1;i<=n;i++)
res+=phi[i];
return res;
}
int main()
{
int n;
cin>>n;
cout<<sss(n)<<endl;
return 0;
}
中间也是数学原理 很好推
三
关于这一周比赛的总结
1 自己还是做题太少,比如会的模板题 在考试中稍加变化自己就看不出来 这个需要多练题
2 就是有的算法掌握我的不太牢固,需要重新复习一下
3 有的题会但是在考试时思路不是很清晰 琢磨很长时间
4 自己的思维有点固化 要多看大佬代码;
标签:总结,cnt,int,矩阵,本周,小方块,include,root 来源: https://blog.csdn.net/weixin_52255583/article/details/117481632