2017 Multi-University Training Contest - Team 2

补题补题

##Is Derek lying?##

题意:
Derek和Alfia分别做题,题目选项有A、B、C,现在他们所选的选项和所得的分告诉你,问他们有没有撒谎。

思路:判定他们的上下限即可,当D和A有一个人全对时,他们一样的肯定得分,不一样的肯定不得分,假设一样的选项为k个,不一样的为p个,那么他们总分之和一定小于k * 2 + p,两分之差肯定小于p。

#include <bits/stdc++.h>

#define MAXN 800050

using namespace std;

int main() {
	int T, n, x, y;
	char ch1[MAXN], ch2[MAXN];
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d", &n, &x, &y);
		getchar();
		gets(ch1);
		gets(ch2);
		if (x > n || y > n || x < 0 || y < 0) {
			puts("Lying");
			continue;
		}
		int k = 0;
		for (int i = 0; i < n; i++) {
			if (ch1[i] == ch2[i]) {
				k++;
			}
		}
		int t1 = min(x, y);
		int t2 = max(x, y);
		int p = t2 - t1, m = n - k;
		if (p <= m && t1 + t2 <= 2 * k + m) {
			puts("Not lying");
		} else {
			puts("Lying");
		}
	}
} 

##Maximum Sequence##

题意:
给一个序列ai,每次可以从b数组里任意选数表示可以从下标为1到n(这里的n,每次制造出来一个新的数n会增长)的数中任意选取一个数下标为j制造出一个数放置在a序列最后面,这个数的值为a j _j j - j,现在问新制造出来的数的和最大是多少。
思路:
拿个优先队列维护一下可用值。
所给的b序列贪心每次取最小的即可。

#include <bits/stdc++.h>

#define MAXN 500050
#define ll long long
#define MOD 1000000007

using namespace std;

struct node {
	ll idx, val;
	bool operator < (const node &a) const {
		return val < a.val;
	}
};

priority_queue<node> q;
ll num[MAXN];

void init() {
	while (!q.empty()) {
		q.pop();
	}
}

int main() {
	ll n, tmp;
	while (~scanf("%lld", &n)) {
		init();
		for (ll i = 0; i < n; i++) {
			scanf("%lld", &tmp);
			q.push((node){i + 1, tmp - (i + 1)});
		}
		for (ll i = 0; i < n; i++) {
			scanf("%lld", &num[i]);
		}
		sort(num, num + n);
		ll ans = 0;
		for (ll i = 0; i < n; i++) {
			while (!q.empty() && q.top().idx < num[i]) {
				q.pop();
			}
			ans += q.top().val;
			ans %= MOD;
			ll tt = q.top().val;
			q.push((node){n + i + 1, tt - (n + i + 1)});
		}
		printf("%lld\n", ans);
	}	
}

##Sdjpx Is Happy##

题意:
给一个序列,你可以将这个序列分成任意块,每一个块可以随意排序,你可以将其中两个块换一次位置。
现在问你最多可以分成多少块

思路:
先暴力预处理将每个区间内,可分成块的个数处理出来,若区间最大值和最小值不等于区间长度,则为0,若相对于上一块区间出现了新的最小值,则只为1,因为只能排序不能在这个区间内再分块。
若没制造出新的,则可以等于上一个区间与该区间的块状和。
然后再枚举每一块区间,若满足区间内可拆,则枚举拆点将其拆开(因为有一次交换机会)。

有点像dp。。但是就是区间暴力枚举

#include <bits/stdc++.h>

#define MAXN 3010
#define ll long long

using namespace std;

int mn[MAXN][MAXN], mx[MAXN][MAXN];
int sum[MAXN][MAXN];
int a[MAXN], la[MAXN];

