2017 Multi-University Training Contest - Team 7

神奇的数据。开机20分钟挂机4小时。

##Build a tree##

题意:
给一个完全k叉树,rootn个节点点,问整个树,每个节点的子节点数+1 异或出来的值是多少。

思路:
其实不难发现,当有n层时,子树里会有n-1层的满k叉树x棵,n-2层的满k叉树y棵,n-1层的完全k叉树z棵这三种情况。
如果k % 2 == 0,那么对于一个满k叉树,他所异或的值为整棵树的节点数
如果k % 2 == 1,那么多于一个满k叉树,他所异或的值为从1到n层(1 - k ^ i) / (1 - k)的异或值
这个可以打个表。
当x % 2 == 1, y % 2 == 1的时候可以取值,因为如果棵数成偶数的话就异或为0了。

我们可以打表算出,当层数为n时拥有的结点数。
那么我们可以通过剩下的rootn个结点,算出x,y,z。
然后递归直到为0或为1即可。
这里要注意(1 - k ^ i) / (1 - k)这个不能直接算,k ^ i可能会先爆long long。累加即可。

当k = 1时,树为链,求1-n的异或值,找规律发现4个出现一次0。那么每次最多只需要算4次即可。

/*
满足
n > (1 - k ^ a) / (1 - k)
有a + 1层,这层的总节点数为(1 - k ^ (a + 1)) / (1 - k) = n1个节点 
上一层总节点数为(1 - k ^ a) / (1 - k) = n2 个节点 
一棵满k叉子树需要(1 - k ^ a) / (1 - k) = n3个节点
一棵少一层的k叉子树需要(1 - k ^ (a - 1)) / (1 - k) = n4个节点
那么剩下的节点数为z = n - x * n3 - y * n4
那么可以算出
y有(n1 - n) / k ^ a个 
x有(n - n2) / k ^ a个
可得剩下的节点数

有前缀和
1 异或 (1 - k ^ 2) / (1 - k) 异或 ...

可知花费为 
^rootn
x % 2 == 1时 贡献 ^sum(a)
y % 2 == 1时 贡献 ^sum(a - 1)
继续递归可得 

这里n1可能已经爆long long了,所以可以通过其他方式计算到x,y
*/
#include <bits/stdc++.h>

#define ll long long
#define MAXN 200

using namespace std;

const ll MOD = 1e9;

ll f[MAXN], sum[MAXN];
ll mi[MAXN];
ll ans, k;

void dfs(ll rootn) {
	ans ^= rootn;
	if (rootn == 0 || rootn == 1) {
		return ;
	}
	ll a = 1;
	for (ll i = 1; i < MAXN; i++) {
		if (f[i] >= rootn || f[i] <= 0 || mi[i] <= 0) {
			break;
		}
		a = i;
	}
	ll n2 = f[a];
	ll n3 = n2;
	ll n4 = f[a - 1];
	ll y;
	ll x = (rootn - n2) / mi[a - 1];
	if ((n2 - x * mi[a - 1]) % mi[a - 1] == 0) {
		y = k - x;
	} else {
		y = k - x - 1;
		y = y < 0 ? 0 : y; 
	}
	if (x % 2) {
		ans ^= sum[a];
	}
	if (y % 2) {
		ans ^= sum[a - 1];
	}
	ll z = rootn - x * n3 - y * n4 - 1;
	dfs(z);
} 

void init() {
	f[0] = sum[0] = 0;
	mi[0] = 1;
	for (ll i = 1; i < MAXN; i++) {
		mi[i] = mi[i - 1] * k;
		f[i] = mi[i - 1] + f[i - 1];
		if (k & 1) {
			sum[i] = sum[i - 1] ^ f[i];
		} else {
			sum[i] = f[i];	
		}
	}
}

int main() {
	int T;
	ll n;
	scanf("%d", &T);
	while (T--) {
		scanf("%lld %lld", &n, &k);
		ans = 0;
		if (k == 1) {
			while (n && (n + 1) % 4) {
				ans ^= n;
				n--;
			}
		} else {
			init();
			dfs(n);
		}
		printf("%lld\n", ans);
	}
} 

/*
100
1000000000000000000 1000000000
*/

##Euler theorem##

