2013-5-11 湘潭多省程序设计 赛后总结
A Alice and Bob
解法一, 比赛时用的解法是,因为N<=10000,那么枚举A,B分别拿的时候数量,然后求最小,然后得出A,B拿的最小次数的上下界比较,得出区间的几个关系,因为k1>=k2,判定有点复杂。特殊情况比较多。
解法二,是用动态规划,状态方程 dp( i, j ) , 表示剩下 i 块石头,j = 0时,最后一次是A拿最小次数, j = 1,最后一次是B拿最小次数。
转移方程为 dp( i, 0 ) = min{ dp(i-2^k,1) } +1, dp( i, 1 ) = min{ dp(i-3^k, 0) } +1 .
比较下还是解法二比较好,没那么多特殊情况判定,写起来也比较快。
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; const int N = 10010; const int inf = 0x3f3f3f3f; int dp[N][2]; int a[20], b[20]; int main(){ a[0] = b[0] = 1; for(int i = 1; i <= 20; i++) a[i] = a[i-1]*2, b[i] = b[i-1]*3; int T, n; scanf("%d", &T); while( T-- ){ scanf("%d", &n); memset(dp,0x3f,sizeof(dp)); dp[0][0] = dp[0][1] = 0; for(int i = 1; i <= n; i++){ int t = inf; for(int k = 0; a[k] <= i; k++) t = min( t, dp[ i-a[k] ][1] ); dp[i][0] = t+1; t = inf; for(int k = 0; b[k] <= i; k++) t = min( t, dp[ i-b[k] ][0] ); dp[i][1] = t+1; } printf("%d\n", dp[n][0] ); } return 0; }
B Binary Search Tree
因为给定的节点权值都为整数,可以调整权值为浮点数.意味着两个整数间存在任意多个数. 通过中序遍历树后, 求出 LIS, 则 N-LIS是最少需要改变的.
这里因为 N = 10^5, 需要使用 O(NlogN)的求法. 说白了就是用了个 二分查找.
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int N = 5010; const int inf = 0x3f3f3f3f; struct node{ int key, lch, rch; }Q[N]; int a[N], pre[N], n, top; int idx[N], m; void gao(int x){ if( Q[x].lch != 0 ) gao( Q[x].lch ); a[ ++top ] = Q[x].key; if( Q[x].rch != 0 ) gao( Q[x].rch ); } int find(int x){ int l = 0, r = m, res = 0; while( l <= r ){ int mm = (l+r)>>1; if( x > idx[mm] ) res=mm,l = mm+1; else r = mm-1; } //printf("res = %d\n", res ); return res; } int LIS(){ m = 0; for(int i = 0; i <= n; i++) idx[i] = -inf; for(int i = 1; i <= n; i++){ int t = find( a[i] )+1; idx[t] = a[i]; if( t > m ) m = t; } return m; } int main(){ int T; scanf("%d", &T); while( T-- ){ scanf("%d", &n); memset(pre,0,sizeof(pre)); for(int i = 1; i <= n; i++){ scanf("%d%d%d", &Q[i].key,&Q[i].lch,&Q[i].rch); pre[ Q[i].lch ] = i; pre[ Q[i].rch ] = i; } int rt; for(int i = 1; i <= n; i++) if( pre[i] == 0 ){ rt = i; break; } top = 0; gao(rt); //for(int i = 1; i <= n; i++) // printf("%d ", a[i] ); puts(""); printf("%d\n", n-LIS() ); } return 0; }
C Coins
题目大意是给N个区间形如[l,r] 其有个最小值,然后求最小和.
解法: 离散化后转换成区间覆盖问题.然后统计.
因为离散化的缘故,涉及端点问题, 解决此题最核心的地方在于,将 [ l, r ] 闭区间转换成 [ l, r+1 )的左闭右开 的线段. 然后线段书或者用 并查集 随便搞搞就出来了. 解题代码 与 更详细的请跳转: http://www.cnblogs.com/yefeng1627/archive/2013/05/15/3079973.html
D DNA
虽然知道大致 思路是, dp( i )表示 前i个字符,最大价值. 然后转移 dp( i ) = max( dp( i-j ) + cost( key(x) ) } , 算当前字符能得到哪些单词.要用AC自动机.不太熟..这两天去弄明白了再回头补上...
E Edges of Triangle
解题报告的解法是, 处理两个端点使其为整点, 那么就可以用 gcd( (x2-x1), (y2-y1) ) 来解. 其求解原理如下:
假定 两端点为整点, (x1,y1), (x2,y2) , 则直线线形式如下: x/a + y/b = 1 .
将方程转换下, 得到 b*x + a*y = a*b , 然后根据 扩展欧几里德定理得到解的形式为 : x = x0 + t* (a/gcd(a,b)). t为任意整数
我们知道改直线 经过点 (0,b), 则 x0 = 0, 是直线的一个解, 因为 满足要求的 x 取值范围为 [ 0, a ] ,则 t的取值范围即为 0, 1, ...,gcd(a,b) 所以 直线 b*x+a*y = a*b在区间[0,a]上的整点数量为 gcd( a,b )+1.
通过以上分析. 我们知道 为什么能够用 gcd( (x1-x2), (y1-y2) ) 来计算 直线上的点数量.
但是在这题,这样做却不可行. 因为 两个端点 (x1,y1),(x2,y2)并非整点. 所以就不能得到上面的 直线方程.就不满足上面计算公式. 这也是为什么要详细指出上面的计算原理.
若要使其 能够用以上公式计算. 则必须要 找到 两端的整点 .A( x1`, y1` ), B( x2`, y2` ) , 才可以带入计算.
而 两端的整点 , 若我们平行看支线四个点的关系. 有 (x1,y1) <= A(x1`,y1`) <= B(x2`,y2`) <= (x2,y2) , 取等号是因为 (x1,y1),(x2,y2)可能为整点.
求法, 是用 EXgcd求出 满足要求的 x0, 然后 其通解形式如 x0 + t*b` , 然后找到第一个大与 (x1,y1)的整点, 找到最后一个小于 (x2,y2)的整点. 然后带入计算即可.
长篇大论了一通. 主要是涉及理论. 建立在 扩展欧几里德定理之上.
其实话说回来, 既然都已经用 exgcd 求出了 x0, 也可以直接算 出当期支线 A*x+B*y = C 在区间 [x1,x2]上整点数量即可. 不要要分情况讨论, x0 与 区间[x1,x2]的三种情况.
另外, 三条支线间点的计算要注意重复问题. 简单点的可以用 map< pair<LL,LL>, int > 来标记.
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<map> #include<algorithm> using namespace std; typedef long long LL; map< pair<LL,LL>,int > mp; const double esp = 1e-8; int sign(double x){ return x<-esp?-1:(x>esp); } LL gcd(LL a, LL b){ return b == 0 ? a : gcd(b,a%b); } LL ExGcd(LL a, LL b, LL &x, LL &y){ if( b == 0 ){ x = 1, y = 0; return a; } int r = ExGcd( b, a%b, x, y ); LL t = x; x = y; y = t - a/b*y; return r; } int A1, B1, C1; int A2, B2, C2; int A3, B3, C3; bool find(LL x,LL y){ if( mp.count(make_pair(x,y) ) == 0 ){ mp[make_pair(x,y)] = 1; return false; } return true; } bool equal( double x, double y ){ if( sign(x-y) == 0 ) return true; return false; } LL fun(double x1,double y1, double x2, double y2,int A, int B,int C){ //printf("A = %d, B = %d, C = %d\n", A,B,C); LL n = 0; if( B == 0 ){ // x = C/A if( C%A != 0 ) return 0; if( sign(y1-y2) > 0 ) swap(y1,y2); n = (LL)( floor(y2)-floor(y1)+1 ); // printf("B = 0, n = %lld\n", n ); if( sign( y1-floor(y1) ) == 0 ){ // if y1 is endpoint if( find(x1,(LL)floor(y1) ) ) n--; } if( sign( y2-floor(y2) ) == 0 ){ // if y2 if endpoint if( find(x1,(LL)floor(y2) ) ) n--; } } else if( A == 0 ){ // y = C/B if( C%B != 0 ) return 0; if( sign(x1-x2) > 0 ) swap(x1,x2); n = (LL)(floor(x2)-floor(x1)+1); //printf("A = 0, n = %lld\n", n ); if( sign( x1-floor(x1) ) == 0 ){ if( find((LL)floor(x1),y1 ) ) n--;; } if( sign( x2-floor(x2) ) == 0 ){ if( find( (LL)floor(x2),y1 ) ) n--; } } else{ if( sign(x1-x2) >= 0 ) swap(x1,x2),swap(y1,y2); //x1<x2 LL d = gcd( A, B ); if( C%d != 0 ) n = 0; else{ LL x0, y0, AA = A/d, BB = B/d; ExGcd( A/d, B/d, x0, y0 ); x0 *= C/d, y0 *= C/d; //printf("x0 = %lld, y0 = %lld\n", x0, y0 ); if( (sign(x0-x1)>=0) && (sign(x2-x0)>=0) ){ // x1, x0, x2 n = abs((LL)floor( (x0-x1)/BB )) + abs((LL)floor( (x2-x0)/BB )) + 1; // printf("case 1: n = %lld\n", n ); LL k = (LL)(floor((x0-x1)/BB)); if( equal(y1,floor(y1)) && equal(x1,x0-BB*k) && equal(y1,y0+AA*k) ){ if( find( x0-BB*k, y0+AA*k ) ) n--; } k = (LL)(floor( (x2-x0)/BB )); if( equal(y2,floor(y2)) && equal(x2,x0+BB*k) && equal(y2,y0-AA*k) ){ if( find( x0+BB*k, y0-AA*k ) ) n--; } } else if( sign(x1-x0) >= 0 ){ // x0, x1, x2 n = abs( (LL)floor((x2-x0)/BB) ) - abs( (LL)floor((x1-x0)/BB)); // printf("Case 2: n = %lld\n", n ); LL k = (LL)(floor((x1-x0)/BB)); if( equal(y1,floor(y1)) && equal(x1,x0+BB*k) && equal(y1,y0-AA*k) ){ if( !find( x0+BB*k, y0-AA*k ) ) n++; } k = (LL)( floor(x2-x0)/BB ); if( equal(y2,floor(y2) ) && equal(x2,x0+BB*k) && equal(y2,y0-AA*k) ){ if( find( x0+BB*k, y0-AA*k ) ) n--; } } else { // x1, x2, x0 n = abs( (LL)floor((x0-x1)/BB) ) - abs( (LL)floor((x0-x2)/BB)); //printf("Case 3: n = %lld\n", n ); LL k = (LL)(floor((x0-x2)/BB)); if( equal(y2,floor(y2)) && equal(x2,x0-BB*k) && equal(y2,y0+AA*k) ){ if( !find(x0-BB*k,y0+AA*k) ) n++; } k = (LL)( floor(x0-x1)/BB ); if( equal(y1,floor(y1)) && equal(x1,x0-BB*k) && equal(y1,y0+AA*k) ){ if( find(x0-BB*k,y0+AA*k) ) n--; } } } } return n; } void Gao(){ LL n = 0; double x13 = (1.0*C1*B3-1.0*C3*B1)/(1.0*A1*B3-1.0*A3*B1); double x12 = (1.0*C1*B2-1.0*C2*B1)/(1.0*A1*B2-1.0*A2*B1); double x23 = (1.0*C2*B3-1.0*C3*B2)/(1.0*A2*B3-1.0*A3*B2); double y13 = (B1==0)?1.0*(C3-A3*x13)/B3:1.0*(C1-A1*x13)/B1; double y12 = (B1==0)?1.0*(C2-A2*x12)/B2:1.0*(C1-A1*x12)/B1; double y23 = (B2==0)?1.0*(C3-A3*x23)/B3:1.0*(C2-A2*x23)/B2; //printf("x13=%lf,y13=%lf, x12=%lf,y12=%lf,x23=%lf,y23=%lf\n",x13,y13,x12,y12,x23,y23); mp.clear(); LL n1 = fun( x12,y12,x13,y13,A1,B1,C1 ); LL n2 = fun( x23,y23,x12,y12,A2,B2,C2 ); LL n3 = fun( x13,y13,x23,y23,A3,B3,C3 ); printf("%lld\n", n1+n2+n3 ); //printf("n1=%lld,n2=%lld,n3=%lld\n", n1,n2,n3 ); } int main(){ int T; scanf("%d",&T); while( T-- ){ scanf("%d%d%d",&A1,&B1,&C1); scanf("%d%d%d",&A2,&B2,&C2); scanf("%d%d%d",&A3,&B3,&C3); Gao(); } return 0; }
F Five Tiger
纯模拟题, 没必要说太多. 不过要注意的是, 有点坑人的地方是, 四斜, 三斜只有给的那几种情况. 不要像我一样看复杂了~~~
G Goddess
神概率题, 不会.
H Hurry Up
初步分析下, 可以知道, 起点走到公路,再坐车到终点. 满足二元函数, 其中有个最小值. 那么直接三分求x点即可. 然后得到最优值与步行花费取个最小即可.
I I Love Military Chess
签到题, 就不说了. 我的写法是 分成 {数字与数字},{数字与字母},{字母与数字},{字母与字母} 来讨论, 这样就不会漏掉了. 不过要注意特殊情况.判定.
K Jack’s sequence
其实分析下,就可以知道, 因为给定的 括号匹配串是合法的, 因为 左括号 小于 右括号. 要得到下一个,则必定是找个右括号与左括号交换.
交换的前提还要保证, 整个括号串合法. 仔细推一下可以发现, 仅 ' (#()) ' 这种串才满足 左右括号交换后, 得到的串还满足要求. 其中'#' 表示一个合法串.
那么要保证下一个. 而非多个. 则对于串 "(())()" 而言, 只有第4个位置的右括号能够与第二位的左括号交换, 然后得到合法串, 此时串形式为 " ()()() ",
但这个并不是 除原先的最小. 因为还有这种情况: " ()(()) " , 大概整理下.解法如下:
找到最后一个形如 " ())" 则必定是交换 第一个 (, 与最后一个 ), 然后讨论后面的 串. 假定串为 ####())####, 则此时需要要考虑交换 (,)后, 后面的字符
该如何排列以保证整个串最小. 直观的情况是 ()()() => ((())). 不难想到, 因为 左括号要比右括号小..
还有个特别地方是, " (((#()))() " => ((()((()))
#include<cstdio> #include<cstring> #include<cstdlib> using namespace std; const int N = 10010; char s[N]; int main(){ int T; scanf("%d", &T); while( T-- ){ scanf("%s", s+1); bool flag = false; int len = strlen(s+1); for(int i = len; i >= 3; i-- ){ if( s[i] == ')' ){ if( s[i-1] == ')' && s[i-2] == '(' ){ //printf("i = %d\n", i ); //getchar(); getchar(); getchar(); flag = true; for(int j = 1; j < i-2; j++) putchar(s[j]); putchar(')'); if( (i+1<=len) && (s[i+1]==')') ){ // putchar('('),putchar(')'); int k = i+1, L = 1; while( (k<=len) && (s[k]==')') ) k++;// putchar(s[k++]); if( k <= len ) L += (len-k+1)/2; for(int j = 1; j <= L; j++) putchar('('); for(int j = 1; j <= L; j++) putchar(')'); k = i+1; while( (k<=len) && (s[k]==')') ) putchar(s[k++]); } else{ int L = (len-(i-1)+1)/2; for(int j = 1; j <= L; j++) putchar('('); for(int j = 1; j <= L; j++) putchar(')'); } puts(""); break; } } } if( !flag ) puts("No solution"); } return 0; }