1.1050.鸣人的影分身
状态表示:f(i,j):所有总和是i且分成j个数的总方案
状态计算:分成两个集合:最小值是0:f(i,j-1);最小值不是0::让每个方案里面的数减一:f(i-j,j)
f(i,j)=f(i,j-1)+f(i-j,j)
#include <bits/stdc++.h>
using namespace std;
const int N=11;
int f[N][N];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&m,&n);
f[0][0]=1;
for(int i=0;i<=m;i++)
for(int j=1;j<=n;j++)
{
f[i][j]=f[i][j-1];
if(i>=j) f[i][j]+=f[i-j][j];
}
printf("%d\n",f[m][n]);
}
return 0;
}
2.1047糖果
状态表示:所有从前i个物品中选且糖数总和除以k的余数是j的所有方案
状态计算:不选第i个物品:f[i-1][j] ;选第i个物品f[i][j-w%k]+w
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n,k;
int f[N][N];
int main()
{
scanf("%d%d",&n,&k);
memset(f,-0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;i++)
{
int w;
scanf("%d",&w);
for(int j=0;j<k;j++)
f[i][j]=max(f[i-1][j],f[i-1][(j+k-w%k)%k]+w);
}
printf("%d\n",f[n][0]);
return 0;
}
3.1222. 密码脱落
tips:
①一个字符串添加多少字符可以变成回文串<=>删除多少字符可以变成回文串
②答案=总长度-最长回文串的长度
③最长回文串在本题中可以不连续
④枚举过程先枚举长度,再枚举左端点,可以算出右端点,省去边界问题
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
char s[N];
int f[N][N];
int main()
{
scanf("%s",s);
int n=strlen(s);
for(int len=1;len<=n;len++)
for(int l=0;l+len-1<n;l++)
{
int r=l+len-1;
if(len==1) f[l][r]=1;
else
{
if(s[l]==s[r]) f[l][r]=f[l+1][r-1]+2;
if(f[l][r-1]>f[l][r]) f[l][r]=f[l][r-1];
if(f[l+1][r]>f[l][r]) f[l][r]=f[l+1][r];
}
}
printf("%d\n",n-f[0][n-1]);
return 0;
}
4.1220.生命之树
状态表示:在以u为根的子树中包含u的所有连通块的权值最大值
f[u]=wu+max{f(si),0}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100010,M=N*2;//分别代表无向图的点数和边数
int n;
int w[N];
int h[N],e[M],ne[M],idx;
LL f[N];
void add(int a, int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs(int u,int father)
{
f[u]=w[u];
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j!=father)
{
dfs(j,u);
f[u]+=max(0ll,f[j]);
}
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=0;i<n-1;i++)//n-1条边,无向图
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
dfs(1,-1);
LL res=f[1];
for(int i=2;i<=n;i++) res=max(res,f[i]);
printf("%lld\n",res);
return 0;
}
5.1303.斐波那契前n项和
使用矩阵快速幂进行求解
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=3;
int n,m;
void mul(int c[],int a[],int b[][N])//矩阵乘法:1维*2维
{
int temp[N]={0};
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
temp[i]=(temp[i]+(LL)a[j]*b[j][i])%m;
memcpy(c,temp,sizeof temp);
}
void mul(int c[][N],int a[][N],int b[][N])//矩阵乘法:2维*2维
{
int temp[N][N]={0};
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
temp[i][j]=(temp[i][j]+(LL)a[i][k]*b[k][j])%m;
memcpy(c,temp,sizeof temp);
}
int main()
{
cin>>n>>m;
int f1[N]={1,1,1};
int a[N][N]={
{0,1,0},
{1,1,1},
{0,0,1}
};
n--;
while(n)//矩阵快速幂模板
{
if(n&1) mul(f1,f1,a);//res=res*a
mul(a,a,a);//a=a*a
n>>=1;
}
cout<<f1[2]<<endl;
return 0;
}
6.1226.包子凑数
状态表示:f[i][j]:所有只考虑前i个物品,且总和是j的方案数
属性:bool类型,判断集合是否为空即j能否被凑出
状态计算:同完全背包
#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int a[110];
bool f[110][N];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int main()
{
int n;
scanf("%d",&n);
int d=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
d=gcd(d,a[i]);//找到n个数的最大公约数
}
if(d!=1) puts("INF");//凑不出来的无解的条件
else//问题转化为完全背包
{
f[0][0]=true;
for(int i=1;i<=n;i++)
for(int j=0;j<N;j++)
{
f[i][j]=f[i-1][j];
if(j>=a[i]) f[i][j]|=f[i][j-a[i]];
}
int res=0;
for(int i=0;i<N;i++)
if(!f[n][i]) res++;
printf("%d\n",res);
}
return 0;
}
7.1070.括号配对
区间DP的枚举顺序:区间长度,左端点,右端点,与上面的回文串的分析类似
#include <bits/stdc++.h>
using namespace std;
const int N=110,INF=100000000;
int n;
int f[N][N];
bool is_match(char l,char r)
{
if(l=='('&&r==')') return true;
if(l=='['&&r==']') return true;
return false;
}
int main()
{
string s;
cin>>s;
n=s.size();
for(int len=1;len<=n;len++)
for(int i=0;i+len-1<n;i++)
{
int j=i+len-1;
f[i][j]=INF;
if(is_match(s[i],s[j])) f[i][j]=f[i+1][j-1];
if(j>=1) f[i][j]=min(f[i][j],min(f[i][j-1],f[i+1][j])+1);
for(int k=i;k<j;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
cout<<f[0][n-1]<<endl;
return 0;
}
8.1078.旅游规划
题意:判断各个点是否在树的直径上
树的直径:图上相距最远的两个点的距离
#include <bits/stdc++.h>
using namespace std;
const int N=200010,M=N*2;
int n,maxd;
int h[N],e[M],ne[M],idx;
int d1[N],d2[N],up[N],p1[N];
void add(int a,int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs_d(int u,int father)
{
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j!=father)
{
dfs_d(j,u);
int distance=d1[j]+1;
if(distance>d1[u])
{
d2[u]=d1[u];d1[u]=distance;
p1[u]=j;
}
else if(distance>d2[u]) d2[u]=distance;
}
}
maxd=max(maxd,d1[u]+d2[u]);
}
void dfs_u(int u,int father)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j!=father)
{
up[j]=up[u]+1;
if(p1[u]==j) up[j]=max(up[j],d2[u]+1);
else up[j]=max(up[j],d1[u]+1);
dfs_u(j,u);
}
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
for(int i=0;i<n-1;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
dfs_d(0,-1);
dfs_u(0,-1);
for(int i=0;i<n;i++)
{
int d[3]={d1[i],d2[i],up[i]};
sort(d,d+3);
if(d[1]+d[2]==maxd) printf("%d\n",i);
}
return 0;
}
9.1217.垒骰子
与上方的斐波那契前n项和的算法基本相似
状态表示:f[i][j]:由i个骰子垒在一起,最上面的数字是j的所有方案的集合
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=6,mod=1e9+7;
int n,m;
int get_op(int x)
{
if(x>=3) return x-3;
return x+3;
}
void mul(int c[][N],int a[][N],int b[][N])
{
static int t[N][N];
memset(t,0,sizeof t);
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
t[i][j]=(t[i][j]+(LL)a[i][k]*b[k][j])%mod;
memcpy(c,t,sizeof t);
}
int main()
{
cin>>n>>m;
int a[N][N];
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
a[i][j]=4;
while(m--)
{
int x,y;
cin>>x>>y;
x--;y--;
a[x][get_op(y)]=0;
a[y][get_op(x)]=0;
}
int f[N][N]={4,4,4,4,4,4};
for(int k=n-1;k;k>>=1)//快速幂
{
if(k&1) mul(f,f,a);
mul(a,a,a);
}
int res=0;
for(int i=0;i<N;i++) res=(res+f[0][i])%mod;
cout<<res<<endl;
return 0;
}