动态规划(DP)

换根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];
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值