还是很弱啊,发现确实要多做CF, 不单单训练思维,而且还有代码。还能多参考大神的思路与代码。
Div 2.
A题, 给定 n <= 5000, 求满足 1<=a<=b<=c<=n, 的直角三角形数量.
解法, 数据量不大,可以暴力枚举 a, b, 然后求满足的 c
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> typedef long long LL; const int N = 10000; int main(){ // memset(ans, 0 , sizeof(ans)); int n; long long ans = 0; scanf("%d", &n); for(int a = 1; a <= n; a++) for(int b = a; b <= n; b++ ){ int cc = a*a+b*b, c = (int)sqrt(cc); if( c*c == cc ){ if( (c<=n) && (c<a+b) ){ ans++; } } } printf("%lld\n", ans ); return 0; }
B题, 给两个日期形式如 yyyy:mm:dd, 求之间的总天数,包含开始,不包含结束那天。
解法,模拟,看到 Python or Ruby 4,5行的代码,真心跪了。
我的写法是, 写一个函数 g(), 用来计算同一年下,从日期A到日期B所需天数. 那么对于给定日期A到日期B,首先从A算到下
一年起始,调用g()即可,然后再整年整年算到B年去, 这时候再调用下 g(). 注意 日期A,B大小没说明.
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; int a[2][13] = { {0,31,28,31,30,31, 30,31,31,30,31, 30,31}, {0,31,29,31,30,31, 30,31,31,30,31, 30,31} }; int b[2] = {365,366}; bool legal(int y){ if( (y%400==0) || ((y%100!=0)&&(y%4==0)) ) return true; return false; } int mon( int y1,int y2, int m1,int m2,int d1,int d2){ int cnt = 0; if( m1 == m2 ){ return d2 - d1; } else{ int x = legal(y1); cnt = a[x][m1] - d1 +1; int m = m1+1; while( m < m2 ) cnt += a[x][m++]; cnt += d2-1; return cnt; } } int solve(int y1,int m1,int d1, int y2,int m2,int d2){ if( (y1>y2)||( (y1==y2)&&(m1>m2) )||( (y1==y2)&&(m1>m2)&&(d1>d2) ) ){ swap(y1,y2); swap(m1,m2); swap(d1,d2); } int ans = 0; if( y1 == y2 ){ return mon( y1,y2,m1,m2,d1,d2); } else{ int x = legal(y1); ans = mon( y1, y1, m1, 12, d1, 31 )+1; // y1+1, 1, 1 int y = y1+1; while( y < y2 ){ ans += b[ legal(y) ]; y++; } int t = mon( y2,y2,1,m2,1,d2 ); ans += t; return ans; } } int main(){ int y1,m1,d1; int y2,m2,d2; char s[100]; scanf("%s", s); sscanf( s, "%d:%d:%d", &y1,&m1,&d1); scanf("%s", s); sscanf( s, "%d:%d:%d", &y2,&m2,&d2); printf("%d\n", solve( y1,m1,d1, y2,m2,d2) ); return 0; }
C题, 给一个n, 其全排列( 0,1,..,n-1 ), 求其中三个排列 a, b, c, 满足 ai+bi = ci ( mod n ), 若不存在则输出 -1.
解法: 构造, 抽象成 n边形,顶点序列为 c1,c2,c3,...,cn . 当n为奇数时,则ai顶点出发,与 a(i+2) 连边,然后 a(i+2)与a(i+4)连边,如此反复,
即可不重复的将所有顶点连成一条线上. 差值为2,意味这我们每次增长2, 那么就有 ci = (i+i)%n.
对于 n为偶数的情况下, 则不存在. 因为若要将所有顶点通过一条直线连接, 则每次顶点编号+1, 那么
若 a = { 0,1 ,2, ..., n-1 }, 因为每次 ai 编号+1了, 则要保持 ci连成直线 ,则意味着 bi需要保持不变. 因为 bi = {0,1,..,n-1},不存在这样的b.
所以不合法.
#include<cstdio> int main(){ int n; scanf("%d", &n); if( n&1 ){ for(int i = 0; i < n; i++) printf(i==0?"%d":" %d", i ); puts(""); for(int i = 0; i < n; i++) printf(i==0?"%d":" %d", i ); puts(""); for(int i = 0; i < n; i++) printf(i==0?"%d":" %d",(i+i)%n); puts(""); } else printf("-1\n"); return 0; }
D题, 二维坐标系第一象限中,给定一个 (0,0)与(n,m)构成的矩形, 和一个 (x,y), 与一个(a,b). 求一个最大的子矩形,包含(x,y),并且,
左下点(x1,y1),与右上角(x2,y2),满足 (x2-x1)/(y2-y1) = a/b , 所有数都要为整数. 若子矩阵存在多个,输出矩阵中心点 与 (x,y)曼哈顿距离最短的.
若还是存在多个,则输出 (x1,y1,x2,y2) 字典序小的,(其实就是偏左边的).
解法: 数学构造.
首先要满足子矩阵最大, 并且 (x2-x1)/(y2-1) = a/b , 令 d = gcd( a, b ), a1 = a/d, b1 = b/d. 那么 (x2-x1)/(y2-y1) = a1/b1, 等价于
k*a1 / k*b1 = a1 / b1, 换句话说,就是 x轴有 k段a1, y轴有 k段b1, 找到一个最大的整数 k, 即是满足要求的最大子矩阵大小. 很容易得到 k = min( n/a1, m/b1 )
由上面分析,我们知道矩阵的长宽分别为 ( k*a1, k*b1 ). 最极端情况是右上角顶点是(n,m), 则左下角顶点是( n-k*a1, m-k*b1 ). 这是最靠右的方案.
现在还要满足后续要求,
第一, 中心点距离(x,y),要尽可能小, 那么 矩阵的中心点为(x,y)时,必然最小, 则左下角顶点应该为 { x- (k*a1+1)/2, y - (k*b1+1)/2 }, 这里 (k*a1+1)/2的
原因是, 若 k*a1 是偶数,肯定是取最靠左边那个, 若k*a1是奇数,则当前这一段的中点是 (k*a1+1)/2, 两种情况合并到一起就是 (k*a1+1)/2.
第二, 若选 (x,y)作为子矩形中点, 其左下角可能会超过 x轴,y轴,而为负数,这不合法.所以最小只能是 x1 = 0, y = 0.
所以有 x1 = min( n-k*a1, max( 0,x-(k*a1+1)/2) ) , y1类似, 而 x2 = x1+k*a1, y = y1+k*b1. 即是最终结果.
#include<cstdio> #include<algorithm> using namespace std; int n, m, x, y, a, b; int gcd(int a,int b){ return b==0?a:gcd(b,a%b); } int main(){ scanf("%d%d%d%d%d%d",&n,&m,&x,&y,&a,&b); int d = gcd(a,b); int a1 = a/d, b1 = b/d; int k = min( n/a1, m/b1 ); int x1 = min( n-k*a1, max( 0, x - (k*a1+1)/2 ) ); int y1 = min( m-k*b1, max( 0, y - (k*b1+1)/2 ) ); printf("%d %d %d %d\n", x1,y1,x1+k*a1,y1+k*b1 ); return 0; }
E题, 给 n(n<=5000)个数, a1,a2,...,an, (ai <= 10^6 ), 问最多去掉 k (k<=4)个数的情况下,使得序列中不再存在 ai = aj (mod M),求最小的M.
解法: 看了某大神的代码,学会的思路. 不过还是对时间复杂度不太自信~~~
大致思路是这样的: 枚举m, (感觉有点凶残,虽说极端情况 m = 10^6 + 1 ), 统计 ai%m余数出现重复的次数,若小于等于k则输出M,.
有个地方可以优化. 令 gap[x] 表示 ai - aj = x 出现的次数, 那么我们就能统计出 ai = aj+t*m 的形式的总数量. 设其为sum, 因为最多删除
k个数, 则极端情况下影响 k+1个数,的数量为 k*(k+1)/2, 所以若 sum > k*(k+1)/2 直接可以判定此时不可能,跳过即可.
对于 sum > k*(k+1)/2 这里确实要分析下, 因为我们最多只能删除掉K个数, 假定我们有 k+1个数相互间影响(满足ai = aj+t*m ), 分别为 a_1, a_2, ..., a_k, a_k+1,
则 产生的影响个数最多为 : ( 假定 a_i, a_i+1, 序列非递减 )
a_1 能 影响 a_2 , a_3 ,.. , a_k+1 , 共 k 个
a_2 能 影响 a_3, a_4 ,....., a_k+1, 共 k-1 个
....
a_k 能影响 a_k+1, 共 1个.
则 总数量为 k+(k-1) + ... + 1 = k*(k+1)/2 ,
所以有 当 sum > k*(k+1)/2 时, 直接判定当前的m不合法.
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int N = 1000010; int a[5010]; bool b[N]; int gap[N]; int main(){ int n, K, i, j, m; scanf("%d%d", &n, &K); for(int i = 0; i < n; i++) scanf("%d", &a[i]); sort( a, a+n ); memset(b,0,sizeof(b)); memset(gap,0,sizeof(gap)); for(int i = 0; i < n; i++) for(int j = 0; j < i; j++) gap[ a[i]-a[j] ]++; for(m = 1;;m++){ bool flag = true; int cnt = 0, sum = 0; for(i = m; i < N; i += m ) sum += gap[i]; if( sum > (K*(K+1)/2) ) continue; for(i = 0;i < n; i++){ int x = a[i]%m; if( !b[x] ) b[x] = true; else{ if(++cnt > K){flag = false; break;} } } for(j = 0; j < i; j++) b[ a[j]%m ] = false; if( flag ){ printf("%d\n",m); return 0; } } return 0; }