zoukankan      html  css  js  c++  java
  • 状态转移

    https://www.cnblogs.com/windysai/p/6824505.html

    https://vjudge.net/contest/218179#problem/C

    codeforces 798C.Mike and gcd problem 解题报告

     

    题目意思:给出一个n个数的序列:a1,a2,...,an (n的范围[2,100000],ax的范围[1,1e9] )

    现在需要对序列a进行若干变换,来构造一个beautiful的序列: b1,b2, ..., bn,使得最大公约数 gcd(b1,b2,...,bn) > 1。

    变换:  任意ai,ai+1 进行一次操作时,可以用 ai-ai+1, ai+ai+1 来替换。

    问序列 a 构造成 序列 b ,使得gcd(b序列) > 1 的最小操作次数

    题目解析:

      第一种解法:思维

      首先,这个题目是肯定有解的,也就是恒输出yes

         试想一下,相邻两个数之间无非就是四种情况:

         (1)对于同偶情况,不需要做转换,公约数直接为2;

         (2)对于同奇情况,只需要变换一次,两奇数进行加减操作,最终结果是偶数,公约数此时为2

      (3)一奇一偶,变换两次: ai, ai+1 ——》 ai-ai+1, ai+ai+1  ——》2(ai+1,ai) ——》 公约数为2

      此时问题就转化成: 构造一个序列所有数的公约数为2的最少操作次数。

          当然,在处理序列之前,要先判断整个序列是否已经有公约数了(注意,并不一定为2); 如果有,代表已经符合条件:gcd(b1,b2,...,bn) > 1,直接输出0即可。(不需要对序列a进行任何操作。

    第二种解法:dp

    设:

    dp[i][0]: 前i-1个数为偶数,第i个数为偶数的最少操作次数
    dp[i][1]:  前i-1个数为偶数,第i个数为奇数的最少操作次数
    如果第 i 个数是奇数,
    dp[i][0] = min(dp[i-1][0]+2, dp[i-1][1]+1);

    dp[i][1] = min(dp[i-1][0], inf);

    如果第 i 个数是偶数,
    dp[i][0] = min(dp[i-1][0], dp[i-1][1]+2);
    dp[i][1] = inf;

    还有一个初始化的问题需要注意下:
    dp[0][!(a[0]%2)] = inf;      ——》 这个要细心体会下
    假设序列中第一个数就是偶数,dp[0][0]= 0 dp[0][1]= inf
    假设序列中第一个数就是奇数,dp[0][0]= inf   dp[0][1]= 0

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    using namespace std;
    
    const int inf = 10000000;
    const int maxn = 1e5 + 5;
    int a[maxn];
    // dp[i][0]: 前i-1个数为偶数,第i个数为偶数的最少操作次数
    // dp[i][1]:  前i-1个数为偶数,第i个数为奇数的最少操作次数
    int dp[maxn][2];
    
    int GCD(int b1, int b2)
    {
        if (b2 == 0)
            return b1;
        return GCD(b2, b1%b2);
    }
    
    int main()
    {
        #ifndef ONLINE_JUDGE
            freopen("in.txt", "r", stdin);
        #endif // ONLINE_JUDGE
    
        int n;
        while (scanf("%d", &n) !=EOF) {
           scanf("%d", &a[0]);
           int t=a[0], cnt = 0;
    
           for (int i = 1; i < n; i++) {
                scanf("%d", &a[i]);
                t = GCD(t, a[i]);
                if (t > 1) { cnt++; }
           }
           printf("YES
    ");
           if ( cnt == n-1 ) { printf("0
    "); }  // all 有公约数
    
           else {
                memset(dp, 0, sizeof(dp));
                dp[0][!(a[0]%2)] = inf;
    
                for (int i = 1; i < n; i++) {
                    if (a[i]%2) {     // odd
                        dp[i][0] = min(dp[i-1][0]+2, dp[i-1][1]+1);
                        dp[i][1] = min(dp[i-1][0], inf);
                    }
                    else {
                        dp[i][0] = min(dp[i-1][0], dp[i-1][1]+2);
                        dp[i][1] = inf;
    
                    }
                }
                printf("%d
    ", dp[n-1][0]);
           }
        }
        return 0;
    }

    https://vjudge.net/contest/218179#problem/F

    题意:给你一个序列。一个数k,m次同样的操作,每次先从大到小排序,然后把奇数位的值异或k。

    然后输出最后那个序列的最大值与最小值。


    可以用计数排序来搞; 或者说将桶排序
    因为数字最大为1000; 
    所以将近O(K*1000)的复杂度; 
    完全可以的; 
    每次从小到大(0到1000(枚举数字; 
    然后根据该数字的个数判断该数字有几个数字要进行异或,几个数字保持原样. 
    然后在新的计数排序数组里面记录一下就好; 
    最后一遍循环搞出最大最小值就好; 

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    const int maxm = 1e5 + 5;
    const int inf = 1e9 + 7;
    int n, m, k;
    int a[maxm];
    int num[2][1024];
    int main() {
    while(~scanf("%d%d%d", &n, &m, &k)) {
        memset(num[0], 0, sizeof(num[0]));
        int ma = -inf, mi = inf;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            num[0][a[i] ]++;
            ma = max(ma, a[i]);
            mi = min(mi, a[i]);
        }
        for(int i = 1; i <= m; i++) {
            int cnt = 0;
            memset(num[i & 1], 0, sizeof(num[i & 1]));
            for(int j = mi; j <= ma; j++) {
                if(num[(i - 1) & 1][j]) {
                    int x = j ^ k;
                    if(((num[(i - 1) & 1][j]) & 1) == 0) {
                        num[i & 1][j] += num[(i - 1) & 1][j] / 2;
                        num[i & 1][x] += num[(i - 1) & 1][j] / 2;
                    }
                    else {
                        if(cnt & 1) {
                            num[i & 1][j] += num[(i - 1) & 1][j] / 2 + 1;
                            num[i & 1][x] += num[(i - 1) & 1][j] / 2;
                        }
                        else {
                            num[i & 1][x] += num[(i - 1) & 1][j] / 2 + 1;
                            num[i & 1][j] += num[(i - 1) & 1][j] / 2;
                        }
                    }
                }
                cnt += num[(i - 1) & 1][j];
            }
            ma = -inf;
            mi = inf;
            for(int j = 0; j < 1024; j++) {
                if(num[i & 1][j]) {
                    ma = max(ma, j);
                    mi = min(mi, j);
                }
            }
        }
        printf("%d %d
    ", ma, mi);
    }
    return 0;
    }

    https://vjudge.net/contest/218179#problem/J

     记录状态转移路径。

    题目大意:

           有n个人和m句话,有些话的说话人不明,要求是每个人不能连着说两句话,每句话不能提到自己,看看能不能将这些话的说话人都找出来,答案可能不固定。

    解题思路:

           根据题目意思将问题简化,建立dp[i][j]意思为第 j 句话可能是 i 说的,首先明确说话人的话,将已知的说话人作为唯一的可能对象,其他的话只要不是提到的人都作为可能对象。接下来其实就是dfs找一条结果树,只要满足相邻两句话不是一个人说的就行了,但是直接爆搜的复杂度有点大,可以使用记忆化搜索,记录所有可能解。最后输出就好了。


    我的解题思路

    说话的顺序就是一个天然的序, 安排第i个人说话的时候,有两个限制条件, 1. 不能与前一个说话的人相同, 2. 不能出现在后面说话的句子里

    那么DP 思想就很显然了。。。。。 F【i,j】 表示第i句话是否为第j个人说的。

    转移方程也就so eazy了 , 我们可以通过枚举前一句话是由谁讲的,进行更新状态!!!!

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 105;
     
    int n , m;
    map<string , int> h;
    string name[N] , str[N] , re[N];
    bool f[N][N];
    int g[N][N];
     
    void print(int x , int y) {
        if (x == 0) {
            return;
        }
        print(x - 1 , g[x][y]);
        cout << name[y] << re[x - 1] << endl;
    }
     
    void work() {
        scanf("%d" , &n);
        h.clear();       //名字编号 
        for (int i = 0 ; i < n ; ++ i) {
            cin >> name[i];
            h[name[i]] = i;
        }
        scanf("%d
    " , &m);
        memset(f , 0 , sizeof(f));      // f【i,j】 = 1 表示 第i句话是第j个人说的 ........  
        memset(g , 0 , sizeof(g));
        f[0][n] = 1;
        for (int i = 0 ; i < m ; ++ i) {
            getline(cin , str[i]);
            re[i].clear();
        }
        for (int i = 0 ; i < m ; ++ i) {
            string usr , buf = str[i];
            int p = 0;
            while (p < buf.size() && buf[p] != ':') {
                usr += buf[p ++];
            }
            int q = p;
            while (q < buf.size()) {
                re[i] += buf[q ++];
            }
            set<int> mention;
            while (p < buf.size()) {
                string word;
                while (p < buf.size() && isalnum(buf[p])) {
                    word += buf[p ++];
                }
                if (h.count(word)) {             //存在该名单中 
                    mention.insert(h[word]);     // 第i句化不能由  mention 这些人 来讲 
                }
                if (p < buf.size()) ++ p;
            }
            if (usr == "?") {                    //该名单暂定时 
                for (int j = 0 ; j <= n ; ++ j) {  // j<=n 考虑第一个人这种特殊情况   ,遍历第i句话可以由哪些人说 
                    if (!f[i][j]) continue;        // 前一句话话如由j这个人说  ..  进行更新 
                    for (int k = 0 ; k < n ; ++ k) {      //则接下来只要这两个人不相邻,且不在黑名单中 就可以更新 
                        if (mention.count(k) || k == j) continue;
                        f[i + 1][k] = 1;         
                        g[i + 1][k] = j;          // 记录该状态的转移 
                    }
                }
            }
            else {
                if (!h.count(usr)) {     // 不存在这个人 
                    puts("Impossible");
                    return;
                }
                int id = h[usr];          //第id个人 
                if (!mention.count(id)) {       //第id个人不在黑名单内 
                    for (int j = 0 ; j <= n ; ++ j) {     //更新当前这句话是由 id 这个讲的 
                        if (f[i][j] && id != j) {
                            f[i + 1][id] = 1;
                            g[i + 1][id] = j;
                        }
                    }
                }
            }
        }
        int x = -1;
        for (int i = 0 ; i < n ; ++ i) {
            if (f[m][i]) {
                x = i;
            }
        }
        if (x == -1) {
            puts("Impossible");
        } else {
            print(m , x);
        }
    }
     
    int main() {
        int T;
        scanf("%d" , &T);
        while (T --) {
            work();
        }
    }

    https://vjudge.net/contest/218179#problem/M

    模拟题

    题目大意:有n只怪兽排成一列,体重大的可以吃掉相邻体重比他小的,吃掉后,体重便会相应地增加。给定初始怪兽和目标状态怪兽体重,问是否可能达成目标状态。可能输出“”YES“和步骤,不可能输出“NO”。

    因为只能吃相邻的,所以目标状态肯定是由初始状态的连续子段和组成。那么遍历目标状态的各体重,判断能否由初始状态到达,即检查是否初始状态有连续子段的和等于该体重。若有连续子段等于该目标体重,那么是否一定能达到呢?可以发现,只要连续子段中各体重不全相等或者连续子段长度等于1,那么一定通过“一直向左吃之后一直向右吃”或者“一直向右吃之后一直向左吃”达到。

    至于步骤的输出,遍历检查时记下各目标体重对应的连续子段的左右端点,在通过以上方法“吃”即可。

    那么“吃“的起始点该怎么找呢?率先开吃的怪兽应该满足以下条件:1)它的体重是最重的,2)它的两边至少存在一个比它轻的。这两个条件保证了它这一次有的吃,而且吃完后还是最重的。

    另外“NO”的判断,要注意吃完还有多余的怪兽,以及怪兽不够吃均是“NO”

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    
    using namespace std;
    
    const int maxm = 505;
    int n, k;
    int a[maxm], b[maxm];
    int l[maxm], r[maxm];
    int sum[maxm];
    
    bool check(int s, int t) {
        if(s == t) return true;
        for(int i = s + 1; i <= t; i++) {
            if(a[i] != a[i - 1]) return true;
        }
        return false;
    }
    
    void print(int ind, int s, int t) {
        int ma = 0;
        for(int i = s; i <= t; i++) ma = max(ma, a[i]);
        int pos = s;
        for(int i = s; i <= t; i++) {
            if(a[i] == ma) {
                if(i != s && a[i- 1] != ma) {
                    pos = i;
                    break;
                }
                if(i != t && a[i + 1] != ma) {
                    pos = i;
                    break;
                }
            }
        }
    //    printf("%d
    ", pos);
        if(pos != s && a[pos - 1] != a[pos]) {
            for(int i = pos; i > s; i--) {
                printf("%d L
    ", ind + i - s + 1);
            }
            for(int i = pos + 1; i <= t; i++) {
                printf("%d R
    ", ind + 1);
            }
        }
        else {
            for(int i = pos; i < t; i++) {
                printf("%d R
    ", ind + pos - s + 1);
            }
            for(int i = pos; i > s; i--) {
                printf("%d L
    ", ind + i - s + 1);
            }
        }
    }
    
    int main() {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            sum[i] = sum[i - 1] + a[i];
        }
        scanf("%d", &k);
        for(int i = 1; i <= k; i++) scanf("%d", &b[i]);
        int s = 1, t = 1;
        int flag = 0;
        for(int i = 1; i <= k; i++) {
            while(t <= n && sum[t] - sum[s - 1] < b[i]) {
                t++;
            }
            if(t > n || s > n || sum[t] - sum[s - 1] != b[i] || !check(s, t)) {
                flag = 1;
                break;
            }
            if((t == n && i != k) || (i == k && t != n)) {
                flag = 1;
                break;
            }
            l[i] = s, r[i] = t;
            s = t + 1;
        }
        if(flag) puts("NO");
        else {
            puts("YES");
            for(int i = 1; i <= k; i++) print(i - 1, l[i], r[i]);
        }
        return 0;
    }

    https://vjudge.net/contest/218179#status/xiayuyang/P/0/

    题目大意:
    输入一个正整数n,再输入一个长度为n的数组c[0…n-1]和n个字符串。
    已知把第i个字符串翻转需要的能量为c[i],求把n个字符串通过翻转操作变成字典序所需最小能量,若不能实现则输出-1。

    题目分析:
    这是一个最优决策的问题,而且容易知道,排第i个字符串时,必须保证前i-1个字符串都有可能通过翻转操作成为字典序,否则无解,也满足最优子问题的特性,所以考虑DP。

    设dp[i][0]表示前i个字符串已经排好,最后一个字符串没翻转,dp[i][1]表示前i个字符串已经排好,最后一个字符串翻转。然后转移一下就好了,我也写不太好转移方程。。。。

    反正dp[i][0]有三种取值,dp[i-1][0],dp[i-1][1],-1.分别考虑s[i]与s[i-1]和rev[i-1]的关系即可。
    dp[i][1]也有三种取值,dp[i-1][0]+c[i],dp[i-1][1]+c[i],-1,同理取决于rev[i]和s[i-1]、rev[i-1]的关系

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxm = 1e5 + 5;
    const ll inf = 1e18;
    ll c[maxm];
    int n;
    ll dp[maxm][2];
    string s[maxm][2];
    string st;
    int main() {
        scanf("%d",&n);
        for(int i = 1; i <= n; i++) scanf("%lld", &c[i]);
        for(int i = 1; i <= n; i++) {
            cin >> st;
            s[i][0] = st;
            reverse(st.begin(), st.end());
            s[i][1] = st;
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < 2; j++) {
                dp[i][j] = inf;
            }
        }
        dp[1][0] = 0;
        dp[1][1] = c[1];
        int flag = 0;
        for(int i = 2; i <= n; i++) {
            if(s[i][0] >= s[i - 1][0]) dp[i][0] = min(dp[i][0], dp[i - 1][0]);
            if(s[i][0] >= s[i - 1][1]) dp[i][0] = min(dp[i][0], dp[i - 1][1]);
            if(s[i][1] >= s[i - 1][0]) dp[i][1] = min(dp[i][1], dp[i - 1][0] + c[i]);
            if(s[i][1] >= s[i - 1][1]) dp[i][1] = min(dp[i][1], dp[i - 1][1] + c[i]);
            if(dp[i][0] == inf && dp[i][1] == inf) {
                flag = 1;
                break;
            }
        }
        if(flag) printf("-1
    ");
        else printf("%lld
    ", min(dp[n][0], dp[n][1]));
        return 0;
    }

    https://vjudge.net/contest/282940#problem/I

    贪心题,比较复杂

    【题意】

    现在有(n+1)个城市,其中第1~n个城市每个城市有一个人,第0个城市是人们需要聚集的地方。还有m条航班,每条航班从0到其他城市或从其他城市到0,当天即可抵达,现在需要选定一个时间段,长度为k天,使得这一个时间段里人们都在0城市工作(航班抵达和离开0城市的那一天不能进行工作),问把n个人送到0城市,工作完成后再送回去的最小花费。

    【思路】

    我们利用贪心的思维,我们去枚举长度为k的区间[l,r],使得所有人能在第l天之前抵达0城市,第r天之后离开0城市。

    那么我们需要做的便是算出能使n个人在第i天及以前能够全部到达0城市的最小花费ss[i],以及在第i天及以后能够全部离开0城市的最小花费tt[i],最后扫一遍即可。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    
    using namespace std;
    const int maxm = 1e5 + 5;
    const int maxm1 = 1e6 + 5;
    typedef long long ll;
    int n, m, k;
    struct NODE {
    int d, u, v, w;
    bool operator < (const NODE &a) const {
    return d < a.d;
    }
    } node[maxm];
    ll dis[maxm], sum1[maxm1], sum2[maxm1];
    
    int main() {
    scanf("%d%d%d", &n, &m, &k);
    int Max = 0, cnt = n;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d%d%d", &node[i].d, &node[i].u, &node[i].v, &node[i].w);
        Max = max(Max, node[i].d);
    }
    sort(node + 1, node + m + 1);
    for(int i = 1; i <= m; i++) {
        int index = node[i].d;
        if(node[i].u != 0 && node[i].v == 0) {
            if(dis[ node[i].u ] == 0) {
                dis[ node[i].u ] = node[i].w;
                cnt--;
                if(!cnt) {
                    ll sum = 0;
                    for(int j = 1; j <= n; j++) {
                        sum += dis[j];
                    }
                    sum1[index] = sum;
                }
            }
            else if(dis[ node[i].u ] > node[i].w) {
                dis[ node[i].u ] = node[i].w;
                if(!cnt) {
                    ll sum = 0;
                    for(int j = 1; j <= n; j++) {
                        sum += dis[j];
                    }
                    sum1[index] = sum;
                }
            }
        }
    }
    if(cnt != 0) {
        printf("-1
    ");
        return 0;
    }
    memset(dis, 0, sizeof(dis));
    cnt = n;
    for(int i = m; i >= 1; i--) {
        int index = node[i].d;
        if(node[i].u == 0 && node[i].v != 0) {
            if(dis[ node[i].v ] == 0) {
                dis[ node[i].v ] = node[i].w;
                cnt--;
                if(!cnt) {
                    ll sum = 0;
                    for(int j = 1; j <=n; j++) {
                        sum += dis[j];
                    }
                    sum2[index] = sum;
                }
            }
            else if(dis[ node[i].v ] > node[i].w) {
                dis[ node[i].v ] = node[i].w;
                if(!cnt) {
                    ll sum = 0;
                    for(int j = 1; j <= n; j++) {
                        sum += dis[j];
                    }
                    sum2[index] = sum;
                }
            }
        }
    }
    if(cnt != 0) {
        printf("-1
    ");
        return 0;
    }
    for(int i = 1; i <= Max; i++) {
        if(!sum1[i]) sum1[i] = sum1[i - 1];
        else if(sum1[i] > 0 && sum1[i - 1] > 0) {
            sum1[i] = min(sum1[i], sum1[i - 1]);
        }
    }
    for(int i = Max; i >= 1; i--) {
        if(!sum2[i]) sum2[i] = sum2[i + 1];
        else if(sum2[i] > 0 && sum2[i + 1] > 0) {
            sum2[i] = min(sum2[i], sum2[i + 1]);
        }
    }
    ll res = 1e14;
    for(int i = 1; i <= Max, i + k + 1 <= Max; i++) {
        if(sum1[i] != 0 && sum2[i + k + 1] != 0) {
            res = min(res, sum1[i] + sum2[i + k + 1]);
        }
    }
    if(res == 1e14) printf("-1
    ");
    else printf("%lld
    ", res);
    
    return 0;
    }
  • 相关阅读:
    .c 文件取为.o文件
    wildcard 处理全部文件
    专家解读Linux操作系统内核中的GCC特性
    Yeoman:适合现代Web应用的现代工作流
    【转】nodejs
    node.js
    2019暑假集训 种树
    2019.6.5 NOIP2014 day2 t2 寻找道路
    2019.6.1 最优贸易
    2019.5.11 海淀区赛之杯子
  • 原文地址:https://www.cnblogs.com/downrainsun/p/10651442.html
Copyright © 2011-2022 走看看