签到题。
题意:有a % b,现在告诉你a,问a % b的值有多少种情况出现。
思路:不难发现,大于0,小于(a + 1) / 2的数都可以被取得,再加上本身可以%=0

#include <bits/stdc++.h>

#define ll long long

using namespace std;

int main() {
	int T;
	ll n;
	scanf("%d", &T);
	while (T--) {
		scanf("%lld", &n);
		printf("%lld\n" , (n + 1) / 2 + 1);
	}
}

##Free from square##

题意:
从1到n当中,每次选取不超过k次,问能取到没有平方因子的情况有多少种。

思路:
没有平方因子很容易能想到素数,也就是500以内的素数随机组合选取,但是又要不超过k次。那就有点难度了。。
500的平方根以内的素数一共有8个,我们只要保证这八个不重复即可(因为其他的只要重复了就超过了500了)。
先将1到n以内的数按照刨去这八个素数后所得的数分组。
每组里面有着对于这八个数的不同状态,状压一下。
将所有情况都分组后,就是分组背包问题了。
dp的时候要每次枚举不同的状态和当前可以选择的数目,从上一次的情况获取到。
然后滚动数组维护一下即可。

#include <bits/stdc++.h>

#define MAXN 505
#define ll long long
#define MOD 1000000007

using namespace std;

int p[] = {2, 3, 5, 7, 11, 13, 17, 19};
vector<int> v[MAXN];
int b[MAXN], bel[MAXN];
int n, k;
bool mark[MAXN][1 << 9];
ll dp[2][MAXN][1 << 9];

void add(int x, int y) {
	v[x].push_back(y);
}

void init() {
	for (int i = 0; i < MAXN; i++) {
		v[i].clear();
		b[i] = 0;
		bel[i] = i;
	}
	memset(dp, 0, sizeof(dp));
	memset(mark, false, sizeof(mark));
}

void div() {
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < 8; j++) {
			if (b[i] != -1 && i % p[j] == 0 && i % (p[j] * p[j]) != 0) {
				b[i] |= (1 << j);
				bel[i] /= p[j];
			} else if (i % p[j] == 0 && i % (p[j] * p[j]) == 0) {
				b[i] = -1;
				break;
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		if (b[i] != -1) {
			if (bel[i] == 1) {
				add(i, b[i]);
			} else {
				add(bel[i], b[i]);
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < v[i].size(); j++) {
			mark[i][v[i][j]] = true;
		}
	}
}

void work() {
	for (int i = 1; i <= n; i++) {
		if (v[i].size() == 0) {
			continue;
		}
		for (int j = 0; j < (1 << 8); j++) {
			if (!mark[i][j]) {
				continue;
			}
			dp[i & 1][1][j] = (dp[(i & 1) ^ 1][1][j] + 1) % MOD;
			for (int l = 1; l <= k; l++) {
				for (int q = 0; q < (1 << 8); q++) {
					if (!(j & q)) {
						dp[i & 1][l][j | q] = (dp[(i & 1) ^ 1][l - 1][q] + dp[i & 1][l][j | q]) % MOD;
					}
				}
			}
		}
		for (int j = 1; j <= k; j++) {
			for (int l = 0; l < (1 << 8); l++) {
				dp[(i & 1) ^ 1][j][l] = dp[i & 1][j][l];
			}
		}
	}
}

ll cal() {
	ll ans = 0;
	for (int i = 1; i <= k; i++) {
		for (int j = 0; j < (1 << 8); j++) {
			ans += dp[k & 1][i][j];
			ans %= MOD;
		}
	}		
	return ans;
}

int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		init();
		scanf("%d %d", &n, &k);
		div();
		work();
		printf("%lld\n", cal());
	}
}

##Hard challenge##

题意:
给n个坐标点每个点的价值为val,两两点构成的线段的价值为val1 * val2,现在求一条经过(0,0)点的直线可以使它交到的线段价值最大。

思路:
把所有点都打成以角度排序,以某点转180°的点,都可以对该点造成价值。
用尺取法一段一段180°取就好了。
用x,y算它角度的 atan2(y, x) / PI * 180

#include <bits/stdc++.h>

#define ll long long
#define MAXN 100005

using namespace std;

const double PI = acos(-1.0);

