状态转移

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\n");
       if ( cnt == n-1 ) { printf("0\n"); }  // 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\n", 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\n", 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\n" , &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\n", pos);
    if(pos != s && a[pos - 1] != a[pos]) {
        for(int i = pos; i > s; i--) {
            printf("%d L\n", ind + i - s + 1);
        }
        for(int i = pos + 1; i <= t; i++) {
            printf("%d R\n", ind + 1);
        }
    }
    else {
        for(int i = pos; i < t; i++) {
            printf("%d R\n", ind + pos - s + 1);
        }
        for(int i = pos; i > s; i--) {
            printf("%d L\n", 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\n");
    else printf("%lld\n", 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\n");
    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\n");
    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\n");
else printf("%lld\n", res);

return 0;
}

 

posted @ 2019-04-03 20:52  downrainsun  阅读(243)  评论(0编辑  收藏  举报