状态转移
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; }