2021 Hubei Provincial Collegiate Programming Contest 补题

总结

多多注意数据范围 数据范围 数据范围!!!

D. Fragmentation merging

题意:

给定1–n的全排列,随机选取两个区间A,B, 产生C的条件为:A和B交集为空,且A和B并集中最大值-最小值 + 1为并集的大小。
求能产生多少种C。(不同种C中组成元素不同)

思路:

观察数据范围,n <= 1e4。提示可能有O(n ^ 2)做法。
假设枚举集合C中min值和max值, 那么能确定集合C中的元素。(C中元素必定是连续的)
考虑O(n ^ 2)是否可行。
[i, j]
[i, j + 1]
[i, j + 2]
[i, j +…]
判断当前[i, j]是否成立。 因为枚举i、j是从小到大枚举。
那么对于同一个i,j是逐渐增大的。
假设j枚举到了k,那么说明[i,k - 1]作为最大值已经遍历过了。那么如果[i, k]合法。说明此时组成的区间要么是1个或者2个。
暴力枚举最小值、最大值,根据区间的个数判断是否合法即可。

code:

int pos[maxn], book[maxn], a[maxn];
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		int n;
		scanf("%d", &n);
		for(int i = 1 ; i <= n ; i ++)
		scanf("%d", &a[i]), pos[a[i]] = i;
		int res = 0;
		if(n == 1)
		printf("0\n");
		else
		{
			for(int i = 1 ; i <= n ; i ++)
			{
				for(int j = 1 ; j <= n ; j ++)
				book[j] = 0;
				int cnt = 0;
				for(int j = i ; j <= n ; j ++)
				{
					book[pos[j]] = 1;
					if(pos[j] == 1)
					{
						if(pos[j] + 1 <= n && !book[pos[j] + 1]) cnt ++;
					}
					else if(pos[j] == n)
					{
						if(pos[j] - 1 >= 1 && !book[pos[j] - 1]) cnt ++;
					}
					else //左右都没有 
					{
						if((pos[j] - 1 >= 1 && !book[pos[j] - 1]) && (pos[j] + 1 <= n && !book[pos[j] + 1])) cnt ++;
						if((pos[j] - 1 >= 1 && book[pos[j] - 1]) && (pos[j] + 1 <= n && book[pos[j] + 1])) cnt --;
					}
					if(cnt <= 2)
					res ++;
				}
			}
			printf("%d\n",res);
		}
	
	}
	return 0;
}

H. Information Transmission

题意:

给定一个有向图。如果从某点出发,能经过<=3条边再回到当前点,那么在之间的边上传递信号,信号不会衰弱,否则每次传递信号衰弱一次。求1号点到各个点的信号衰弱的最小次数。

思路:

根据题意,一个点能经过<=3次信号衰弱再次到达当前点,那么在此次路径上信号不会衰弱。可以看成当前路径间边权为0,否则边权为1。那么就将题目转化为了求1号点到其他点的最小距离的问题。
一个点经历 <= 3条边再次回到当前点。
那么有可能为 u—> v —> u 、 u – > v – > t – > u
考虑到数据范围n <= 500且路径中最多出现三个点,就不需要dfs缩点了。
直接暴力枚举从n个点中任选两点、三点之间的路径和是否<=3,如果<=3,那么之间的路径权值为0。
然后跑次floyed计算答案即可(floyed暴力思想yyds)。

code:

int dis[305][305];
int main()
{
	int N, M;
	scanf("%d %d", &N, &M);
	for(int i = 1 ; i <= N ; i ++)
	{
		for(int j = 1 ; j <= N ; j ++)
		{
			if(i == j) dis[i][j] = 0;
			else dis[i][j] = 1e6;
		}
	}
	for(int i = 1 ; i <= M ; i ++)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		dis[u][v] = 1;
	}
	for(int i = 1 ; i <= N ; i ++)
	{
		for(int j = 1 ; j <= N ; j ++)
		{
			if(i != j && dis[i][j] + dis[j][i] <= 3)
			dis[i][j] = dis[j][i] = 0;
			for(int k = 1 ; k <= N ; k ++)
			{
				if(i != j && i != k && j != k && dis[i][j] + dis[j][k] + dis[k][i] <= 3)
				{
					dis[i][j] = dis[j][k] = dis[k][i] = 0;
					dis[i][k] = dis[j][i] = dis[k][j] = 0;
				}	
			}
		}
	}
	for(int k = 1 ; k <= N ; k ++)
	{
		for(int i = 1 ; i <= N ; i ++)
		{
			for(int j = 1 ; j <= N ; j ++)
			{
				if(dis[i][j] > dis[i][k] + dis[k][j])
				dis[i][j] = dis[i][k] + dis[k][j];
			}
		}
	}
	for(int i = 1 ; i <= N ; i ++)
	{
		if(dis[1][i] == 1e6) printf("-1 ");
		else printf("%d ", dis[1][i]);
	}
	return 0;
}

I. Sequence

题意:

给定一个长度为N的数组和M个限制。
限制形式:a b表示p[a] != b
(A,B,L,R):在区间[A,B]上任意一点为值域[L,R]上某值。
求有多少个(A, B,L, R)。

思路:

考虑直接计算很难, 需要容斥。
观察题目数据范围,(A,B,L,R) <= 5000。M <= 1e6
考虑解题时间复杂度O(n ^ 2) O(n ^ 2 log)
考虑题意给定的限制。
假设把其看作是矩阵中某个格子不能放东西。
类似(1为能放, 0为不能放)
1 0 1 0
0 0 1 0
1 1 0 0
1 0 1 0
那么合法的(A,B,L,R)数量为所有1能组成的矩形的个数。
题目就转化为给定n * n的矩阵,求其中所有能由1组成的矩形的个数(非常巧妙的转化)
那么题目就变成了很经典的问题,直接上板子。

code:

int mm[5010][5010], h[maxn], l[maxn], r[maxn];
int main()
{
	int N, M;
	scanf("%d %d", &N, &M);
	for(int i = 1 ; i <= M ; i ++)
	{
		int a, b;
		scanf("%d %d", &a,&b); 
		mm[a][b] = 1; //1不能放 
	}
	ll sum = 0;
	for(int i = 1 ; i <= N ; i ++)
	{
		for(int j = 1 ; j <= N ; j ++)
		{
			if(!mm[i][j]) h[j] ++;
			else h[j] = 0;	
		}
		stack <int> stk;
		h[0] = h[N + 1] = -1;
		for(int j = 1 ; j <= N + 1; j ++)
		{
			while(!stk.empty() && h[stk.top()] > h[j])  // 从当前点可以拓展  右边第一个 比他小的 
			r[stk.top()] = j, stk.pop();
			stk.push(j);
		}
		while(!stk.empty()) stk.pop();
		for(int j = N ; j >= 0 ; j --)
		{
			while(!stk.empty() && h[stk.top()] >= h[j]) // 左边第一个 >= h[j]的 
			l[stk.top()] = j, stk.pop();
			stk.push(j);
		}
		for(int j = 1 ; j <= N ; j ++)
		sum += (j - l[j]) * 1ll * (r[j] - j) * 1ll * h[j] * 1ll;
	}
	printf("%lld\n", sum);
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值