第9周cf刷题(dp)

Problems(1300-1600)

an ac a day keeps the doctor away

Gas Pipeline (1500)

2019.10.28

题意:

管道工人需要在一段凹凸不平的道路上铺管子,给一串长度为n的01序列描述地形,1表示一定要把水管用钢管架高在离地面2个单位,0表示水管离地面高度可以为1,也可以为2.

在转折的地方需要额外花费1单位的水管,给定水管和钢管的价格a,b;问最少需要花费多少钱

思路:线性DP,用dp[i] [j] 表示前j列在第j列在第i层时的最小花费(i = 0表示离地高度为1,i = 1表示离地高度为2),很容易想到状态转移方程。

注意点:当该点为0时,如果前一个点为1那么必须继续保持离地高度为2(我也不知道为什么,样例的规则就是这样)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
const ll inf = 0x3f3f3f3f3f3f3f3f;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
int t,n;
ll a,b;
char s[maxn];
ll dp[2][maxn];
int main()
{
    t = read();
    while(t--){
        n = read();scanf("%lld %lld",&a,&b);
        scanf("%s",s);
        for(int i = 0;i < 2;i++){ //初始化
            for(int j = 0;j <= n;j++) dp[i][j] = inf;
        }
        dp[0][0] = b;
        for(int i = 1;i < n;i++){
            if(s[i] == '0' && s[i - 1] == '0'){
                dp[0][i] = min(dp[1][i - 1] + 2 * a + b,dp[0][i - 1] + a + b);
                dp[1][i] = min(dp[1][i - 1] + a + 2 * b,dp[0][i - 1] + 2 * a + 2 * b); 
            } else{
                dp[1][i] = min(dp[1][i - 1] + a + 2 * b,dp[0][i - 1] + 2 * a + 2 * b); 
            }
            // cout << dp[0][i] << " " << dp[1][i] << endl;
        }
        //最后处理第n列
        dp[0][n] = min(dp[1][n - 1] + 2 * a + b,dp[0][n - 1] + a + b);
        printf("%lld\n",dp[0][n]);
    }
    return 0;
}

Block Adventure(1300)

2019.10.28

题意:

从起点到终点有 n 个格子,每块高度 h[i],初始时背包有m块方块,从 i 跳到 i+1 有三种选择,1. 把背包的一个方块放到当前的格子上;2. 把当前格子的一个方块放到背包里;3.跳到下一块。

前两种选择可以进行任意多次

只有当 | h[i] - h[i+1] | <= k 才能跳到下一块。 问是否能跳到终点?

