2020ICPC 江西省大学生程序设计竞赛

E.Color Sequence

算法:数学 + 前缀和
因为合法字串含的每一种颜色都是偶数,所以我们可以利用异或判断字串是否合法,因为颜色的种类不超过20,所以我们可以直接用int存下,任何在二进制下每一位代表一种颜色,即0表示:2(1), 10(1), 1表示:2(10), 10(2), 2表示:2(100), 10(4)…
那么怎么去计算里面有多个合法的字串呢?
首先我们得知道,连续异或一个数两次,那么这个数就等于原来那个数,即:a ^ b ^ b = a,
有此可知,只要s[i] == s[j],则 i + 1 ~ j 这段字串一定符合要求。
下面说一下实现,首先我们可以用s[ ]数组存下来他们的异或前缀和,然后用mp[ ]来记录有多少种可能
参考代码:

#include <iostream>
using namespace std;

const int N = 1e6 + 10, M = 3e6;

typedef long long LL; 
int a[N], s[N], mp[M];

int main()
{
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) 
	{
		cin >> a[i];
		s[i] = s[i - 1] ^ (1 << a[i]);
	}
	
	LL ans = 0;
//	mp[0] = 1;
// 这里必须从0开始,或者也可以令mp[0] = 1
	for(int i = 0; i <= n; i++)
	{
		ans += mp[s[i]];
		mp[s[i]]++;
	}
	
	cout << ans << endl;
}

H.Sequence

算法:线段树 + 二分
最近刚学了线段数,属于是现学现用吧,感觉这道题不难在线段树,而是二分上,实属是难调
这道题非常容易看出是线段树的题,只用用线段树来维护最小值,然后在用二分去找左边最近的小于自己的,和右边最近小于自己的(包含自己),然后答案就是,左边的个数 * 右边的个数
这里需要注意的是,找左边的话,在有右区间找,找右边的话,在左区间找,然后因为是使距离尽可能大,直接上二分模板即可
参考代码:

#include <iostream>
#include <cstdio>
using namespace std;

typedef long long LL; 
const int N = 1e5 + 10,INF = 0x3f3f3f3f;

struct Node
{
	int l, r;
	int minn;
}tr[4 * N];
int n, m;
int a[N];

void pushup(int u)
{
	tr[u].minn = min(tr[u << 1].minn, tr[u << 1 | 1].minn);
}

void build(int u, int l, int r)
{
	if(l == r) 
	{
		tr[u] = {l, r, a[l]};
		return ;
	}
	
	int mid = l + r >> 1;
	tr[u] = {l, r};
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

void modify(int u, int x, int y)
{
	if(tr[u].l == x && tr[u].r == x)
	{
		tr[u] = {x, x, y};
		return ;
	}
	
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid) modify(u << 1, x, y);
	else modify(u << 1 | 1, x, y);
	pushup(u);
}

int ask(int u, int l, int r)
{
	if(l <= tr[u].l && r >= tr[u].r) return tr[u].minn;
	
	int mid = tr[u].l + tr[u].r >> 1;
	int ans = INF;
	if(l <= mid) ans = min(ans, ask(u << 1, l, r));
	if(r > mid) ans = min(ans, ask(u << 1 | 1, l, r));
	return ans;
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	
	build(1, 1, n);
	
	int op, x, y;
	while(m--)
	{
		cin >> op;
		if(op == 1)
		{
			scanf("%d%d", &x, &y);
			a[x] = y;
			modify(1, x, y);
		}
		
		else
		{
			scanf("%d", &x);
			
			int l = 1, r = x;
			while(l < r)
			{
				int mid = l + r + 1 >> 1;
				// 在右区间找
				if(ask(1, x - mid + 1, x) >= a[x]) l = mid;
				else r = mid - 1;
			}
			int ans1 = l;
			
			l = 1, r = n - x + 1;
			while(l < r)
			{
				int mid = l + r + 1 >> 1;
				// 在左区间找
				if(ask(1, x, x + mid - 1) >= a[x]) l = mid;
				else r = mid - 1;
			}
			int ans2 = l;
			
			printf("%lld\n", (LL)ans1 * ans2);
		}
	}
	
	return 0;
} 

I.Simple Math Problem

一道找规律题,直接看可能看不出什么规律,我们可以斜着看,就是一个菱形,这样规律就比较就挺容易找到,规律就不多了说了
参考代码:

#include <iostream>
using namespace std;

typedef long long LL;

int main()
{
	int n, x, y;
	cin >> n >> x >> y;
	x++, y++;
	
	// x + y - 1就是代表是第几行,这题要分上半部分和下半部分求解
	if(x + y - 1 <= n)
	{
		LL ans = (LL)(x + y - 1) * (x + y - 2) / 2 + x - 1;
		cout << ans << endl;
	}
	
	// 下半部分求解,因为是对称的,所以可以利用上半部分来求解,即 n * n - 上半部分
	else
	{
		x = n - x + 1, y = n - y + 1;
		LL t = (LL)(x + y - 1) * (x + y - 2) / 2 + x ;
//		cout << t << endl;
		LL ans = n * n - t;
		cout << ans << endl;
	}
	
	return 0;
}

K.Travel Expense

算法:图论 + 二分
这个很容易看的出是多源最短问题,就是在判断答案的时候,用二分求找答案,因为答案要尽可能大,所以直接是二模板即可
参考代码:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;
const int N = 110, INF = 1e9;

int n, m;
int s, t, b;
int d[N][N];

void init()
{
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++) 
		{
			if(i == j) d[i][j] = 0;
			else d[i][j] = INF;
		}
}

void floyd()
{
	for(int k = 1; k <= n; k++)
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++)
			{
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
			}
}

bool judge(int mid)
{
//	printf("hhh%d\n", mid);
	LL sum = 0;
	for(int i = 1; i <= d[s][t]; i++)
	{
		sum += (LL)pow(mid, i);
//		printf("***%lld\n", sum);
		if(sum > b) return false;
	}
	return true;
}

int main()
{
	cin >> n >> m;
	init();
	while(m--)
	{
		int a, b;
		cin >> a >> b;
		d[a][b] = 1; d[b][a] = 1;
	}
	
	floyd();
	int q;
	cin >> q;
	while(q--)
	{
		scanf("%d%d%d", &s, &t, &b);
		
//		cout << d[s][t] << endl;
		int l = 0, r = b;
		while(l < r)
		{
			int mid = l + r + 1 >> 1;
			// 因为答案要求最大,所以这里要判断答案能否更大,即l = mid
			if(judge(mid)) l = mid;
			else r = mid - 1;
		}
		
		cout << l << endl;
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值