luogu 动态规划口胡合集

P1831 杠杆数

是否说出正解: ×

显然是个数位DP,但是感觉无从下手,事实上可以做18次数位dp,每次枚举支点,就可以算出每个数的力矩,取出力矩为0的作为贡献即可

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <bitset>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
#define For(i, x, y) for(int i=x;i<=y;i++)  
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))  
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x)  
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x)  
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long  
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second 
typedef vector<int> VI;
LL read(){LL x = 0,f = 1;char c = getchar();while (c<'0' || c>'9'){if (c == '-') f = -1;c = getchar();}
while (c >= '0'&&c <= '9'){x = x * 10 + c - '0';c = getchar();}return x*f;}
const double PI = acos(-1.0);
const double eps = 1e-9;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7; 
int N,M,K,now;
int str[20];
LL dp[20][3000];
LL dfs(int pos,int val,int limit){
    if(!pos) return val == 0;
    if(val < 0) return 0;
    if(!limit && ~dp[pos][val]) return dp[pos][val];
    LL ans = 0;
    int up = limit?str[pos]:9;
    for(int i = 0 ; i <= up; i ++){
        ans += dfs(pos - 1,val + i * (pos - now),limit && (i == up));
    }
    if(!limit) dp[pos][val] = ans;
    return ans;
}
LL solve(LL x){
    if(!x) return 1;
    int l = 0;
    LL m = x;
    while(m){
        l++; str[l] = m % 10;
        m /= 10;
    }
    LL ans = 0;
    for(int i = 1; i <= l; i ++){
        Mem(dp,-1);
        now = i;
        ans += dfs(l,0,1);
    }
    return ans - (l - 1); 
}
int main(){
    LL l = read(),r = read();
    Prl(solve(r) - solve(l - 1));
    return 0;
}
View Code

 

 

P2939 [USACO09FEB]改造路Revamping Trails

是否说出正解:√

很原题了 最短路维护dis[i][j]表示到i点已经经过了j条高速公路的最短路, Dijkstra直接维护即可

 

P2198 杀蚂蚁

是否说出正解:√

似乎dp[x][y]表示经过了x个放射塔,y个放射塔的最小值即可,干扰塔数量用n - x - y表示

