今天上午讲的是搜索,主要讲的是搜索的顺序,提出了“制约力”的概念,把它按照从大到小的顺序搜索,例题有八数码问题、质数方阵。在图论方面主要讲了SPFA,竟然是中国西安交大的教授提出来的,不简单啊!
中午去了龚秋成的生日宴会,在伊典。吃完后后看是上演奶油大战……衣服则成了牺牲品。
下午到时已迟到一个小时,所以赶紧做。下午的题目质量比较高,都值得一写。
A题:
题目大意:能否把把N(<=50)个数分成四组,它们的和相等?
算法:搜索
优化:先对数降序排序,第一个人为放到第一组,这样就可以过了
B题:
题目大意:乱序给定一个数的各给位数,问是否存在一个组合,使得到的数能被99整除?
数学知识:
1.被9乘除的充要条件是各位之和能被9整除;
2.被11整除的充要条件是奇数位与偶数位和之差能被11整除;
其中1容易实现,对于2则要进行类似与Tug Of War那题,把它分成两组,且使用个数和相差不超过1;(题目数据有问题,做成了和要相等,不过关系不大)
算法1:搜索
基本思路是枚举分成两堆,看哪一堆有哪些数字,确定了思路:
dfs(dep,sum_now,lastnum),其中dep表示当前枚举第几个数字,sum_now表示当前获得的和是多少,lastnum表示上一次枚举的是哪一个数(按升序枚举)。
优化:
1.由于分的个数是确定的,所以可以估计出当前所能达到的最大和最小和(预先处理出在给定的数据中K个数能达到的最大和与最小和),这样是一个很大的剪枝
if (nowsum + min[left] > sum/2) return false; if (nowsum + max[left] < sum/2) return false;
2.枚举的时候从9,8----0的顺序枚举
效率分析:
虽然理论上这个枚举的复杂度是很高的,但由于解的分布是很多的,所以有很大概率短时间内找到解;
另一种搜索:
由于只有0-9这10个数字,所以可以预先记录出这几个数的个数,然后去枚举有多少个,这样的话DFS的深度就比较小;
#include <stdio.h> #include <string.h> int dig[10], sum, min[101], max[101]; bool ok; int getmin(int a, int b) { if (a < b) return a; else return b; } bool dfs(int num, int nowsum, int left) { if (nowsum + min[left] > sum/2) return false; if (nowsum + max[left] < sum/2) return false; if (left == 0) { if (nowsum == sum/2) return true; return false; } if (num < 0) return false;//of course but necessary for (int i = getmin(dig[num], left); i >= 0; i--) if (dfs(num-1, nowsum+i*num, left-i)) return true; return false; } int main() { int cases, n, i, j, m,; scanf("%d", &cases); while (cases--) { scanf("%d", &n); memset(dig,0,sizeof(dig));//record the number of the digit sum = 0; for (i = 0; i < n; i++) { scanf("%d", &m); sum +=m; dig[m]++; } if (sum % 9 != 0 || sum % 2 != 0) { printf("no/n"); continue; } else { int pos = 0; min[0] = 0; for (i = 0; i <= 9 && pos <= sum/2; i++) for (j = 1; j <= dig[i] && pos <= sum/2; j++) { pos++; min[pos] = min[pos-1]+i; } pos = 0; max[0] = 0; for (i = 9; i >=0 && pos <= sum/2; i--) for (j = 1; j <= dig[i] && pos <= sum/2; j++) { pos++; max[pos] = max[pos-1]+i; } if (dfs(9,0,n/2)) printf("yes/n"); else printf("no/n"); } } }
算法2:DP
用f[k][s]表示用k个数字能否组成和为s的情况,则 f[k][s] = f[k][s] || f[k-1][s-a[i]],1 <= i <= n;
具体在枚举的时候先枚举哪一个物品,再倒序枚举个数(正序枚举则该物品能用无限次),最后倒序枚举和
#include <stdio.h> #include <string.h> bool f[101][1000]; int a[201]; int sum, n, i, j, k; int main() { int cases; scanf("%d", &cases); while (cases--) { scanf("%d", &n); sum = 0; for (i = 1; i <= n; i++) { scanf("%d", &a[i]); sum += a[i]; } if (sum % 9 != 0 || sum % 2 != 0) { printf("no/n"); continue; } memset(f,0,sizeof(f)); f[0][0] = 1; for (i = 1; i <= n; i++) for (j = n/2; j >= 1; j--) for (k = sum/2; k >= a[i]; k--) f[j][k] = f[j][k] || f[j-1][k-a[i]]; if (f[n/2][sum/2]) printf("yes/n"); else printf("no/n"); } return 0; }
C题
题目大意:在一个N*N(N<=500),有传送门可以传送到任意一个门(<=1000),需要一定的时间,求在给定图中的起点和终点的最短路?
算法:BFS
1.容易知道,传送门不会使用多于2次;
算法1:从起始点和终点分别不考虑做BFS做出最短路,然后枚举哪俩个门之间传送;(STANDARD)
算法2:一开始想到SPFA,但如果纯粹用SPFA,则由于传送门的存在,可能一个点会进入队列多次,效率比较低;于是想避免这个时间差,就得从等待时间开刀,而用BFS。
如果拓展到传送门,可以知道它到达其它传送门的时间;可以不马上进行拓展,而是等待时机成熟。
那这个时机是什么呢?
以时间顺序枚举,必要的时候才用传送门!
当遇到传送门时,记录下当前可以用的传送门可以达到的最少时间T;如果当前时间是t=T-1,则经由时间t的点集这些点到达其它点的最优解也就是T,这是时机成熟了。可以使用了!去更新以前还没到达的传送门,必定是达到那些传送门的最优解。
(还有一点要注意,在必须经过传送门的情况下的处理)
这样所有的传送门的最优解都已经出来了!以后不可能再用传送门了,已没有意义,不可能得到比现在更优的解!
这样再继续BFS,就可以得到最优解了!
D题
算法:二分
第一次二分在车上的最短时间,对于判断则不需要用二分了(效率反而低)!因为是顺序执行的,这样for一遍就可以解决了的!
E题
算法:后缀表达式
计算一个0-9,+,-,*的表达式的值,需要特别注意特殊的数据!
#include <iostream> #include <string> using namespace std; string s; long long calc(int l, int r) {//notice that the answer may be bigger than long int int p = 0, brakets = 0, totbrakets = 0, plus = 0;// for (int i = l ; i < r; i++) switch (s[i]) { case'(':brakets++;totbrakets++;break; case')':brakets--;break; case'*':if (!plus && !brakets) p = i;break; //case'/':if (!plus && !brakets) p = i;break; //the problem don't need case'+':if (!brakets) { p = i;plus = 1;}break;//we should get the last +/- operation case'-':if (!brakets) { p = i;plus = 1;}break; } if (tkuohao == 1 && s[l] == '(' && s[r-1] == ')') return calc(l+1, r-1);//removed the brackets if (p == 0) {//can't find the operation,as it's a number int num = 0; for (int i = l; i < r; i++) num = num*10+s[i]-'0'; return num; } else { long long n1 = calc(l, p), n2 = calc(p+1, r); if (s[p] == '+') return n1+n2; else if (s[p] == '-') return n1-n2; else if (s[p] == '*') return n1*n2; else return n1/n2;//the problem don't need } } int main() { int cases; scanf("%d/n", &cases); while (cases-- > 0) { cin >> s; if (s[0] == '-') s = '0'+s; cout << calc(0,s.size()) << endl; } return 0; } /* sometimes we should pay attention to the space! special test cases for testing programs (1+1)*3//notice that the brackets should be removed when dealing with (1+1) 1-2-3//notice the we should get the last character as the opration,as it doesn't obey the associative law -1-2//notice the first number may be a negative number,we can insert 0 before it */
F题
题目大意:给定一个4X4的MATRIX,有黑白两种状态,变换一次可以使得5个点反色(上下左右中),求最少需要几次操作可以使得全部同色?
算法:BFS或枚举
1.对于BFS,很容易想,但状态难表示,可以用状态压缩的方法去做,但时间效率比较低;
2.这道题有很强的规律性!
每一格只能被有限个元素改变状态,所以可以枚举最终的颜色,枚举开始4个是否变色,然后从第二行开始判断上面的这个是否需要变色,如果需要则它必须变色!这样如果最后一行也满足条件,则可以去更新是有次数;
效率的差距是750MS vs 0MS
#include <stdio.h> #include <string.h> const int dx[5] = {1,0,-1,0,0}, dy[5] = {0,1,0,-1,0}; int map[4][4], orimap[4][4]; char ch[10]; int i, j; void turn(int x, int y) { for (int i = 0; i < 5; i++) { int nx = x + dx[i], ny = y + dy[i]; if (nx < 0 || ny < 0 || nx >= 4 || ny >= 4) continue; map[nx][ny] ^= 1; } } int main() { freopen("f.in", "r", stdin); int cases; scanf("%d/n", &cases); bool first = true; while (cases--) { if (!first) scanf("/n"); else first = false; for (i = 0; i < 4; i++) { scanf("%s", ch); for (j = 0; j < 4; j++) if (ch[j] == 'w') orimap[i][j] = 0; else orimap[i][j] = 1; } int best = 1 << 30; for (int color = 0; color <= 1; color++) for (int type = 0; type < 16; type++) { int now = 0; memcpy(map,orimap,sizeof(orimap)); for (i = 3; i >= 0; i--) if ((1 << i) & type) { int pos = 3-i; turn(0,pos); now++; } for (i = 1; i < 4; i++) for (j = 0; j < 4; j++) if (map[i-1][j] != color) { turn(i, j); now++; } bool flag = true; for (j = 0; j < 4; j++) if (map[3][j] != color) { flag = false; break; } if (flag && now < best) best = now; } if (best == 1 << 30) printf("Impossible/n"); else printf("%d/n", best); } return 0; }
G题:
题目大意:矩形覆盖的一维版本,但有10000个,且多组数据,所以N^2的算法不可以!要用NlogN的算法了!
算法是线段树,系统学习线段树后可以做此题!
下午写了二叉树版本的,对于随机数据效果较好,但是有几个点没有过。
H题 PRO
算法:最大堆、最小堆
算法意图很明显,就是考这个算法,但是也有点TRICK在里面。
在删除数据的时候,不是只要删ROOT就行了,一个堆中删除的在另外一个堆中也需要删除!所以要用4个数组分别记录下读入的第I个数据在堆中的位置和堆中的元素是原来的位置!复杂度提高了!
反例:
11 2
13 12
正确的应为7,可是不删除数据则是8
花了一下午的时间终于把这道题目解决了,主要是一个调试的小错误。
关于这道题目专门放在一篇文章中详细讲解!
I题
算法:线段树或RMQ
题目大意:求出数据中每K个元素的最大值
好久没写了,尝试了一下,框架可以写出来,但具体细节还是会有错误!
这次的比赛的题目很丰富!
USACO上的旅程有点卡了,EULER回路就有点想不通了。争取一下!