“Wishare杯”南邮第九届大学生程序设计竞赛之网络赛 部分题解

作为一只退役狗,有幸还能出3个题(E,F,H)

H.小学生都会

时间限制(普通/Java) :  10000 MS/ 30000 MS          运行内存限制 : 81920 KByte
总提交 : 306            测试通过 : 42 

比赛描述

有次TC去某厂面试,被问了一道据面试官说小学生都会做的题,题面如下:

n16位无符号整数, 用二进制表示可组成一个很长的01串,要求将每个01串逆序(比如10的二进制为0000000000001001,逆序后为100100000000 0000),逆序后又得到n16位无符号整数,求逆序后n个整数的最大值,以及其之前对应的数




输入

第一行为一个整数n (0 < n < 10^7)

第二行为n16位无符号正整数


输出

逆序后n个整数的最大值及其之前所对应的数

样例输入

3
3 4 5

样例输出

49152 3



这个题本来想卡空间的,不让开数组,本质是考查位运算,没难度,某厂某面试官:"小学生都会"

#include <cstdio>

int main() {
	int n, num, ans = 0, x, ma = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &x);
		num = 0;
		for (int j = 0; j < 16; j ++) {
			num <<= 1;
			num |= (x & (1 << j)) ? 1 : 0;
		}
		if (ans < num) {
			ans = num;
			ma = x;
		}
	}
	printf("%d %d\n", ans, ma);
}


E.最大剩余和

时间限制(普通/Java) :  2000 MS/ 6000 MS          运行内存限制 : 81920 KByte
总提交 : 17            测试通过 : 0 

比赛描述

给两个数列ABB数列的每个数可同时增加或减少,使其成为A数列的子数列(子数列是连续的一段),若不存在这样的子数列输出0,否则输出可能的子数列个数,并且对任意可行的子数列,找出A中删除B剩余值和的最大值。



输入

第一行两个数n,分别表示数列AB的大小(1 <= mn <= 2000000)

第二行n个数 (1 <= a[i] <= 10^9)

第三行m个数 (1 <= b[i] <= 10^9)


输出

若不存在这样的子数列输出0,否则输出子数列个数及剩余和的最大值

样例输入

4 2
1 2 3 4
1 2

样例输出

3 7

提示

B数列保持不变,A-B={34},和为7

B数列同时增加1A-B={14},和为5

B数列同时增加2A-B={12},和为3

能变成的子数列个数为3,最大剩余和为7



这题目看数据肯定是一个O(n)的算法解决,由于B数列的增减是对其所有数字,假设B数列的某个数字B[i]增加了x,可以使其等于A数列的某个数字A[j],那么B[i + 1] + x是否等于A[i + 1]呢?可以明显看出来,决定因素在x上,也就是相邻两个数的差值,这样思路就出来了,处理出A数列和B数列相邻数字间的差值得到新的C数列和D数列,我们要做的就是找到C数列中有几个D数列,归根到底就是一个字符串匹配问题,用KMP算法轻松解决,剩余值可以通过预处理前缀和O(1)得到,注意当m等于1时需要特判。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int const MAX = 2000005;
int a[MAX], b[MAX], nxt[MAX];
ll sum[MAX];
int n, m, cnt;

void get_next() {
    nxt[0] = -1;
    int i = 0, j = -1;
    while (i < m) {
    	if (j == -1 || b[i] == b[j]) {
    		i ++;
    		j ++;
    		nxt[i] = j;
    	} else {
    		j = nxt[j];
    	}
    }
}

ll KMP() {  
	n --;
	m --;
    get_next();  
    int i = 0, j = 0;
    cnt = 0;
    ll ans = 0;
    while(i < n) {  
        if(j == -1 || a[i] == b[j]) {  
            i ++;  
            j ++;  
        } else  
            j = nxt[j];  
        if(j == m) {  
            ans = max(ans, sum[n] - sum[i] + sum[i - j - 1]);
            cnt ++;
            j = nxt[j];  
        }  
    }  
    return ans;  
}     

int main() {
    scanf("%d %d", &n, &m);
    int mi = 2000000000;
    for (int i = 0; i < n; i ++) { 
        scanf("%d", &a[i]);
        if (i == 0) {
            sum[0] = a[i];
        } else {
            sum[i] = sum[i - 1] + a[i];
        }
        mi = min(a[i], mi);
    }
    for (int i = 0; i < m; i ++) {
        scanf("%d", &b[i]);
    }
    if(m > n) {
		printf("0\n");
	} else {
        if(m == 1) {
			printf("%d %I64d\n", n, sum[n - 1] - mi);
		} else {
			for (int i = 0; i < n - 1; i ++) {
            	a[i] = a[i] - a[i + 1];
        	}
        	for (int i = 0; i < m - 1; i ++) {
            	b[i] = b[i] - b[i + 1];
        	}
            ll ans = KMP();
            if (ans == 0) {
                printf("0\n");
            } else {
        	   printf("%d %I64d\n", cnt, ans);
            }
        }
    }
}


F.采花