dp[x][y] = min(dp[x - 1][y] + (x - 1) * g * (t + y * b),dp[x][y - 1] + x * g  * (t + (y - 1) * b,dp[x][y] + (x + r) * g * (t + y * b) 

 

P4766 [CERC2014]Outer space invaders

是否说出正解: ×

离散化之后区间dp

#include<bits/stdc++.h>
using namespace std;
const int maxn = 6010;
const int INF = 0x3f3f3f3f;
int N;
struct Node{
    int l,r,d;
    Node(int l = 0,int r = 0,int d = 0):l(l),r(r),d(d){}
}node[maxn];
int Hash[maxn];
int dp[maxn][maxn];
int main(){
    int T; scanf("%d",&T);
    while(T--){
        scanf("%d",&N); int cnt = 0;
        for(int i = 1; i <= N; i ++){
            scanf("%d%d%d",&node[i].l,&node[i].r,&node[i].d);
            Hash[++cnt] = node[i].l; Hash[++cnt] = node[i].r;
        }
        sort(Hash + 1,Hash + 1 + cnt);
        cnt = unique(Hash + 1,Hash + 1 + cnt) - Hash - 1;
        for(int i = 1; i <= N ; i ++){
            node[i].l = lower_bound(Hash + 1,Hash + 1 + cnt,node[i].l) - Hash;
            node[i].r = lower_bound(Hash + 1,Hash + 1 + cnt,node[i].r) - Hash;
        }
        for(int len = 1; len <= cnt; len ++){
            for(int l = 1; l + len - 1 <= cnt; l ++){
                int r = l + len - 1;
                int id = 0;
                for(int i = 1; i <= N ; i ++){
                    if(node[i].l < l || node[i].r > r) continue;
                    if(!id || node[id].d < node[i].d) id = i;
                }
                if(!id){
                    dp[l][r] = 0;
                    continue;
                }
                dp[l][r] = INF;
                for(int k = node[id].l; k <= node[id].r; k ++){
                    dp[l][r] = min(dp[l][r],dp[l][k - 1] +dp[k + 1][r] + node[id].d);
                }
            }
        }
        printf("%d\n",dp[1][cnt]);
    }
    return 0;
}
View Code

 

P2400 秘密文件

是否说出正解:√

利用KMP的next数组求出每个子串的循环节,然后区间dp

 

P4046 [JSOI2010]快递服务

是否说出正解:√

从给定的顺序开始递推,dp[i][j]表示除了一个司机必定在上一个点之外,其他两个司机分别在第i,j的位置,因为每一层之间独立,所以需要开滚动数组

时间复杂度O(n³)

 

P3413 SAC#1 - 萌数

是否说出正解:√

显然是数位dp,意识到最短回文串只需要关注两位数字和三位数字即可,即存在一个数和前一位或者前前一位相等即可。

那么dp[1000][11][11]表示当前位置pos上一位数字为i,上上位数字为j有多少种情况然后去记忆化搜索即可。

因为数位dp并不熟练,所以敲了一发

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn = 1010;
const int mod = 1e9 + 7;
char s1[maxn],s2[maxn];
int str[maxn];
LL dp[maxn][11][11];
LL ten[maxn],num[maxn];
int L;
LL dfs(int pos,int la,int lla,bool limit,bool zero){
    if(pos == L) return 0;
    if(~dp[pos][la][lla] && !limit && !zero) return dp[pos][la][lla];
    int top = limit?str[pos]:9;
    LL ans = 0;
    for(int i = 0; i <= top; i ++){
        if(i == la || i == lla){
            if(limit && i == str[pos]){
                 ans = (ans + num[pos + 1] + 1) % mod;
                // cout << pos << " " << num[pos + 1] << endl;
            }
            else ans = (ans + ten[pos]) % mod;
            continue;
        }
        ans = (ans + dfs(pos + 1,(zero && !i)?10:i,la,limit && i == str[pos],zero && !i)) % mod;
    }
    if(!limit && !zero) dp[pos][la][lla] = ans;
    return ans;
}
LL solve(char *s){
    int l = strlen(s); L = l;
    ten[l - 1] = 1; num[l] = 0;
    for(int i = l - 1; i >= 0; i --){
         str[i] = s[i] - '0';
         num[i] = (num[i + 1] + (str[i] * ten[i]) % mod) % mod;
         if(i) ten[i - 1] = ten[i] * 10 % mod;
    }
    for(int i = 0 ; i <= l; i ++){
        for(int j = 0 ; j <= 10; j ++) for(int k = 0 ; k <= 10; k ++) dp[i][j][k] = -1;
    }
    return dfs(0,10,10,1,1);
}

int check(char *s){
    int l = strlen(s);
    for(int i = 1; i < l; i ++){
        if(s[i] == s[i - 1]) return true;
        if(i > 1 && s[i] == s[i - 2]) return true;
    }
    return false;
}
int main(){
    scanf("%s%s",s1,s2);

    printf("%lld\n",(solve(s2)-solve(s1) + check(s1) + mod) % mod);
    return 0;
}
View Code

 

P4290 [HAOI2008]玩具取名

比较裸的区间dp dp[l][r][x]表示区间[l,r]合成字母x的可行性

#include<bits/stdc++.h>
using namespace std;
const int maxn = 220;
int N;
const char c[5] = {'0','W','I','N','G'};
map<char,int>id;
int num[5];
char str[5];
char s[maxn];
bool change[5][5][5];
bool dp[maxn][maxn][5];
int main(){
    id['W'] = 1; id['I'] = 2; id['N'] = 3; id['G'] = 4;
    for(int i = 1; i <= 4; i ++) scanf("%d",&num[i]);
    for(int i = 1; i <= 4; i ++){
       for(int j = 1; j <= num[i]; j ++){
            scanf("%s",str);
            change[id[str[0]]][id[str[1]]][i] = 1;
       }
    }
    scanf("%s",s + 1); int L = strlen(s + 1);
    for(int i = 1; i <= L ; i ++) dp[i][i][id[s[i]]] = 1;
    for(int len = 2; len <= L; len++){
        for(int l = 1; l + len - 1<= L; l ++){
            int r = l + len - 1;
            for(int k = l; k < r; k ++){
                for(int p = 1; p <= 4; p ++){
                    if(!dp[l][k][p]) continue;
                    for(int q = 1; q <= 4; q ++){
                        if(!dp[k + 1][r][q]) continue;
                        for(int x = 1; x <= 4; x ++){
                            if(change[p][q][x]) dp[l][r][x] = 1;
                        }
                    }
                }
            }
        }
    }
    bool flag = 0;
    for(int i = 1; i <= 4; i ++) if(dp[1][L][i]){
        putchar(c[i]);
        flag = 1;
    } 
    if(!flag) puts("The name is wrong!");
    
    return 0;
}
View Code

 

P1121 环状最大两段子段和

枚举中间点,求中间点往左的最大字段和加上中间点往右的最大子段和的最大值。

如果字段经过了环,就用将字段取反,用同样的方法求两段最大值,表示这两段不取。

 

posted @ 2019-10-24 15:46  Hugh_Locke  阅读(242)  评论(0编辑  收藏  举报