codeforces 972div2 动态规划,记忆化搜索

A Simple Palindrome

问题:

思路:如果n大于五,那么一定会存在重复的字母,只要把重复的字母排在一起,就是最佳方案。读入后sort一下即可

代码:这里代码方法比较繁琐,并没有想到sort

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

void solve() {
    int n;
    cin >> n;
    map<int, char> ma;
    ma[0] = 'a';
    ma[1] = 'e';
    ma[2] = 'i';
    ma[3] = 'o';
    ma[4] = 'u';
    int num = n / 5;
    int idx = n % 5;
    int cnt = 0;
    for(int i = 0; i <= 4; i ++ ) {
        for(int j = 1; j <= num; j ++ ) cout << ma[i];
        if(cnt < idx) {
            cout << ma[i];
            cnt ++;
        }      
    }
    cout << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t -- ) solve();
    return 0;
}

B The Strict Teacher (Hard Version)

问题:

思路:首先分三种情况 1: T1 S T2这种情况的解是abs(t1 - t2) / 2

                                     2:    (边界1) S Tmin Tmax 这种情况的解是 Tmin - 1

                                     3:    Tmin Tmax S(边界n)这种情况的解是 n - Tmax

显而易见,就是从一群老师中找到第一个小于学生的位置和第一个大于学生的位置,然后范别带入上述三种情况中

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

void solve() {
    int n, m, q;
    cin >> n >> m >> q;
    vector<int> pos(m + 2);
    pos[0] = -1, pos[m + 1] = n;
    for(int i = 1; i <= m; i ++ ) cin >> pos[i];
    sort(pos.begin() + 1, pos.end());
    while(q -- ) {
        /*int b, c, d;
        cin >> b >> c >> d;
        vector<int> a(4);
        a[1] = b, a[2] = c, a[3] = d;
        sort(a.begin() + 1, a.end());
        if(a[2] == d) cout << (a[3] - a[1]) / 2 << "\n";
        else if(a[1] == d) cout << a[2] - 1 << "\n";
        else cout << n - a[2] << "\n";*/
        int a;
        cin >> a;
        int l = 0, r = m + 1;
        while(l < r) {
            int mid = l + r + 1 >> 1;
            if(pos[mid] < a) l = mid;//找到第一个小于等于a的位置
            else r = mid - 1;
        }
        //cout << l << " ";
        if(l == m) cout << pos[m + 1] - pos[m] << "\n";
        else if(l == 0) cout << pos[1] - 1 << "\n";
        else cout << (pos[l + 1] - pos[l]) / 2 << "\n";
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t -- ) solve();
    return 0;
}

C lazy narek

问题:

思路:最开始的思路是预处理分别求出各个字符串中narek分别的数量,然后通过动态规划求解,但是在写的过程的中发现,这样无法处理每读入五个narek分数+ 5的情况,于是改变思路,边扫字符串边dp,遇见narek中的字母让当前分数 - 1如果碰见完整的narek让分数+ 10(因为完整的narek不能算到-1的的分数中去,因此要加10,把减去的分数也加回来)

此时dp的状态是dp[n][5]表示从前i个字符串中选择,并且当前拼接到了narek的第j个位置。仔细思考,这实际上和背包问题类似,都是选与不选,因此考虑从第i个字符串的选与不选进行状态转移:

第i个字符串可以由前i - 1个字符串的五个拼接位置进行转移, 在进行状态转移时,维护一个字符串str,str的初始值与我们当前状态从哪个状态转移有关例如从dp[i - 1][2]转移,意味着此时的str为 "na"当在扫描第i个字符串时遇见r,则str变成"nar”...以此类推,当维护的str变成narek就说明当前分数要+ 10,并且重新将str置空。于是dp[i][j] = max(dp[i][j], dp[i - 1][j])(不选)  这里必须加max, 因为在0 ~ j - 1中,最后str的长度可能刚好是j

dp[i][当前str的长度] = max(dp[i][当前str的长度], dp[i - 1][j] + scr)

