Tutorials for The 10th SWJTU Collegiate Programming Contest - Online Round

【前言】由于并不是所有的同学都能进决赛,没有进决赛 (等待通知名单) 的同学也不要气馁,后面还会有很多机会。只要你坚持、谦虚、低调、勤奋,相信你一定会成为一名出色的编码选手。


【A题】略。


【B题】

基本思想:模拟每一步的处理,对于每一步重在实现上、下、左、右四种情况下的处理。

#include<stdio.h>
#include<string.h>

const int maxN = 5;

int maze[maxN][maxN];
char cmd[maxN * 100];
// L, R, U, D
int dir[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};

void go(int d)
{
    // L
    if( d == 0 ) {
        for(int i = 0; i < 4; i ++) {
            for(int j = 0; j + 1 < 4; ) {
                if( maze[i][j] == 0 ) { j ++; continue; }
                int k = j + 1;
                for(; k < 4; k ++)
                    if( maze[i][k] )
                        break;
                if( k < 4 && maze[i][j] == maze[i][k] ) { // 处理相同
                    maze[i][j] <<= 1;
                    maze[i][k]   = 0;
                    j = k + 1;
                } else
                    j ++;
            }
            
            int k = 0;
            for(int j = 0; j < 4; j ++) // 向左靠齐
                if( maze[i][j] )
                    maze[i][k ++] = maze[i][j];
            while( k < 4 ) maze[i][k ++] = 0;
        }
    }
    
    // R
    if( d == 1 ) {
        for(int i = 0; i < 4; i ++) {
            for(int j = 3; j - 1 >= 0; ) {
                if( maze[i][j] == 0 ) { j --; continue; }
                int k = j - 1;
                for(; k >= 0; k --)
                    if( maze[i][k] )
                        break;
                if( k >= 0 && maze[i][j] == maze[i][k] ) {
                    maze[i][j] <<= 1;
                    maze[i][k]   = 0;
                    j = k - 1;
                } else
                    j --;
            }
            
            int k = 3;
            for(int j = 3; j >= 0; j --)
                if( maze[i][j] )
                    maze[i][k --] = maze[i][j];
            while( k >= 0 ) maze[i][k --] = 0;
        }
    }
    
    // U
    if( d == 2 ) {
        for(int j = 0; j < 4; j ++) {
            for(int i = 0; i + 1 < 4; ) {
                if( maze[i][j] == 0 ) { i ++; continue; }
                int k = i + 1;
                for(; k < 4; k ++)
                    if( maze[k][j] )
                        break;
                if( k < 4 && maze[i][j] == maze[k][j] ) {
                    maze[i][j] <<= 1;
                    maze[k][j]   = 0;
                    i = k + 1;
                } else 
                    i ++;
            }
            
            int k = 0;
            for(int i = 0; i < 4; i ++)
                if( maze[i][j] )
                    maze[k ++][j] = maze[i][j];
            while( k < 4 ) maze[k ++][j] = 0;
        }
    }
    
    // D
    if( d == 3 ) {
        for(int j = 0; j < 4; j ++) {
            for(int i = 3; i - 1 >= 0; ) {
                if( maze[i][j] == 0 ) { i --; continue; }
                int k = i - 1;
                for(; k >= 0; k --)
                    if( maze[k][j] )
                        break;
                if( k >= 0 && maze[i][j] == maze[k][j] ) {
                    maze[i][j] <<= 1;
                    maze[k][j]   = 0;
                    i = k - 1;
                } else
                    i --;
            }
            
            int k = 3;
            for(int i = 3; i >= 0; i --)
                if( maze[i][j] )
                    maze[k --][j] = maze[i][j];
            while( k >= 0 ) maze[k --][j] = 0;
        }
    }
}