时间限制(普通/Java) :  1000 MS/ 3000 MS          运行内存限制 : 81920 KByte
总提交 : 0            测试通过 : 0 

比赛描述

传说中有个天空花园存在着一种极其珍贵的玫瑰,这种玫瑰不会凋谢,它有个杀马特的名字叫nodie玫瑰,nodie玫瑰一年只会开出十几朵, 情人节就要到了,TC准备去天空花园采这种花送给YSJJ,花园的管理员不会轻易就让人采到花,他将花园放到了一个平面直角坐标系中,花园的出入口相同,都处在坐标系的原点,管理员规定每人每次必须从花园的入口出发沿着规定的路径采花,路径只能是某种二次函数,可以在自己选择的路径上任意走动,但是在某一路径采完后必须回到入口,整个过程算一次采花,由于天空花园的花都非常珍贵,一个人最多采k次,TC用了些套路打探到nodie玫瑰只有n朵, 贪心的TC想采下所有的nodie玫瑰,请问TC能如愿以偿吗,如果不可以,输出poor TC,否则输出TC well done in x time(s)x代表采完所有nodie玫瑰所需要的最少采花次数。




输入

第一行一个数字T(1 <= T <= 15),表示数据组数

每组数据的第一行两个数字nk(0< k < n <=20)

接下来n行,每行2个数字代表一朵nodie玫瑰的坐标,坐标绝对值不超过1000


输出

若不能采光所有nodie玫瑰,输出poor TC;否则输出采完所有nodie玫瑰所需要的最少次数


样例输入

2
2 2
1.00 3.00
2.00 4.00
2 1
-1.00 1.00
-1.00 2.00

样例输出

TC well done in 1 time(s)
poor TC

提示

对第一组样例可通过路径y=-(x-2)^2+4



首先题目中的抛物线过原点,这样抛物线的方程就确定为y=ax^2+bx的形式,显然一条过原点的抛物线可被其他任意两个不重合的点(非原点)唯一确定,因此我们用两个点(x1,y1),(x2,y2)来表示每条抛物线,联立两个点的方程可得a=(x2y1-x1y2)/(x1x2(x1-x2)),通过a可得b=y1/x1-a*x1,用一个数组s[i][j]表示过点i和点j的抛物线的覆盖点的状态(1表示经过,0表示没经过),x1和x2不能相等且都不能为0,上面都处理完,就可以愉快的状压dp了,dp[sta]表示点覆盖状态为sta时的最小抛物线个数,因为其实有很多抛物线是多余的,故直接考虑当前状态下最先 (这里先后指的是点输入的顺序) 没有被覆盖的点如何被覆盖最优,转移方程就很容易写出来了。这样做的时间复杂度是O(n*2^n)

#include <cstdio>    
#include <cstring>    
#include <algorithm>    
#include <cmath>    
using namespace std;    
int const INF = 0x6fffffff;    
int const MAX = 21;    
double const EPS = 1e-10;    
double x[MAX], y[MAX], A[MAX][MAX];    
int s[MAX][MAX], dp[1 << MAX];    
  
int main() {    
    int T, n, k;    
    scanf("%d", &T);    
    while (T --) {    
        scanf("%d %d", &n, &k);    
        int all = (1 << n) - 1;    
        for (int i = 0; i < n; i ++) {    
            scanf("%lf %lf", &x[i], &y[i]);    
        }    
        for (int i = 0; i < n; i ++) {    
            for (int j = i + 1; j < n; j ++) {    
                if (fabs(x[i] - x[j]) < EPS || x[i] == 0 || x[j] == 0) {    
                    s[i][j] = 0;    
                    continue;    
                }    
                s[i][j] = (1 << i) | (1 << j);    
                double A = (x[j] * y[i] - x[i] * y[j]) / (x[i] * x[j] * (x[i] - x[j]));    
                if (fabs(A) < EPS) {      
                    s[i][j] = 0;    
                    continue;    
                }    
                double B = y[i] / x[i] - A * x[i];      
                for (int k = 0; k < n; k ++) {    
                    if (k != i && k != j && fabs(A * x[k] * x[k] + B * x[k] - y[k]) < EPS) {    
                        s[i][j] |= (1 << k);    
                    }    
                }    
            }    
        }    
        for (int i = 0; i <= all; i ++) {    
            dp[i] = INF;     
        }    
        dp[0] = 0;    
        for (int sta = 0; sta <= all; sta ++) {    
            int i = 0;    
            while (sta & (1 << i)) {     
                i ++;    
            }    
            if (dp[sta] < INF) {    
                dp[sta | (1 << i)] = min(dp[sta | (1 << i)], dp[sta] + 1);    
                for (int j = i + 1; j < n; j ++) {    
                    dp[sta | s[i][j]] = min(dp[sta | s[i][j]], dp[sta] + 1);    
                }    
            }   
        }   
        if (dp[all] <= k) {  
            printf("TC well done in %d time(s)\n", dp[all]);  
        } else {  
            printf("poor TC\n");  
        }  
    }    
}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值