int main() {
	int T, n;
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		memset(sum, 0, sizeof(sum));
		for (int i = 1; i <= n; i++) {
			scanf("%d", &a[i]);
			mn[i][i] = mx[i][i] = a[i];
			sum[i][i] = 1;
			la[i] = i;
		}
		for (int i = 1; i <= n; i++) {
			for (int j = i + 1; j <= n; j++) { 
				mn[i][j] = min(mn[i][j - 1], a[j]);
				mx[i][j] = max(mx[i][j - 1], a[j]);
			}
		}
		for (int i = 2; i <= n; i++) {
			for (int j = 1; j + i - 1 <= n; j++) {
				int e = i + j - 1;
				if (mx[j][e] - mn[j][e] != e - j) {
					sum[j][e] = 0;
				} else {
					if (mn[j][e] < mn[j][la[j]]) {
						sum[j][e] = 1;
					} else {
						sum[j][e] = sum[j][la[j]] + sum[la[j] + 1][e];
					}
					la[j] = e;
				}
			}
		}
		int ans = sum[1][n];
		for (int i = 1; i <= n; i++) {
			for (int j = i; j <= n; j++) {
				if (sum[i][j] && (i == 1 || (sum[1][i - 1] && mn[1][i - 1] == 1))) {
					int p = mx[i][j];
					if (p == n || (sum[p + 1][n] && mx[p + 1][n] == n)) {
						for (int k = p; k > j; k--) {
							if (sum[k][p] && mn[k][p] == i) {
								ans = max(ans, sum[1][i - 1] + sum[j + 1][k - 1] + sum[p + 1][n] + 2);
							}
						}
					}
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
} 

##Funny Function##

题意:有三个算式现在让你求F m , 1 _{m, 1} m,1

思路:
赛后跟机智的队友补题,暴力打表把10x10的数据打了出来。。竟然发现了规律

对于偶数
每行有f(n, m) = f(n - 2, 2) * (2 n ^n n - 1) m − 1 ^{m -1} m1
每列有f(n, 2) = f(n - 2,2) * 4 + 2

对于奇数
每行有f(n, m) = f(n - 2, 2) * (2 n ^n n - 1) m − 1 ^{m - 1} m1
每列有f(n, 2) = f(n - 2,2) * 4 + 1

当n或者m为1时,都为1。
然后矩阵快速幂和快速幂求解即可。。

#include <bits/stdc++.h>

#define N 2
#define ll long long
#define MOD 1000000007

using namespace std;

struct mat{
	ll mapp[N][N];
};

mat res;
ll rec[] = {2, 5};

void output(mat a) {
	printf("=======\n");
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			printf("%lld ", a.mapp[i][j]);
		}
		puts("");
	}
}

ll update(ll a) {
	while (a < 0) {
		a += MOD;
	}
	return a;
}

mat mul_mat(mat a, mat b, ll mod, int p) {
	mat c;
//	output(a);
//	puts("-----");
//	output(b);
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < p; j++) {
			c.mapp[i][j] = 0;
			for (int k = 0; k < N; k++) {
				c.mapp[i][j] = update(c.mapp[i][j]);
				c.mapp[i][j] += (a.mapp[i][k] * b.mapp[k][j]) % mod;
				c.mapp[i][j] %= mod;
			}
		}
	}
	return c;
}

mat ksm_mat(mat n, ll k, ll mod, int p) {
	mat t = n, ans = res;
	while (k) {
		if (k & 1) {
			ans = mul_mat(t, ans, mod, p);
		}
		t = mul_mat(t, t, mod, 2);
		k >>= 1;
	}
	return ans;
}

ll ksm(ll n, ll k, ll mod) {
	ll temp = n, ans = 1;
	while (k) {
		if (k & 1) {
			ans = update(ans);
			ans = (ans * temp) % mod;
		}
		k >>= 1;
		temp = (temp * temp) % mod;
	}
	return ans % mod;
}

ll get(ll n, ll opt) {
	mat p = {4, 1, 0, 1};
	if (opt) { //偶数 
		res = (mat){rec[0], 0, 2, 0};
	} else {
		res = (mat){rec[1], 0, 1, 0};
	}
	mat ans = ksm_mat(p, n, MOD, 1);
	return ans.mapp[0][0];
}

ll getAns(ll n, ll m) {
	if (n == 1 || m == 1) {
		return 1;
	}
	ll ans;
	if (n & 1) {
		ll a = get(n / 2 - 1, 0);
		ll b = get(n / 2 - 1, 1);
		res = (mat){a, 0, -b, 0};
		mat p = (mat){ksm(2, n, MOD) - 1, 1, 0, 1};
//		output(p);
		ans = ksm_mat(p, m - 2, MOD, 1).mapp[0][0];
		
	} else {
		ans = (get(n / 2 - 1, 1) * ksm(((ksm(2, n, MOD) - 1 + MOD) % MOD), m - 2, MOD)) % MOD;
	}
	ans = update(ans);
	return ans;
}