int main()
{
    int t = 0;
    while( scanf("%d", &maze[0][0]) == 1 ) {
        if( t ) puts(""); else t ++;
        for(int j = 1; j < 4; j ++)
            scanf("%d", &maze[0][j]);
        for(int i = 1; i < 4; i ++)
            for(int j = 0; j < 4; j ++)
                scanf("%d", &maze[i][j]);
        scanf("%s", cmd);
        for(int i = 0; cmd[i]; i ++) {
            if( cmd[i] == 'L' ) go(0);
            if( cmd[i] == 'R' ) go(1);
            if( cmd[i] == 'U' ) go(2);
            if( cmd[i] == 'D' ) go(3);
        }
        for(int i = 0; i < 4; i ++) {
            for(int j = 0; j < 4; j ++)
                printf("%5d", maze[i][j]);
            printf("\n");
        }
    }
    return 0;
}

【C题】原型某年省赛题。

while( scanf("%s", str) == 1 ) {
        memset(times, 0, sizeof(times));
        for(int i = 0; str[i]; i ++)
            times[ str[i] ] ++;
        int res = times['g'] - 1; // 可共用
        res = min(res, times['u']);
        res = min(res, times['a']);
        res = min(res, times['n'] >> 1);
        res = min(res, times['j']);
        res = min(res, times['i']);
        printf("%d\n", max(0, res));
    }

【D题】枚举第2种情况的所有选择情况,取最小值。

import java.io.*;
import java.math.*;
import java.util.*;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sys = new Scanner(System.in);
		int N, M, K, V, ones, twos, res, a, b, c;
		while( sys.hasNext() ) {
			N = sys.nextInt();
			M = sys.nextInt();
			K = sys.nextInt();
			ones = 0; twos = 0;
			for(int i = 0; i < N; i ++) {
				V = sys.nextInt();
				if( V == 1 )
					ones ++;
				else
					twos ++;
			}
			res = 1000000009;
			for(int i = 0; i <= twos; i ++) { // 拆分第二种选择
				a = ones + i;
				b = twos - i;
				c = 0;
				if( a > M )
					c += a - M;
				if( b > K )
					c += b - K;
				if( c < res )
					res = c;
			}
			if( res < 0 )
				res = 0;
			System.out.println(res);
		}
		System.gc();
	}

}

【E题】

动态规划题,相信你可以推导出如下表达式:


(表达式的含义,不妨留作思考题)

import java.io.*;
import java.math.*;
import java.util.*;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int Mod = 20120354;
		int[][] dp = new int [1502][1502]; 
		for(int j = 1; j <= 1500; j ++)
			dp[1][j] = 1;
		for(int i = 2; i <= 1500; i ++) {
			for(int j = 1; j <= 1500; j ++) {
				dp[i][j] = 0;
				for(int d = 1; d * d <= j; d ++) {
					if( j % d == 0 ) {
						dp[i][j] = (dp[i][j] + dp[i - 1][d]) % Mod;
						if( d * d != j )
							dp[i][j] = (dp[i][j] + dp[i - 1][j / d]) % Mod;
					}
				}
			}
		} // 预处理所有临时结果
		Scanner sys = new Scanner(System.in);
		while( sys.hasNext() ) {
			int N = sys.nextInt();
			int K = sys.nextInt();
			int sum = 0;
			for(int j = 1; j <= K; j ++) // 最后一个数不大于K
				sum = (sum + dp[N][j]) % Mod;
			System.out.println(sum);
		}
		//System.gc();
	}

}

【F题】

不妨假设A的左边是B,右边是C,很容易想到,最优方案中,A要么不参与传递,要么只拿出一部分给B,要么只拿出一部分给C,要么同时分别拿出一部分给B和C。看上去很复杂?假设总数是S,显然如果S不能被人数除尽,则肯定是不能平均分配,这一点显而易见;如果能够除尽,则必然可以平均分配,进而此时考虑的问题就是最优的问题!

假设最优方案中,是A拿出x个给B,拿出y个给C,注意:x和y都可以为0,比如如果x=0,表示A实际上并没有拿给B。可见,用这样的表达方式可以把所有情况都能表示出来!而为了解决这道题,我们需要转变一个认识,那就是你可能会觉得即便x和y都可能等于0,但是也必然是非负的,不是吗?而我想说的是,x和y可以为任意整数,包括负数!考虑一下,假设x=-3,原汁原味的意思就是A拿出-3个给B,而这是不是等价于B拿出3个给A呢?好了,有了这样的认识,解决这道题不再困难!【我的初衷总是让你尽可能明白解题的思路,而不在于代码的实现!】

