换根DP
void dfs1(ll u, ll fa)
{
siz[u]++;
dep[u] = dep[fa] + 1;
for (ll i = head[u]; i; i = edge[i].next)
{
ll v = edge[i].to;
if (v == fa)
continue;
dfs1(v, u);
siz[u] += siz[v];
}
}
void dfs2(ll u, ll fa, ll depth)
{
ans = max(ans, depth);
for (ll i = head[u]; i; i = edge[i].next)
{
ll v = edge[i].to;
if (v == fa)
continue;
dfs2(v, u, depth - siz[v] + (n - siz[v]));
}
}
void slove()
{
cin >> n;
for (ll i = 1; i <= n - 1; i++)
{
ll u, v;
cin >> u >> v;
add(u, v, 0);
add(v, u, 0);
}
dep[0] = 0;
dfs1(1, 0);
ans = accumulate(dep + 1, dep + 1 + n, 0ll);
dfs2(1, 0, ans);
cout << ans << endl;
}
分组背包
for(i=1;i<=t;i++){ //小组
for(j=v;j>=0;j--){ //容量
for(k=1;k<=b[i];k++){ //小组中的物品
if(j>=w[g[i][k]]){ //小组i中物品k的容量
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]); //状态转移方程
}
}
}
}
多重背包
ll arr[10];
ll dp[maxn], sum;
void zero(ll w, ll val)
{
for (ll i = sum / 2; i >= w; i--)
{
dp[i] = max(dp[i - w] + val, dp[i]);
}
}
void comple(ll w, ll val)
{
for (ll i = w; i <= sum / 2; i++)
{
dp[i] = max(dp[i - w] + val, dp[i]);
}
}
void multi(ll w, ll val, ll num)
{
if (sum / 2 <= num * w)
{
comple(w, val);
}
else
{
ll k = 1;
while (k <= num)
{
zero(k * w, k * val);
num -= k;
k *= 2;
}
zero(num * w, num * val);
}
}
for (ll i = 1; i <= n; i++)
{
multi(w[i], val[i], nums[i]);
}
二维费用背包
for (ll i = 1; i <= n; i++)
{
for (ll j = m; j >= 1; j--)
{
for (ll k = t; k >= arr[i]; k--)
{
dp[j][k] = max(dp[j][k], max(dp[j - 1][t] + 1, dp[j][k - arr[i]] + 1));
}
}
}
有依赖的背包
//核心就是买附件的时候,一定带上子件
for (int i = 1; i <= m; i++)
{
for (int j = n; main_item_w[i] != 0 && j >= main_item_w[i]; j--)
{
f[j] = max(f[j], f[j - main_item_w[i]] + main_item_c[i]);
if (j >= main_item_w[i] + annex_item_w[i][1])
f[j] = max(f[j], f[j - main_item_w[i] - annex_item_w[i][1]] + main_item_c[i] + annex_item_c[i][1]);
if (j >= main_item_w[i] + annex_item_w[i][2])
f[j] = max(f[j], f[j - main_item_w[i] - annex_item_w[i][2]] + main_item_c[i] + annex_item_c[i][2]);
if (j >= main_item_w[i] + annex_item_w[i][1] + annex_item_w[i][2])
f[j] = max(f[j], f[j - main_item_w[i] - annex_item_w[i][1] - annex_item_w[i][2]] + main_item_c[i] + annex_item_c[i][1] + annex_item_c[i][2]);
}
}
最长递增子序列 (LIS)
复杂度:O(nlogn)
memset(low, 0, sizeof(low));
low[1] = arr[1];
ans = 1;
for (ll i = 2; i <= n; i++)
{
ll j = lower_bound(low + 1, low + ans + 1, arr[i]) - low;
// printf("*%lld*",j);
if (arr[i] >= low[ans])
low[++ans] = arr[i];
else
low[j] = arr[i];
}
最长公共子序列(LCS)O(n2)
ll n = str1.length(), m = str2.length();
for (ll i = 0; i <= n; i++)
{
dp[i][0] = 0;
}
for (ll i = 0; i <= m; i++)
{
dp[0][i] = 0;
}
for (ll i = 1; i <= n; i++)
{
for (ll j = 1; j <= m; j++)
{
if (str1[i - 1] == str2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else if (dp[i - 1][j] > dp[i][j - 1])
{
dp[i][j] = dp[i - 1][j];
}
else
{
dp[i][j] = dp[i][j - 1];
}
}
}
cout << dp[n][m] << endl;
最长公共子序列(LCS)O(nlogn)
for (ll i = 1; i <= n; i++)
{
scanf("%lld", &arr[i]);
res[arr[i]] = i; //离散化
}
ll v;
for (ll i = 1; i <= n; i++)
{
scanf("%lld", &v);
arr[i] = res[v];
// printf("%lld ",arr[i]);
}
low[1] = arr[1];
//然后转化为LIS
ans = 1;
for (ll i = 2; i <= n; i++)
{
ll j = lower_bound(low + 1, low + ans + 1, arr[i]) - low;
// printf("*%lld*",j);
if (arr[i] >= low[ans])
low[++ans] = arr[i];
else
low[j] = arr[i];
}
最长公共子串 O(n2)
ll n = str1.length(), m = str2.length();
ll maxLen = 0, maxEnd = 0;
for (ll i = 0; i <= n; i++)
{
dp[i][0] = 0;
}
for (ll i = 0; i <= m; i++)
{
dp[0][i] = 0;
}
for (ll i = 1; i <= n; i++)
{
for (ll j = 1; j <= m; j++)
{
if (str1[i] == str2[j])
{
if (i == 0 || j == 0)
{
record[i][j] = 1;
}
else
{
record[i][j] = record[i - 1][j - 1] + 1;
}
}
else
{
record[i][j] = 0;
}
if (record[i][j] > maxLen)
{
maxLen = record[i][j];
maxEnd = i; //若记录i,则最后获取LCS时是取str1的子串
}
}
}
cout << dp[n][m] << endl;
区间DP(环)
复杂度:O(n3)
memset(dp);
for (ll i = 1; i <= n; i++)
{
scanf("%d", &num[i]);
dp[i][i] = 0;
relation[i][i] = i;
sum[i] = sum[i - 1] + num[i];
}
for (ll i = 1; i <= n; i++)
{
sum[i + n] = sum[i + n - 1] + num[i];
relation[i + n][i + n] = i + n;
dp[i + n][i + n] = 0;
}
for (ll len = 1; len <= n; len++)
{
for (ll j = 1; j + len <= 2 * n; j++)
{
ll ends = j + len - 1;
for (int i = j; i < ends; i++)
{ //枚举分割点
dp[j][ends] = min(dp[j][ends], dp[j][i] + dp[i + 1][ends] + sum[ends] - sum[j - 1]); //更新状态
}
}
}
for (int i = 1; i <= n; i++)
{
ansmin = min(ansmin, dpmin[i][i + n - 1]); //找1~n,2~n~1,3~n~2....的合并n个堆的中最大和最小的值
ansmax = max(ansmax, dpmax[i][i + n - 1]);
}
// printf("%lld",dp[1][n]); //线性时直接输出
数位DP
时间复杂度为O(状态数*转移数) //状态数是dp数组的大小,转移数是for循环大小
typedef long long ll;
int a[20];
ll dp[20][state];//不同题目状态不同
ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
{
//递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
//第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
ll ans=0;
//开始计数
for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
{
if() ...
else if()...
ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
}
if(!limit && !lead) dp[pos][state]=ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
}
ll solve(ll x)
{
int pos=0;
while(x)//把数位都分解出来
{
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main()
{
ll le,ri;
memset(dp,-1,sizeof dp);
while(~scanf("%lld%lld",&le,&ri))
{
printf("%lld\n",solve(ri)-solve(le-1));
}
}
二进制,二维,DP状态包含lim
ll dp[maxn][3][3];
ll dta[maxn],dtb[maxn];
ll dfs(ll len,bool lima,bool limb)//二维数位DP,DP状态带上lim
{
if(len==0) return 1;
if(dp[len][lima][limb]!=-1) return dp[len][lima][limb];
ll upa=lima?dta[len]:1;
ll upb=limb?dtb[len]:1;
ll ans=0;
for(ll i=0;i<=upa;i++)
{
for(ll j=0;j<=upb;j++)
{
ll x=i&j;
if(x==1) continue;
ans+=dfs(len-1,lima&&i==upa,limb&&j==upb);
ans%=mod;
}
}
return dp[len][lima][limb]=ans;
}
int main()
{
// DEBUG;
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld",&n,&m);
memset(dta,0,sizeof(dta));
memset(dtb,0,sizeof(dtb));
ll lena=0,lenb=0;
for(ll i=n;i;i>>=1)
{
dta[++lena]=i&1;
}
for(ll i=m;i;i>>=1)
{
dtb[++lenb]=i&1;
}
ll ans=0;
memset(dp,-1,sizeof(dp));
for(ll i=lena;i>=1;i--)//根据具体情况进行dp
{
ans+=(dfs(i-1,i==lena,i>lenb)*i)%mod;
ans%=mod;
}
memset(dp,-1,sizeof(dp));
for(ll i=lenb;i>=1;i--)
{
ans+=(dfs(i-1,i>lena,i==lenb)*i)%mod;
ans%=mod;
}
printf("%lld\n",ans);
}
}
树形DP
点权用vector,边权用链式前向星
ll dfs(ll u)
{
if (something)//到叶结点了
{
dp[u][1] = val[u];//dp[u][j]的j表示选多少点
return 1;
}
ll sum = 0, t;
for (ll k = head[u]; k; k = edges[k].next) //链式前向星写法
{
ll v = edges[k].to;
t = dfs(v);//已这个儿子为根的子树大小(已选节点)
sum += t;
for (ll j = sum; j > 0; j--)//dp[u][j]的j表示选多少点
{
for (ll i = min(t, j); i >= 0; i--)
{
//表示取某子树的部分点
dp[u][j] = max(dp[u][j], dp[u][j - i] + dp[v][i] - edges[k].w);
}
}
// for (ll j = sum; j > 0; j--)//dp[u][j]的j表示选多少边
// for (ll i = min(t, j); i >= 0; i--)
// dp[u][j] = max(dp[u][j], dp[u][j - i - 1] + dp[v][i] + edges[k].w);
}
return sum;
}
根节点任意时
void dfs(int u,int fa){
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;if(v==fa)continue;
dfs(v,u);sz[u]+=sz[v]+1;
for(int j=min(sz[u],m);j;--j)
for(int k=min(sz[v],j-1);k>=0;--k)
f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w);
}
}
int main()
{
dfs(1,-1);
printf("%d\n",f[1][m]);
return 0;
}
最短编辑距离
int dp[1005][1005]; /*dp[i][j]表示表示A串从第0个字符开始到第i个字符和B串从第0个
字符开始到第j个字符,这两个字串的编辑距离。字符串的下标从1开始。*/
char a[1005],b[1005]; //a,b字符串从下标1开始
int EditDis()
{
int len1 = strlen(a+1);
int len2 = strlen(b+1);
//初始化
for(int i=1;i<=len1;i++)
for(int j=1;j<=len2;j++)
dp[i][j] = INF;
for(int i=1;i<=len1;i++)
dp[i][0] = i;
for(int j=1;j<=len2;j++)
dp[0][j] = j;
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
int flag;
if(a[i]==b[j])
flag=0;
else
flag=1;
dp[i][j]=min(dp[i-1][j]+1,min(dp[i][j-1]+1,dp[i-1][j-1]+flag));
//dp[i-1][j]+1表示删掉字符串a最后一个字符a[i]
//dp[i][j-1]+1表示给字符串添加b最后一个字符
//dp[i-1][j-1]+flag表示改变,相同则不需操作次数,不同则需要,用flag记录
}
}
return dp[len1][len2];
}