struct point {
	ll x, y, val;
	double ang;
	point(ll _x, ll _y, ll _val) : x(_x), y(_y), val(_val) {
		ang = atan2(y, x) / PI * 180;
		if (ang < 0) {
			ang += 360;
		}
	}
	point() {}
} reg[MAXN];

int cmp(point a, point b) {
	return a.ang < b.ang;
}

int main() {
	ll T, x, y, val, n;
	scanf("%lld", &T);
	while (T--) {
		scanf("%lld", &n);
		for (int i = 0; i < n; i++) {
			scanf("%lld %lld %lld", &x, &y, &val);
			reg[i] = point(x, y, val);
			reg[i + n] = point(x, y, val);
			reg[i + n].ang += 360;
		}
		n *= 2;
		sort(reg, reg + n, cmp);
		ll l, r;
		l = 0, r = 0;
		ll lval = 0, rval = 0;
		while (r + 1 < n && reg[0].ang + 180 > reg[r + 1].ang) {
			rval += reg[++r].val;
		}
		ll ans = 0;
		if (r == 0) {
			rval += reg[++r].val;
		}
		for (ll i = 1; i < n; i++) {
			rval -= reg[i].val;
			lval += reg[i - 1].val;
			while (l < i && reg[i].ang - 180 >= reg[l].ang) {
				lval -= reg[l++].val;
			}
			while (r + 1 < n && reg[i].ang + 180 > reg[r + 1].ang) {
				rval += reg[++r].val;
			}
			ans = max(ans, (lval + reg[i].val) * rval);
			if (i == r && i + 1 < n) {
				rval +=  reg[++r].val;
			}
		}
		printf("%lld\n", ans);
	}
}

/*
3
3
1 1 1
1 -1 10
-1 0 100
*/

##Just do it##

题目:
给一个序列a,有一种操作,对于每一个下标i的数,他是 [ 1 , i ] 之 间 的 a i 异 或 得 到 的 [1,i]之间的ai异或得到的 [1,i]ai
现在进行m次操作,问最终序列的样子。

思路:
其实可以发现有dp[i][j]表示下标为i,已经操作了j次。
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] x o r d p [ i ] [ j − 1 ] dp[i][j] = dp[i - 1][j] xor dp[i][j - 1] dp[i][j]=dp[i1][j]xordp[i][j1]
再转化一下。有 d p [ i ] [ j ] = d p [ i − 2 k ] x o r d p [ i ] [ j − 2 k ] dp[i][j] = dp[i - 2^k] xor dp[i][j - 2^k] dp[i][j]=dp[i2k]xordp[i][j2k]
那可以发现,其实对于一个下标为i的,我们只需要做他j二进制化后,为1的位置(跟快速幂差不多)。
然后dp一下就好了。

#include<bits/stdc++.h>

#define MAXN 200005
#define ll long long

using namespace std;

ll a[MAXN];

int main(){
	ll T, n, m;
	scanf("%d", &T);
	while (T--) {
		scanf("%lld %lld", &n, &m);
		for (ll i = 0; i < n; i++) {
			scanf("%lld", &a[i]);
		}
		for (ll k = 0; 1 << k <= m; k++) {
			if (m >> k & 1) {
				for (ll i = 1 << k; i < n; i++) {
					a[i] ^= a[i - (1 << k)];
				}
			}
		}
		for (ll i = 0; i < n; i++) {
			if (i) {
				printf(" ");
			}
			printf("%lld", a[i]);
		}
		puts("");
	}
}

##Kolakosk##

题意:一个Kolakoski序列。。。。。。问你第n位是多少
什么是Kolakoski序列呢。百度去吧。。好难描述

思路:按照Kolakoski的描述打表即可。。
数据水到比赛的时候判奇偶都可以过。。

#include <bits/stdc++.h>

#define ll long long
#define MAXN 10000005

using namespace std;

int k[MAXN];

void init() {
	k[1] = 1;
	k[2] = 2;
	int cnt = 2;
	for (int i = 1; i < MAXN; i += k[cnt++]) {
		for (int j = 0; j < k[cnt]; j++) {
			k[i + j + 1] = k[i] ^ 3; 
		}
	}	
}

int main() {
	int T, n;
	init();
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		printf("%d\n", k[n]);
	}	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值