好了,我们用<P, Q, n>表示P拿出n个给予Q,注意n为任意整数。不失一般性,假设现在由N个人,即P(1), P(2), P(3), ..., P(N),他们手中已有的糖果数分别是:T(1), T(2), ..., T(N)。那么,在最优方案中,首先一点可以确定,他们手中的糖果肯定会优先考虑给自己,然后如果有多余的,才给别人!既然如此,也就是说,只有多余的糖果才会被传来传去,那么每个人手中有多少是多余的呢?假设糖果平均数是V个,显然第k个人有T(k)-V个多余的!注意,这个值也可能为负数,一样的道理,假设它的值为-7,原汁原味的意思就是多出了-7个,实际上是少了7个,这是等价的,我们只从多出的角度思考问题。【很多时候我们要转变思维认识,不要定格在现实的牢套当中!】

我们要推导出一个结论:任意两相邻的人之间至多有一次传输!为什么呢?假设现在有相邻的两个人A和B,某方案中,A拿出x个给B,B拿出y个给A,也即:<A, B, x>和<B, A, y>,而这个显然可以用一个来表示即:<A, B, x - y>。所以可以看到,基于这一点,我们可以得出,最优方案总可以写成下面的形式:

<P(1), P(2), n(1)>, <P(2), P(3), n(2)>, ..., <P(N), P(1), n(N)>

可以看到,这不就说明最优方案总是可以转化为朝着1个方向走的吗?如果朝着1个方向走,从哪个人开始呢?大不了枚举一下?那又怎么计算答案呢?(点到为止,留作思考题)

#include<stdio.h>
#include<string.h>

const int maxN = 1024;

long long A[maxN]; int nA;

int main()
{
    while( scanf("%d", &nA) == 1 ) {
        for(int i = 0; i < nA; i ++)
            scanf("%lld", &A[i]);
        long long sum = 0;
        for(int i = 0; i < nA; i ++)
            sum += A[i];
        if( sum % nA ) {
            puts("oh,no!");
            continue;
        }
        sum /= nA; // 计算平均数
        for(int i = 0; i < nA; i ++)
            A[i] -= sum; // 每一个值减去平均数
        long long res = -1;
        for(int i = 0; i < nA; i ++) { // 正方向枚举起始点
            long long ret = 0, tmp = A[i];
            for(int j = (i + 1) % nA; j != i; j = (j + 1) % nA) {
                ret += tmp > 0 ? tmp : (-tmp);
                tmp = A[j] + tmp;
            } // 留作思考
            if( res == -1 || ret < res )
                res = ret; // 取较小值
        }
        long long t;
        for(int i = 0, j = nA - 1; i < j; i ++, j --)
            t = A[i], A[i] = A[j], A[j] = t; // 逆序
        for(int i = 0; i < nA; i ++) { // 反方向枚举起始点
            long long ret = 0, tmp = A[i];
            for(int j = (i + 1) % nA; j != i; j = (j + 1) % nA) {
                ret += tmp > 0 ? tmp : (-tmp);
                tmp = A[j] + tmp;
            } // 留作思考
            if( res == -1 || ret < res )
                res = ret; // 取较小值
        }
        printf("%lld\n", res);
    }
    return 0;
}



【G题】略。

【H题】略。


#include<stdio.h>
#include<string.h>

const int maxN = 64;

char A[maxN], B[maxN], C[maxN];

double get(char str[])
{
    int dot_pos = -1;
    for(int i = 0; str[i]; i ++)
        if( str[i] == '.' ) {
            dot_pos = i;
            break;
        }
    double res = 0.0;
    if( dot_pos == -1 ) {
        sscanf(str, "%lf", &res);
        strcpy(str, "");
        return res;
    }
    str[dot_pos] = ' ';
    sscanf(str, "%lf %s", &res, C);
    strcpy(str, C); return res;
} // 计算整数部分