int main() {
	int T;
	ll n, m;
	scanf("%d", &T);
	while (T--) {
		scanf("%lld %lld", &n, &m);
		printf("%lld\n", getAns(n, m));
	}
}

##TrickGCD##

题意:
给一个序列,对于序列的每个数,你可以取1-ai中的任意数,现在让你组成新的序列,问可以组成多少个满足每个区间都满足gcd >= 2。

思路:
用筛法每次枚举每个除数,算区间内的可以被除的数的情况,最后再容斥一下把多余的去掉即可。

#include <iostream>
#include <vector>
#include <map>
#include <cstdlib>
#include <stdio.h> 
#include <string.h>

#define MAX_PRIME 200000
#define ll long long
#define MAXN 100005
#define MOD 1000000007

using namespace std;

ll f[MAXN];
ll sum[MAXN];

void init() {
	memset(sum, 0, sizeof(sum));
	for (ll i = 0; i < MAXN; i++) {
		f[i] = 1;
	}
}

ll ksm(ll a, ll n) {
	ll ans = 1, t = a;
	while (n) {
		if (n & 1) {
			ans = (ans * t) % MOD;
		}
		t = (t * t) % MOD;
		n >>= 1;
	}
	return ans;
}

int main(){
	ll T, n, c, cas = 1;
	while (~scanf("%lld", &T)) {
		while (T--) {
			init();
			ll lastans = 0;
			scanf("%lld", &n);
			for (ll i = 0; i < n; i++) {
				scanf("%lld", &c);
				sum[c]++;
			}
			for (ll i = 1; i < MAXN; i++) {
				sum[i] += sum[i - 1];
			}
			for (ll i = 2; i < MAXN; i++) {
				if (sum[i - 1]) {
					f[i] = 0;
					continue;
				}
				for (ll j = i; j < MAXN; j += i) {
					ll k = min(i + j - 1, (ll)(MAXN) - 1);
					ll b = sum[k] - sum[j - 1];
					ll a = j / i;
					f[i] = (f[i] * ksm(a, b)) % MOD;
				}
			}
			for (ll i = MAXN - 1; i >= 2; i--) {
				for (ll j = i * 2; j <= MAXN; j += i) {
					f[i] = (f[i] - f[j] + MOD) % MOD;
				}
			}
			for (ll i = 2; i < MAXN; i++) {
				lastans = (lastans + f[i]) % MOD;
			}
			printf("Case #%lld: %lld\n", cas++, lastans);
		}
	}
}

##Regular polygon##

题意:
一个200x200的坐标上有n个点整数点。问能组成多少个不同的正多边形。

题解:
其实题意都是废话。。求正多边形。其实就只有正方形的存在。。
直接判每四个点存不存在就好了。
(orz hdu卡精度搞到写个cos和sin都给卡了。。)

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

#define MAXN 1005

using namespace std;

const double PI = acos(-1.0);
bool mapp[MAXN][MAXN];

struct point {
	int x, y;	
} p[MAXN];

point xuanzhuan(point a, point b) {
	point c;
	c.x = -(a.y - b.y) + b.x;
	c.y = a.x - b.x + b.y;
	return c;
}

int solve(point a, point b) {
	for (int i = 0; i < 4; i++) {
		point c = xuanzhuan(a, b);
		a = b;
		b = c;
		if (c.x < MAXN && c.x >= 0 && c.y < MAXN && c.y >= 0) {
			if (!mapp[c.x][c.y]) {
				return 0;
			}
		} else {
			return 0;
		}
	}
	return 1;
}

int main() {
	int n, ans;
	while (~scanf("%d", &n)) {
		ans = 0;
		memset(mapp, false, sizeof(mapp));
		for (int i = 0; i < n; i++) {
			scanf("%d %d", &p[i].x, &p[i].y);
			p[i].x += 200;
			p[i].y += 200;
			mapp[p[i].x][p[i].y] = true;
		}
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (i != j) {
					ans += solve(p[i], p[j]);
				}
			}
		}
		printf("%d\n", ans / 4);
	}
} 

/*
9
0 0
0 1
1 0
1 1
2 0
0 2
2 2
2 4
4 2

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值