思路:贪心+线性模拟,能拿就尽可能拿更多的石头(但注意最多只能拿完),不能拿就放最少回去使其通过,如果石头为负数就不符合条件.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
int t,n,m,k;
int h[105];
int main()
{
    t = read();
    while(t--){
        n = read(); m = read(); k = read();
        for(int i = 0;i < n;i++) h[i] = read();
        bool ok = 1;
        for(int i = 1;i < n;i++){
            if(h[i - 1] - h[i] >= -k) m += min(h[i - 1] - h[i] + k,h[i - 1]);
            else{
                if(h[i] - h[i - 1] - m <= k) m -= (h[i] - h[i - 1] - k);
                else {ok = 0;break;}
            }
        }
        if(ok) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

Basketball Exercise (1400)

2019.10.29

题意:

输入n,给出两组均为 n个数字的数组a和b,轮流从a和b数组中取出一个数字(可以不取),要求严格按照当前所选数字的数组下标比上一个所选数字的数组下标更大,计算能够取出的数字加起来的总和最大能为多少。

思路:线性DP,又是这种上下两行条件约束的递推题,处理好约束条件和状态转移方程即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
int n;
ll h[2][maxn],dp[2][maxn]; 
int main()
{
    n = read();
    for(int i = 0;i < 2;i++){
        for(int j = 1;j <= n;j++) scanf("%lld",&h[i][j]);
    }
    dp[0][1] = h[0][1]; dp[1][1] = h[1][1];
    for(int j = 2;j <= n;j++){
        for(int i = 0;i < 2;i++){
            dp[i][j] = max(dp[i][j - 1],dp[i ^ 1][j - 1]); //不选
            dp[i][j] = max(dp[i ^ 1][j - 1],max(dp[i][j - 2],dp[i ^ 1][j - 2])) + h[i][j]; //选
        }
    }
    printf("%lld",max(dp[0][n],dp[1][n]));
    return 0;
}

RGB Substring (1600)

2019.10.29

人类的智慧!这题好好总结!

题意:

有一个“RGB……”无限循环的母串,q组样例,每组一个给出一个长度为n的字符串,让你通过更改该串的某个字符构造一个长度为k的母串的子串,求满足要求的最少更改次数。

思路:思维+前缀和,母串的样板串只有三种:"RGB……" "GBR……" "BRG……",分别统计这三种情况下母串和子串的不匹配贡献的前缀和,即如果对应位置不同,前缀和++,否则不变。然后暴力枚举所有长度为k的区间和,找出最小值即为答案。时间复杂度O(n).

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
int q,n,k,ans;
char s[maxn];
int a[3][maxn];
string t = "RGB";
int main()
{
    q = read();
    while(q--){
        n = read(); k = read();
        scanf("%s",s);
        ans = maxn;
        for(int i = 0;i < n;i++){
            for(int j = 0;j < 3;j++){
                if(s[i] != t[(i + j) % 3]) a[j][i + 1] = a[j][i] + 1;
                else a[j][i + 1] = a[j][i];
            }
        }
        for(int i = 0;i + k <= n;i++){
            for(int j = 0;j < 3;j++){
                ans = min(ans,a[j][i + k] - a[j][i]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

WOW Factor(1300)

2019.10.29

题意:给出一个字符串s,只含v和o,求其中wow对数量,w由两个v组成,v只能相邻。

思路:前缀和统计w对数即可,答案就是每个o两端w对数的积的总和。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
char s[maxn];
int a[maxn];
ll ans;
int main()
{
    scanf("%s",s);
    int len = strlen(s);
    //前缀和统计'w'对数 
    for(int i = 0;i < len;i++){
        if(s[i] == 'v' && s[i - 1] == 'v' && i != 0) a[i + 1] = a[i] + 1;
        else a[i + 1] = a[i];
    }
    for(int i = 0;i < len;i++){
        if(s[i] == 'o') ans += 1LL * a[i] * (a[len] - a[i]);
    }
    cout << ans << endl;
    return 0;
}

Lose it!(1300)

2019.10.30

题意:给出一串数n个,只含4,8,15,16,23,42,从中删去k个数,使数列长度能被6整除并且其子序列(n - k) / 6个都满足给出的形式[4,8,15,16,23,42]。求k的最小值

思路:贪心+计数,需要一定的思维,用seq[0]代表[4],seq[1]代表[4,8]……seq[5]代表[4,8,15,16,23,42],贪心进行计数,遇到4直接seq[0]++,其他情况贪心,优先补足长的序列,短的序列--,最后答案就是n - seq[5] * 6。

瞎搞貌似也过了,但是感觉会被hack。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
vector<int> p({4,8,15,16,23,42});
int n;
int main()
{
    n = read();
    vector<int> a(n);
    for(int i = 0;i < n;i++){
        a[i] = read();
        a[i] = lower_bound(p.begin(),p.end(),a[i]) - p.begin(); //类似离散化
    }
    vector<int> seq(6);
    for(int i = 0;i < n;i++){
        if(a[i] == 0) seq[0]++;
        else{
            int x = a[i];
            if(seq[x - 1] > 0) seq[x - 1]--,seq[x]++;
        }
    }
    printf("%d\n",n - seq[5] * 6);
    return 0;
}

Candies! (1400)

题意:给定n个数,每次查询一个区间\([l,r]\)。对这个区间内的数,相邻两个数之和超过10,则得到一个candy,然后将他们之和取模10的结果作为新的数。

一共操作\(l−r\)次,问这个区间得到的candy数。

思路:一种思路是可以发现,实际上candy数是\(⌊区间和/10⌋\),而新得到的数实际上是/10之后的余数部分。

每次操作的过程其实可以看成是两两求和然后取/10的商,然后把余数部分留给下一次操作。

所以ans\([l,r]\)=\(\frac{⌊a_l+a_{l+1}~+...+a_r⌋ }{10}\)

另一种思路是用倍增的思想进行dp。

\(dp[i][j]\)表示以\(i\)为起点,长度为\(2^j\)的区间所得到的candy数量。因为转移的时候还需要知道当前操作之后的数是什么,所以用\(sum[i][j]\)记录对应操作后的数。

\(dp[i][j]=dp[i][j−1]+dp[i+2^{j−1}][j−1]+(sum[i][j−1]+sum[i+2^{j−1}][j−1]≥10)\)

$sum[i][j] = (sum[i][j - 1] + sum[i + 2^{j - 1}][j - 1]) $ % \(10\)

倍增解法

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
inline int read(){
    int f = 1,x = 0; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - 48;ch = getchar();}
    return f * x;
}
int n,q,l,r;
int a[maxn];
int f[maxn][25],sum[maxn][25];
void gao(){
    for(int i = 1;i <= n;i++){
        f[i][0] = 0;
        sum[i][0] = a[i];
    }
    for(int j = 1;(1 << j) <= n;j++){
        for(int i = 1;i + (1 << (j - 1)) <= n;i++){
            f[i][j] = f[i][j - 1] + f[i + (1 << (j - 1))][j - 1] + 
                ((sum[i][j - 1] + sum[i + (1 << (j - 1))][j - 1]) >= 10);
            sum[i][j] = (sum[i][j - 1] + sum[i + (1 << (j - 1))][j - 1]) % 10;
        }
    }
}
int query(int l,int r){return f[l][(int)log2(r - l + 1)];}
int main()
{
    n = read();
    for(int i = 1;i <= n;i++) a[i] = read();
    gao();
    q = read();
    while(q--){
        l = read(); r = read();
        printf("%d\n",query(l,r));
    }
    return 0;
}

智慧解法

#include <bits/stdc++.h>
const int maxn = 1e5 + 5;
int n, q;
int num[maxn], sum[maxn];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &num[i]);
        sum[i] = sum[i - 1] + num[i];
    }
    scanf("%d", &q);
    while(q--){
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", (sum[r] - sum[l - 1]) / 10);
    }
    return 0;
}

Diverse Garland(1400)

2019.10.31

题意:给出一个只含R,G,B的字符串,改变其中的字符,使相邻两个字符都不相同,求最少改变量?

思路:模拟即可,遇到和前面一个字符相同的字符,把它改变为和前后相邻字符都不相同的字符即可,统计答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int n,ans;
char s[maxn];
string k = "RGB";
int main()
{
    scanf("%d",&n); scanf("%s",s + 1);
    for(int i = 2;i <= n;i++){
        if(s[i] == s[i - 1]){
            for(int j = 0;j < 3;j++){
                if(k[j] != s[i - 1] && k[j] != s[i + 1]){s[i] = k[j];ans++;break;} 
            }
        }
    }
    cout << ans << endl;
    for(int i = 1;i <= n;i++) cout << s[i];
    cout << endl;
    return 0;
}

Dima and a Bad XOR(1600)

2019.10.31

题意:给你一个n*m的矩阵,每一行你可以选一个数,在每一行都选数的前提下,能不能使每一行选的这些数的异或值不为0,如果存在相应方案,输出这些方案对应的每一行数的下标。

思路:数学+思维,首先将每一行第一列的数异或起来,若不为0,输出答案;否则对于每一行,如果能找出一个与该行第一列的数不相同的数,则存在对应方案,如果找不到,说明这一行所有数相同,该行无法修改。如果所有行都不满足条件,则没有方案,否则必然存在一种方案使得异或值不为0.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 505;
int n,m;
int a[maxn][maxn];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++) scanf("%d",&a[i][j]);
    }
    int ans = 0;
    for(int i = 1;i <= n;i++) ans ^= a[i][1];
    if(ans){
        cout << "TAK" << endl;
        for(int i = 1;i <= n;i++) cout << 1 << " ";
        cout << endl;
    } else{
        bool ok = 0;
        int pos = -1,row = -1;
        for(int i = 1;i <= n;i++){
            if(ok) break;
            for(int j = 2;j <= m;j++){
                if(a[i][j] != a[i][1]){
                    row = i,pos = j;
                    ok = 1;
                    break;
                }
            }
        }
        if(ok){
            cout << "TAK" << endl;
            for(int i = 1;i <= n;i++){
                if(i == row) cout << pos << " ";
                else cout << 1 << " ";
            }
            cout << endl;
        } else{
            cout << "NIE" << endl;
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/Rui-Roman/p/11789143.html