long long gcd(long long a, long long b)
{
    if( a == 0LL && b == 0LL )
        return 1LL;
    if( b == 0 )
        return a;
    return gcd(b, a % b);
} // 计算最大公约数

void doit(long long &X, long long &Y, char str[])
{
    int len = strlen(str);
    X = 0LL; Y = 1LL;
    if( len == 0 )
        return ;
    int bra_pos = len;
    for(int i = 0; str[i]; i ++)
        if( str[i] == '(' ) {
            bra_pos = i; 
            break;
        }
    if( bra_pos == 0 )
        return ;
    for(int i = 0; i < bra_pos; i ++)
        Y *= 10LL;
    for(int i = 0; i < bra_pos; i ++)
        X = 10LL * X + (str[i] - '0');
    long long d = gcd(X, Y);
    X /= d; Y /= d;
} // 计算小数部分不包含括号里面的部分

void go(long long &X, long long &Y, char str[])
{
    int len = strlen(str);
    X = 0LL; Y = 1LL;
    if( len == 0 )
        return ;
    int fa = -1, fb = -1;
    for(int i = 0; str[i]; i ++)
        if( str[i] == '(' )
           fa = i;
        else 
            if( str[i] == ')' )
                fb = i;
    if( fa == -1 )
        return ;
    long long pre_based = 1LL;
    for(int i = 0; i < fa; i ++)
        pre_based *= 10LL;
    long long cur_based = 1LL, cur_val = 0LL;
    for(int i = fa + 1; i < fb; i ++) {
        cur_based *= 10LL;
        cur_val    = 10LL * cur_val + (str[i] - '0');
    }
    
    long long d1 = gcd(cur_val, pre_based);
    long long d2 = gcd(cur_val, cur_based - 1LL);
    X = cur_val / d1 / d2; Y = (pre_based / d1) * ((cur_based - 1LL) / d2);
} // 计算小数部分的括号里面的部分

// (a / b) + (c / d)
void add(long long a, long long b, long long c, long long d, long long &X, long long &Y)
{
    Y = b * d; X = a * d + b * c;
    long long dd = gcd(X, Y);
    X /= dd; Y /= dd;
} // 计算两个分数的和

int main()
{
    int nt; scanf("%d", &nt);
    while( (nt --) > 0 ) {
        scanf("%s %s", A, B);
        
        double int_part_A = get(A);
        double int_part_B = get(B);
        long long int_part = (long long)(int_part_A + int_part_B + 0.5); // 先把整数部分加起来
        //printf("int_part = %lld\n", int_part);
        
        long long pre_bra_A_0, pre_bra_A_1;
        doit(pre_bra_A_0, pre_bra_A_1, A);
        long long pre_bra_B_0, pre_bra_B_1;
        doit(pre_bra_B_0, pre_bra_B_1, B);
        long long X1, Y1; add(pre_bra_A_0, pre_bra_A_1, pre_bra_B_0, pre_bra_B_1, X1, Y1); // 再把不包括括号部分的小数加起来
        //printf("X1/Y1 = %lld/%lld\n", X1, Y1);
        
        long long ins_bra_A_0, ins_bra_A_1;
        go(ins_bra_A_0, ins_bra_A_1, A);
        long long ins_bra_B_0, ins_bra_B_1;
        go(ins_bra_B_0, ins_bra_B_1, B);
        long long X2, Y2; add(ins_bra_A_0, ins_bra_A_1, ins_bra_B_0, ins_bra_B_1, X2, Y2); // 最后把括号里面的部分加起来
        //printf("X2/Y2 = %lld/%lld\n", X2, Y2);
        
        long long resX, resY; add(X1, Y1, X2, Y2, resX, resY);
        add(int_part, 1LL, resX, resY, resX, resY); // 求和
        
        printf("%lld/%lld\n", resX, resY);
    }
    return 0;
}

【I题】略。


能力有限,难免有错,如有误,敬请留言,有其他疑问,也可联系 [email protected]

你可能感兴趣的:(校赛)