Codeforces Round #695 (Div. 2)

比赛地址

A (水题)

题目链接
⭐⭐⭐

题目:
给出\(n\)个面板,每个面板初始时间相同,每过1s面板上数字会加1(数字在\(0\sim9\)循环播放),任意时刻选择一个面板\(x\)使他的时间停止,其他的面板\(y\)会在间隔\(|x-y|\)s以后停止,问从左到右顺序组成的数字序列所对应的十进制数最大值是多少?

解析:
只要最高位最大即可,因此首先需要最高位为9,那么次高位一定得是8,再次高位并不是7,因为可以在次高位为8时停止第二个面板构成989的序列

总结:

  • \(n=1\)时,只输出9
  • \(n\ne1\)时,输出\(98\)其余位模十加1即可
#include<bits/stdc++.h>
using namespace std;

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n;
		scanf("%d", &n);
		if (n == 1)
			printf("9\n");
		else
		{
			printf("98");
			int t = 8;
			for (int i = 2; i < n; ++i)
				printf("%d", t = (t + 1) % 10);
			printf("\n");
		}
	}
}

B (暴力)

题目链接
⭐⭐

题目:
给出一个数组\(a\),可以改变一次数组中某一元素的值,问数列\(a\)中最小的极值点个数是多少?

解析:

  1. 对于改变任意一个数而言,能够影响到的极值点的情况也只有自己或者左右的两个数据,而为了减小极值点数量,必然是使\(a[i]=a[i-1]\mid a[i]=a[i+1]\)两种情况,
  2. 这样便可以对已经是极值点的数据进行操作,并且暴力检查两种情况下缩减极值点数目的最大值即可

注意:可以对端点值进行复制,避免在遍历过程中对端点进行特判

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
int dat[maxn];
int cnt;

bool check(int i)
{
	return dat[i] > dat[i - 1] && dat[i] > dat[i + 1] || dat[i] < dat[i - 1] && dat[i] < dat[i + 1];
}

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		cnt = 0;
		int n;
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i)
			scanf("%d", &dat[i]);
		dat[0] = dat[1], dat[n + 1] = dat[n];
		int mx = 0;
		for (int i = 2; i < n; ++i)
		{
			if (check(i))
			{
				++cnt;
				int t = dat[i], now = 0, aa = 0;
				for (int j = i - 1; j <= i + 1; ++j)
					now += check(j);
				dat[i] = dat[i - 1];
				for (int j = i - 1; j <= i + 1; ++j)
					aa += check(j);
				mx = max(mx, now - aa);
				aa = 0;
				dat[i] = dat[i + 1];
				for (int j = i - 1; j <= i + 1; ++j)
					aa += check(j);
				mx = max(mx, now - aa);
				dat[i] = t;
			}
		}
		printf("%d\n", cnt - mx);
	}
}

C (贪心+思维)

题目链接
⭐⭐

题目:
给出三个数列,每次可以从两个数列中分别挑出两个数 \(a,b\) 并从原数列中删去,并将\(a-b\)放进第一个数列,问最后剩余的一个数最大是多少(其余两个数列为空)?

解析:
两种贪心策略

  1. 每次选出某一个序列的最小值用这个最小值减去其他序列的”所有“(保留一个)值,再用保留值减去这个所选的序列中所有的值,最后剩余数的值=其他两个序列的值之和-2*选中序列的值
  2. 分别选出两个序列中的最小值分别用各自的最小值减去另一个序列的除最小值以外的其他值,未选中序列的“所有”(保留一个)值被两个最小值中任意一个减去都可以,然后再用保留值减去这两个最小值进行了若干次操作后的结果,最后剩余数的值=除两个选中最小值的和-选中的最小值的和

总结:

\[result=sum-2\times\min(sum_{array},min_{ai},min_{aj}) \]

注意:要开long long

#include<bits/stdc++.h>
using namespace std;

LL mn[3] = { INF,INF,INF }, sum[3], t;
LL n[3];

int main()
{
	scanf("%d%d%d", &n[0], &n[1], &n[2]);
	for (int i = 0; i < 3; ++i)
	{
		while (n[i]--)
		{
			scanf("%lld", &t);
			sum[i] += t;
			mn[i] = min(mn[i], t);
		}
	}
	printf("%lld", sum[0] + sum[1] + sum[2] - 2 * min({ mn[0] + mn[1],mn[1] + mn[2],mn[0] + mn[2],sum[0],sum[1],sum[2] }));
}

D (dp)

题目链接
⭐⭐⭐⭐

题目:
给出一组数列,每次可以选择一个位置作为起点,每次可以向左(右)移动一步,共移动\(k\)次,给出\(q\)条询问,每次改变数列中某个元素的值,问当前数列下所有可能路径对应的权值和的和是多少?(答案取模\(10^9+7\)

解析:

  1. 首先想到的是对于给定的数组长度\(n\)以及移动次数\(k\),那肯定对应数列中每一个元素有一个对应的贡献值,于是考虑打表找规律,发现找不着,再观察到数据范围控制在\(10^3\),所以联想到用dp求解
  2. 如果定义状态\(dp[i][j]\)为以\(i\)为终点,总共移动了\(j\)次对应的路径数量,很显然可以得到状态转移方程

\[dp[i][j]=dp[i-1][j-1]+dp[i+1][j-1] \]

 同时由于路径是具有对称性的,也可以将这个状态理解为,以\(i\)为起点,总共移动了\(j\)次对应的路径数量

  1. 那么如果要求得移动\(j\)次后恰好在第\(i\)个数所对应的路径数量,则为\(dp[i][j]\times dp[i][k-j]\),于是也就可以得到所有路径中经过\(i\)点的路径数量

\[sum[i]=\sum_{j=0}^kdp[i][j]\times dp[i][k-j] \]

  1. 最后每次查询的时候更新变化量即可

注意:

  1. 一定要记得最后查询的时候,变化量可能为负值,要加模取模
  2. 注意状态转移方程中,要先枚举\(j\),再枚举\(i\),因为只有在所有元素对应的\(j-1\)层的移动确认以后,才可以更新\(j\)层的数值
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;

int n, k, q;
LL sum[5005], dp[5005][5005], dat[5005];
const int mod = 1e9 + 7;

int main()
{
	scanf("%d%d%d", &n, &k, &q);
	for (int i = 1; i <= n; ++i)
	{
		dp[i][0] = 1;
		scanf("%lld", &dat[i]);
	}
	for (int j = 1; j <= k; ++j)
		for (int i = 1; i <= n; ++i)
			dp[i][j] = (dp[i - 1][j - 1] + dp[i + 1][j - 1]) % mod;
	LL ret = 0;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 0; j <= k; ++j)
			sum[i] = (sum[i] + dp[i][j] * dp[i][k - j]) % mod;
		ret = (ret + sum[i] * dat[i]) % mod;
	}
	while (q--)
	{
		scanf("%d%d", &n, &k);
		ret = (ret + sum[n] * (k - dat[n]) % mod + mod) % mod;
		dat[n] = k;
		printf("%lld\n", ret);
	}
}
posted @ 2021-01-11 00:52  DreamW1ngs  阅读(116)  评论(0编辑  收藏  举报