最后的答案就是dp[n][0 ~ 4]中与0的最大值

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve() {
    int n, m;
    cin >> n >> m;
    vector<string> s(n + 1);
    for(int i = 1; i <= n; i ++ ) cin >> s[i];
    string str = " ";
    str += "narek";
    vector<vector<int>> dp((n + 1), vector<int>((6), -0x3f3f3f3f));
    dp[0][0] = 0;
    int ans = 0;
    for(int i = 1; i <= n; i ++ ) {
        for(int j = 0; j <= 4; j ++ ) {
            string cmp = " ";
            int idx = j + 1;
            int scr = 0;
            if(j) cmp += str.substr(1, j);
            for(auto t: s[i]) {
                if(t == 'n' || t == 'a' || t == 'r' || t == 'e' || t == 'k') {
                    scr --;  
                    if(t == str[idx]) {
                        cmp += t;
                        idx ++;
                        if(idx == 6) idx = 1;
                    }
                    //if(i == 5 && j == 4) cout << cmp << " ";
                    if(cmp == str) {
                        scr += 10;
                        cmp = " ";
                    }
                }
            }
            //if(i == 1 && j == 0)cout <<scr;
            int now = idx;
            now --;
            dp[i][j] = max(dp[i][j], dp[i - 1][j]);//不选
            dp[i][now] = max(dp[i][now], dp[i - 1][j] + scr);//选
            //dp[i][now] = max(dp[i][now], dp[i - 1][now]);
        }
    }
    
    for(int i = 0; i <= 4; i ++ ) ans = max(dp[n][i], ans);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t -- ) solve();
    return 0;
}

E1 : subtangle game        

问题:

思路:记忆化搜索,博弈,和之前atc中一道状态压缩记忆化的博弈论类似

首先,dp状态为dp[i][j][k]表示当前位置是{i, j}并且搜索到了a数组的第k个位置

这里的记忆化搜索的含义是,以当前状态选择是否必赢

首先定义出当前状态ok设置为必赢

如果当前的dp[i][j][k]!=-1(初始值)那么直接return

并且如果当k = l, 即我的对手什么都选不了了,那么return true

以上均为dfs结束条件

然后 写dfs主体

pos表示当前要找元素的位置的集合

如果当前的位置在i,j的子矩阵中,那么选择继续dfs(pos.first, pos.second, k + 1)

注意,如果当前dfs层数的操作者为T,那么当前层数调用的dfs即为操作者N,也就意味这如果dfs(pos.first, pos.second, k + 1)是true的话,那么当前的dp就是false。由于每个选择都是optical的,即最佳选择,因此只要有一个dfs(pos.first, pos.second, k + 1)是true,那么当前的ok就是false。

最后更新dp[i][j][k] = ok;

return ok;

这里代码不出意外的tle了,原因是,设想a数组与矩阵中都是同一个数字,那么我们每一层dfs都要遍历整个子矩阵

但是由于选择都是optical的,因此我们的每次操作都应该尽可能的让对手有更加少的选择,例如如果x1在x2的子矩阵中,那么选择x1一定比x2更优,这里可以采用类似单调栈的方法对pos进行预处理,这样每个pos中存的数据数量一定是线性的(阶梯型)

if(a[i] == g[j][k]) {

while(pos.size() && pos.back().second <= k) pos.pop_back();

pos.push_back({j, k})

}

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N = 310;

int a[N], g[N][N];
int dp[N][N][N];
int l, n, m;

void init(int l, int n, int m) {
    for(int i = 1; i <= n; i ++ ) {
        for(int j = 1; j <= m; j ++ ) {
            for(int k = 1; k <= l; k ++ ) {
                dp[i][j][k] = -1;
            }
        }
    }
}

void solve() {
    cin >> l >> n >> m;
    init(l, n, m);
    
    for(int i = 1; i <= l; i ++ ) cin >> a[i];
    for(int i = 1; i <= n; i ++ ) {
        for(int j = 1; j <= m; j ++ ) {
            cin >> g[i][j];   
        }
    }
    vector<vector<pair<int, int>>> pos(8);
    for(int i = 1; i <= l; i ++ ) {
        for(int j = 1; j <= n; j ++ ) {
            for(int k = 1; k <= m; k ++ ) {
                if(g[j][k] == a[i]) {
                    while(pos[a[i]].size() && pos[a[i]].back().second <= k) pos[a[i]].pop_back();
                    pos[a[i]].push_back({j, k});
                }
            }
        }
    }
    //cout << a[1] << " ";
    //for(int i = 1; i <= l; i ++ )
    //for(auto t: pos[a[i]]) cout << " " << g[t.first][t.second] << ": " << t.first << " " << t.second << endl;
    function<int(int, int, int)> dfs = [&](int x, int y, int now) -> int {
        bool ok = true;
        if(dp[x][y][now] != -1) return dp[x][y][now];
        if(now == l) return true;
        for(auto t: pos[a[now + 1]]) {
            if(t.first > x && t.second > y) {
                if(dfs(t.first, t.second, now + 1)) ok = false;
            }
        }
        dp[x][y][now] = ok;
        return ok;
    };
    
    bool ok = 0;
    for(auto t: pos[a[1]]) {
        if(dfs(t.first, t.second, 1)) ok = true;
    }
    
    if(ok) cout << "T\n";
    else cout << "N\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t -- ) solve();
    return 0;
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值