P1022 计算器的改良(解简单一元一次方程), P1583 魔法照片(多个信息多个处理系列(题意比较绕)), P1588 丢失的牛(倒逆思想), P1459 三值的排序, P2386 放苹果(递归、dp、dfs都可以做), P2562 [AHOI2002]Kitty猫基因编码,P1106 删数问题 (贪心),P2758 编辑距离(dp),P2404 自然数的拆分问题(回溯)
P1022 计算器的改良(解简单一元一次方程) P1022
这道题如果直接读入一个字符串再一个个处理的话可以会想复杂,我之前这样的处理的话代码长还wa(菜鸡),如果采用栈的思想,则会简单很多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <iostream> #include <cstdio> #include <cmath> using namespace std;typedef long long ll;ll nowValue = 0 ; ll coefficient; ll position = 1 ; ll sign = 1 ; ll value; bool is = false ; char x;int main () { char c; c = getchar (); while (1 ) { if (c >= 'a' && c <= 'z' ) { x = c; if (nowValue == 0 && !is) { coefficient += position * sign; is = false ; } else { coefficient += position * sign * nowValue; sign = 1 ; nowValue = 0 ; is = false ; } } else if (c >= '0' && c <= '9' ) { nowValue = nowValue * 10 + c - '0' ; is = true ; } else if (c == '-' ) { value += -position * sign * nowValue; sign = -1 ; nowValue = 0 ; is = false ; } else if (c == '+' ) { value += -position * sign * nowValue; sign = 1 ; nowValue = 0 ; is = false ; } else if (c == '=' ) { value += -position * sign * nowValue; nowValue = 0 ; sign = 1 ; position = -1 ; is = false ; } else { value += -position * sign * nowValue; break ; } c = getchar (); } double ans = (double )(value) / coefficient; printf ("%c=%.3lf" ,x,ans==0 ?abs (ans):ans); return 0 ; }
P1583 魔法照片(多个信息多个处理系列(题意比较绕)) p1583
这道题如果直接用结构体或者用map和数组模拟的话,原则上是可以,但是比较复杂(因为题目比较绕),后来在评论区看见一个比较方便的解法
摘自评论区 作者: 白井黑子1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 题目实际上指的是先给定你1~n号对应的权值,从大到小排序后根据当前次序再编第二次号,分类别加上对应的e[i],再次从大到小进行排序后输出前k大权值分别的初始编号。 注意!第二次编号与最终的编号输出无关,仅用于分类。 举个例子: 输入:(测试点#1) 14 3 9 2 5 4 0 0 0 0 0 0 1 1 3 4 9 2 8 2 8 8 8 7 1 9 编号为:1 2 3 4 5 6 7 8 9 10 11 12 13 14 第一次排序后:(序号小优先) 9 9 8 8 8 8 7 4 3 2 2 1 1 1 编号为:5 14 7 9 10 11 12 4 3 6 8 1 2 13 类别为:1 2 3 4 5 6 7 8 9 10 1 2 3 4 加上e[i]后:18 11 13 12 8 8 7 4 3 2 11 3 6 5 第二次排序后:(序号小优先) 18 13 12 11 11 8 8 7 6 5 4 3 3 2 编号为:5 7 9 8 14 10 11 12 2 13 4 1 3 输出:5 7 9 这就是这道题大概的思路:输入——排号——排序——分类加e[i]——排序——输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <cstdio> #include <algorithm> using namespace std;int e[12 ],n,k;struct person { int w; int num; int d; }p[20010 ]; int w_comp (const person &a,const person &b) { if (a.w!=b.w)return a.w>b.w; return a.num<b.num; } int main () { scanf ("%d%d" ,&n,&k); for (int i=0 ;i<10 ;i++)scanf ("%d" ,&e[i]); for (int i=0 ;i<n;i++){ scanf ("%d" ,&p[i].w); p[i].num=i+1 ; } sort (p,p+n,w_comp); for (int i=0 ;i<n;i++){ p[i].d=i%10 ; p[i].w+=e[p[i].d]; } sort (p,p+n,w_comp); for (int i=0 ;i<k;i++)printf ("%d " ,p[i].num); }
P1588 丢失的牛(倒逆思想) p1588
这道题可以用bfs,但是可能比较复杂,后来在题解发现了比较容易的解法,就倒过来想加上一点贪心。
作者: 我真的要吃酱
看到题解一溜溜全是搜索,这里发个带贪心的解题方法,而且是所有题解中最快的(0ms)
原题是FJ追牛,我们可以反过来想,变成牛追FJ,这样子,牛的移动方式有y+1,y-1,和直接y/2,因此我们在移动牛的时候可以优先 考虑能否y/2,然后再考虑y+1,y-1的移动方式
之所以要反过来想是因为 在FJ追牛的时候是不能优先考虑x2再考虑x+1,x-1的,也不能优先考虑x+1,x-1再考虑x 2!!
举个例子,FJ在25,牛在102,先2再+1再 2共三步,FJ追牛的时候没办法优先考虑怎么走。但是如果是牛追FJ,牛可以直接y/2,这个时候牛的位置变成51,可以发现是奇数,没办法优先y/2,所以y-1变成50(y+1也要考虑),然后优先考虑y/2变成25,共三步。
然后这个思路我刚开始是用数组写,发现在-1,+1那里要考虑很多东西,然后看下了用队列,瞬间简单了很多。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <queue> using namespace std;int main () { typedef pair<int , int > P; P p; queue<P>q; int t; cin >> t; while (t--) { int Min = 100000000 ; int n; cin >> n >> p.first; q.push (P (p.first, 0 )); while (q.size ()) { p = q.front (); q.pop (); if (p.first < n) { p.second += n - p.first; if (Min > p.second) Min = p.second; } else if (p.first % 2 == 0 ) { if (p.first / 2 > n) q.push (P (p.first / 2 , p.second + 1 )); else if (n - p.first / 2 < p.first - n) q.push (P (p.first / 2 , p.second + 1 )); else { p.second += p.first - n; if ( Min > p.second ) Min = p.second; } } else { q.push ( P (p.first+1 ,p.second+1 ) ); q.push ( P (p.first-1 ,p.second+1 ) ); } } cout << Min << endl; } return 0 ; }
P1459 三值的排序 题目:P1459
作者: 敌敌畏
这道题可以用线段树什么的,但是也可以用模拟,先模拟1、2、3各有多少个,发现1区间的2与2区间的1交换,发现1区间的3与3区间的1交换,发现2区间的3与3区间的2交换。
剩下的3个区间内的无法一次交换的数也只用两次交换就可以把无法一次交换的三个数换了。
主要是遍历过程。怎么样才能做到不漏呢。
下面代码主要思路:
区间一:应该存的是1,区间二:存的是应该是2,区间三同理
从末尾开始遍历区间三,如果是1的话,那么就在区间一找看看有没有3,有的话就交换,没有的话在区间一二搜索不等于1的值,然后交换,接着判断如果是2的话(如果上面找不到3将2与1交换的话,那么同样也可以判断到这个已经交换了的2),然后在区间2找3,如果找不到的话,在区间一找。其实主要是还是上面那个找不到1的话,就将其他值交换了,这样就可以将1换到上面了。
然后再处理区间二就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <algorithm> #include <iostream> using namespace std;const int len = 1010 ;int arr[len];int num[4 ]; int main () { int n; cin >> n; for (int i = 1 ;i <= n;i ++) { cin >> arr[i]; num[arr[i]]++; } num[2 ] = num[1 ] + num[2 ]; num[3 ] = num[1 ] + num[2 ] + num[3 ]; int ans = 0 ; for (int i = n; i > num[2 ]; i--) { if (arr[i] == 1 ) { bool l = 1 ; for (int k = 1 ; k <= num[1 ]; k++) if (arr[k] == 3 ) { swap (arr[k], arr[i]); l = 0 ; ans++; break ; } if (l) for (int k = 1 ;k <= num[2 ]; k++) { if (arr[k] != 1 ) { swap (arr[k], arr[i]); ans++; break ; } } } if (arr[i] == 2 ) { bool l = 1 ; for (int k = num[1 ] + 1 ; k <= num[2 ]; k++) if (arr[k] == 3 ) { swap (arr[k],arr[i]); ans++; l = 0 ; break ; } if (l) for (int k = 1 ; k <= num[1 ]; k++) if (arr[k] == 3 ){ ans ++; swap (arr[k], arr[i]); break ; } } } for (int i = num[2 ]; i >= num[1 ]+1 ; i--) { if (arr[i] == 1 ) for (int k = 1 ; k <= num[1 ]; k++) if (arr[k] == 2 ) { swap (arr[k], arr[i]); ans++; break ; } } cout << ans; return 0 ; }
P2386 放苹果 题目:P2386 放苹果
题解:作者: ybb756032937 递归
详细解释思路(当苹果数大于等于盘子数时的分法):一种是目前 所有的盘子都放一个苹果,盘子数不变,即:apple(m-n,n);
另一种是将一个盘子不放,再来进行思考,即:apple(m,n-1);
两种情况的总和就是答案;
接下来就用一个例子来详细解释:
现在有5个苹果和3个盘子;
因为苹果数大于盘子数,所以分成两种:
第一种:目前所有盘子放一个苹果(目前盘子数3个)apple(2,3);
第二种:拿一个盘子不放苹果,剩下的盘子继续考虑,apple(5,2);
apple(2,3):因为苹果数小于盘子数,所以apple(2,2);
apple(2,2):因为苹果数等于盘子数,所以又分为两种:
第一种:目前所有盘子放一个苹果(目前盘子数2个)apple(0,2); 没有苹果,达到边界,返回值1 ;
第二种:拿一个盘子不放苹果,剩下的盘子继续考虑,apple(2,1); 盘子数只有1个,达到边界,返回值1 ;
apple(5,2):因为苹果数大于盘子数,所以又分为两种:
第一种:目前所有盘子放一个苹果(目前盘子数2个)apple(3,2);
第二种:拿一个盘子不放苹果,剩下的盘子继续考虑,apple(5,1); 因为盘子数只有1个,达到边界,返回值1 ;
apple(3,2):因为苹果数大于盘子数,所以又分为两种:
第一种:目前所有盘子放一个苹果(目前盘子数2个)apple(1,2); 苹果数只有1个,达到边界,返回值1 ;
第二种:拿一个盘子不放苹果,剩下的盘子继续考虑,apple(3,1); 盘子数只有1个,达到边界,返回值1 ;
所有的值相加得到最后的结果5,记录在数组sum中,最后输出;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;int put (int m,int n) { if (m == 0 || m == 1 || n == 1 ) return 1 ; if (m < n) return put (m, m); if (m >= n) return put (m - n, n) + put (m, n - 1 ); } int main () { int t; cin >> t; while (t--) { int m ,n, cnt = 0 ; cin >> m >> n; cnt = put (m, n); cout << cnt << endl; } return 0 ; }
P2562 [AHOI2002]Kitty猫基因编码(递归) 题目:p2562
看到这道题一想就是递归题,类似于分段函数的题目都可以想想递归,但是有了别的思路,用队列来做,就是不断出队入队,但是如果提前出队的话这样的结果不对,如果都不出队的话,直接处理完了的话又不知道怎么结束循环,所以还是递归好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <string> using namespace std;string check (string s) { int mid = s.size () / 2 ; if (s.find ('0' ) != string::npos) { if (s.find ('1' ) != string::npos) return 'C' + check (s.substr (0 , mid)) + check (s.substr (mid, mid)); else return "A" ; } else return "B" ; } int main () { ios::sync_with_stdio (false ); string in; cin >> in; cout << check (in); return 0 ; }
P1209 [USACO1.3]修理牛棚 Barn Repair(贪心) 题目:p1209
题解作者: Starlight_Glimmer
典型的贪心题目
我们可以先假设只有一块木板从编号最小的牛棚一直铺到编号最大的牛棚,然后断开m-1处。自然要按相邻牛棚的编号差从大到小断开才能使我们断开的地方可以有效节省木板长度(因为中间省去的要更多)
另外,要将输入的数据排序,数据可能不是按编号从小到大给的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <functional> #include <algorithm> const int len = 1000000 ;using namespace std;int main () { int m, s, c; int C[len], dis[len]; cin >> m >> s >> c; for (int i = 0 ; i < c; i++) cin >> C[i]; if (m > c) { cout << c; return 0 ; } int ans = 0 ; sort (C, C + c); ans = C[c - 1 ] - C[0 ] + 1 ; for (int i = 0 ; i < c - 1 ; i++) dis[i] = C[i + 1 ] - C[i]; sort (dis, dis + c - 1 , greater<int >()); for (int i = 0 ; i < m - 1 ; i++) ans = ans - dis[i] + 1 ; cout << ans; return 0 ; }
P1106 删数问题 (贪心) P1106 删数问题
题解参考: 书海扬帆
这道题目是一道非常经典的贪心问题。由于n的位数非常多,而且删除操作改变的是每个数位,所以我们非常自然地能想到用字符串来存储n。
本题目的贪心策略:从高位到低位搜索,如果各位数字均递增,则删去最后一个数字,否则删除第一个递减区间的首字符。举个栗子,如果1234567要删除2位的话,我们必定是删除6和7,而3654321如果要删除2位的话,我们则要选择删去6和5.
重复以上过程s次,剩下的数字串便是问题的解了。
这里再谈一下0的问题,由于前导0不能输出(你见过有人把123写成0123或是000123嘛)所以我们再来一个布尔变量flag来记录每一位是否为0.但是这个时候还出现了一个问题,就是这个字符串被删除完了,只剩下一个0怎么办?比如说样例7,10删除1位之后,我们还是必须要输出0的。因此我们再建立一个cnt变量来存储输出的数字的个数。如果个数为0,那么说明整个字符串均为0,此时我们还需要输出一个0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <string> using namespace std;int main () { ios::sync_with_stdio (false ); string s; cin >> s; int n; cin >> n; int len = s.size (); for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < len; j++) { if (s[j] > s[j + 1 ]) { for (int k = j; k < len - 1 ; k++) s[k] = s[k + 1 ]; break ; } } len--; } int cnt = 0 ; int flag = 0 ; for (int i = 0 ; i < len; i++) { if (s[i] != '0' ) flag = 1 ; if (flag) { cout << s[i]; cnt++; } } if (cnt == 0 ) cout << 0 ; return 0 ; }
P2758 编辑距离(dp) p2758
题解参考:
4396瞎
qwaszx
做动态规划的题一般分为四个步骤:确定子问题—>定义状态—>转移方程—>避免重复求解
1.确定子问题:
由于对于字符串的操作只有4种情况(删除,添加、更改、不变),所以该题的子问题就是进行了这4种操作后的A字符串变为B字符串需要多少步。
2.定义状态:
也就是说递归的dp函数需要哪些参数,参数越少越好因为需要建memo。后来想到dp(i,j)代表字符串A的前i个字符(包括第i个)变为字符串B的前j个(包括第j个)需要多少步。也就是说解出来dp(lenA,lenB)就可以了。
3.转移方程:
假设用f[i][j]表示将串a[1…i]转换为串b[1…j]所需的最少操作次数(最短距离)
首先是边界:
①i==0时,即a为空,那么对应的f[0][j]的值就为j:增加j个字符,使a转化为b
②j==0时,即b为空,那么对应的f[i][0]的值就为i:减少i个字符,使a转化为b
1 2 3 4 5 6 7 8 转移有三种: ①f[i - 1][j] -> f[i][j],插入即可,f[i][j] = f[i - 1][j] + 1; ②f[i][j - 1] ->f [i][j],删除即可f[i][j] = f[i][j - 1] + 1; ③f[i - 1][j - 1] -> f[i][j],要把a[i - 1]换成b[j - 1],如果相同就不用换了
那么,
1 2 3 4 if (a[i-1 ] == b[j-1 ]) f[i][j] = f[i - 1 ][j - 1 ]; else f[i][j] = min (f[i - 1 ][j] + 1 ,f[i][j - 1 ] + 1 ,f[i - 1 ][j - 1 ] + 1 );
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <algorithm> #include <string> using namespace std;string s1, s2; const int maxLen = 3000 ;int arr[maxLen][maxLen];void dp (int lenA, int lenB) { for (int i = 1 ; i <= lenA; i++) arr[i][0 ] = i; for (int i = 0 ; i <= lenB; i++) arr[0 ][i] = i; for (int i = 1 ; i <= lenA; i++) for (int j = 1 ; j <= lenB; j++) { if (s1[i - 1 ] == s2[j - 1 ]) arr[i][j] = arr[i - 1 ][j - 1 ]; else arr[i][j] = min (arr[i - 1 ][j] + 1 , min (arr[i][j - 1 ] + 1 , arr[i - 1 ][j - 1 ] + 1 )); } } int main () { ios::sync_with_stdio (false ); cin >> s1 >> s2; dp (s1.size (), s2.size ()); cout << arr[s1.size ()][s2.size ()]; return 0 ; }
P2404 自然数的拆分问题(回溯) p2404
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;int arr[1000 ] = {1 }; int n, m;void print (int num) { for (int i = 1 ; i < num; i++) cout << arr[i] << "+" ; cout << arr[num] << endl; } void dfs (int num) { for (int i = arr[num - 1 ]; i <= m; i++) { if (i == n) continue ; arr[num] = i; m -= i; if (m == 0 ) print (num); else dfs (num + 1 ); m += i; } } int main () { ios::sync_with_stdio (false ); cin >> n; m = n; dfs (1 ); return 0 ; }