Educational Codeforces Round 102 (Rated for Div. 2)

比赛地址

A(水题)

题目链接

题目:
给出一个数组\(a\)并能进行一个操作使得数组元素更改为数组任意其他两元素之和,问是否可以让数组元素全部小于等于\(d\)

解析:
排序后判断最大值是否小于等于\(d\)或者最小的两个值是否小于等于\(d\)即可

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, dat[maxn];

int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, d;
		dat[1] = 0;
		scanf("%d%d", &n, &d);
		for (int i = 0; i < n; ++i)
			scanf("%d", &dat[i]);
		sort(dat, dat + n);
		printf("%s\n", dat[n - 1] <= d || dat[0] + dat[1] <= d ? "YES" : "NO");
	}
}

B(思维)

题目链接

题目:
给出两个字符串,定义\(LCM(S_1,S_2)\)是由这两个串作为循环节的最小长度串,问\(LCM(S_1,S_2)\)是什么,如果不存在则输出\(-1\)

解析:
如果\(LCM\)存在,则它的长度一定是\(LCM(len_1,len_2)\),那么已知最终串的长度,就可以算出每个循环节的循环次数,判断循环倍增后的串是否相同即可

#include<bits/stdc++.h>
using namespace std;
string str[2];

string repeat(const string& a, int x) {
	string t;
	while (x--)
		t += a;
	return t;
}

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin >> T;
	while (T--) {
		cin >> str[0] >> str[1];
		int d = gcd(str[0].length(), str[1].length());
		string t[2] = { repeat(str[0],str[1].length() / d),repeat(str[1],str[0].length() / d) };
		cout << (t[0] == t[1] ? t[0] : "-1") << endl;
	}
}

C(找规律)

题目链接
⭐⭐

题目:
给出\(n\)\(k\),以及对应的数组\(a=1,2,\dots,k-1,k,k-1,k-2,\dots,k-(n-k)\),现在假定有一个长度为\(k\)的排列\(p\),依照他构造出数组\(b\),使得\(b[i]=p[a[i]]\),问使得\(b\)中逆序对数不超过\(a\)的话,p中满足要求的且为最大字典序的排列是什么

解析:
这样的变换可以理解为\(a\)为索引下表,\(b\)为对应值
o( ̄▽ ̄)ブ找规律可以发现,将原本\(k,k-1,k-2,\dots,k-(n-k)\)进行倒序排列,逆序对数不会改变,且可以让字典序增加,但除此之外的序列仍然需要服从正序排列\(1\dots n-(n-k+1)\)

#include<bits/stdc++.h>
using namespace std;
int n, k;

int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &k);
		int a = n - k;
		int b = k - a;
		for (int i = 1; i < b; ++i)
			printf("%d ", i);
		for (int j = 0; j <= a; ++j)
			printf("%d ", k - j);
		printf("\n");
	}
}

D(思维+多种方法求端点区间极值)

题目链接
⭐⭐

题目:
给出一系列操作“+1”“-1”,以及\(q\)次查询,每次查询区间\([l,r]\)内的操作忽略,那么打印出整个操作序列中出现的不同数值的个数

解析:

  1. 这样可以将区间分为三段\([1,l-1],[l,r],[r+1,n]\),且因为变换时是连续的,所以知道变换过程中的极值即可
  2. 假设用\(sh,sl\)数组分别表示从[1,i]变换的最大值与最小值,\(eh,el\)表示从\([i,n]\)变换最大值与最小值,则区间最值\(max=\max(sh[l-1],sum[l-1]+eh[r+1]),min=\min(sl[l-1],sum[l-1]+el[r+1])\)
  3. 对于后半部分,可以考虑动态规划求解,以最大值为例子,如果从\(i+1\sim n\)的操作极大值为\(eh[i+1]\),那么显然如果第\(i\)个操作为\(+1\),那么\(eh[i]=eh[i+1]+1\)(相当于垫高了后序值),若为\(-1\),则同理\(eh[i]=eh[i+1]-1\),但要注意最大值不可能小于0。(区间最小值同理)
  4. 答案取\(max\)\(min\)之间的距离即可

注:
这道题用\(st\)表,线段树也可以,但是更浪费时间,因为这道题只需要获得两端的极值,且对于求取后半段极值的方法,也不一定必须用\(dp\),也可以直接从后向前遍历找到加上之前操作的影响获得的极值,使用时减去影响即可(即\(eh[r+1]-sum[r]\)),两种求解都是\(O(n)\)处理

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int sh[maxn], sl[maxn], sum[maxn], eh[maxn], el[maxn];
char op[maxn];
int main()
{
	int t, n, m;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d%s", &n, &m, op + 1);
		for (int i = 1; i <= n; i++)
		{
			if (op[i] == '+')
				sum[i] = sum[i - 1] + 1;
			else
				sum[i] = sum[i - 1] - 1;
			sh[i] = max(sh[i - 1], sum[i]);
			sl[i] = min(sl[i - 1], sum[i]);
		}
		eh[n + 1] = el[n + 1] = 0;
		for (int i = n; i >= 1; i--)
		{
			if (op[i] == '+')
			{
				eh[i] = eh[i + 1] + 1;
				el[i] = el[i + 1] == 0 ? 0 : el[i + 1] + 1;
			}
			else
			{
				eh[i] = eh[i + 1] == 0 ? 0 : eh[i + 1] - 1;	//也可以写作eh[i]=max(eh[i+1]-1,0)
				el[i] = el[i + 1] - 1;
			}
		}
		int l, r;
		while (m--)
		{
			scanf("%d%d", &l, &r);
			int low = min(sl[l - 1], sum[l - 1] + el[r + 1]), high = max(sh[l - 1], sum[l - 1] + eh[r + 1]);
			printf("%d\n", high - low + 1);
		}
	}
	return 0;
}

E(dijkstra+dp)

题目链接
⭐⭐⭐⭐

题目:
给出一张图,定义\((u,v)\)的路径权值=路径权重之和+路径中最小的权重-路径中最大的权重,求\(1\)到各点的路径权值最小值

解析:

  1. 可以先不管是否为最大值或者最小值,将题目抽象为,路径权重之和+某条边的权重-某条边的权重,很明显这个问题的最优解(最小解)也就是加上最小边,减去最大边时
  2. 假设\(state\)表示状态,用第一位表示路径中是否已减去某条边的权重,第二位表示路径中是否已加上某条边的权重,那么就可以获得多个状态转移方程(假设\(u\)为当前点,\(v\)为可达点)

\[\begin{cases} dis[v][state]=\min(dis[v][state],dis[u][state]+w) &\\ dis[v][state|1]=\min(dis[v][state|1],dis[u][state]) &\text{state&1==0}\\ dis[v][state|2]=\min(dis[v][state|2],dis[u][state]+2\times w) &\text{state&2==0} \end{cases} \]

  同时每个转移方程可以类比dijkstra,使用堆优化,寻找当前最短路径,保证每次可以确定一个最终状态
 3. 最终答案输出状态3对应的值即可

注意:

  1. 可以使用\(bfs\)但这样会可能会重复修改某个状态对应的值,运行相对慢一点点
  2. 由于可能存在最大值和最小值都是本身的情况,在上述做法中,每次是将一个新的点加入时确定他为(删去边、增加边、普通边)三种可能性状态中的一个,但也有可能同时一次性成为两种状态(删去边、增加便),这样的话上述转移方程未能考虑到,需要添加转移方程\(dis[v][state|3]=\min(dis[v][state|3],dis[u][state]+w)\quad state\&3==0\),或者考虑到这样的情况下必然是路径上的权值相同,且在通过点数超过一个点的情况下,遇到第二个新点时也能将路径需要的状态补全,因此只有一个点的情况下无法进行转移,所以可以在读入到与\(1\)直接相连的点时,直接将\(dis[u][3]\)赋上\(w\)
  3. 权值需要开long long
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MEM(X,Y) memset(X,Y,sizeof(X))
typedef long long LL;
using namespace std;
/*===========================================*/

const int maxn = 2e5 + 5;
struct Edge {
	int to;
	LL w;
};
struct Node {
	int st, p;
	LL w;
};
queue<Node> q;
vector<Edge> e[maxn];
LL dis[maxn][4];

void add(int u, int v, int w) {
	e[u].push_back(Edge{ v,w });
	e[v].push_back(Edge{ u,w });
}

void dij() {
	dis[1][0] = 0;
	q.push(Node{ 0,1,0 });
	while (!q.empty()) {
		auto t = q.front();
		q.pop();
		if (dis[t.p][t.st] != t.w) continue;
		for (auto& i : e[t.p]) {
			if (dis[t.p][t.st] + i.w < dis[i.to][t.st]) {
				dis[i.to][t.st] = dis[t.p][t.st] + i.w;
				q.push(Node{ t.st,i.to ,dis[i.to][t.st] });
			}
			if (!(t.st & 1) && dis[t.p][t.st] < dis[i.to][t.st | 1]) {
				dis[i.to][t.st | 1] = dis[t.p][t.st];
				q.push(Node{ t.st | 1,i.to,dis[i.to][t.st | 1] });
			}
			if (!(t.st & 2) && dis[t.p][t.st] + 2 * i.w < dis[i.to][t.st | 2]) {
				dis[i.to][t.st | 2] = dis[t.p][t.st] + 2 * i.w;
				q.push(Node{ t.st | 2,i.to,dis[i.to][t.st | 2] });
			}
		}
	}
}

int main()
{
	MEM(dis, INF);
	int n, m;
	int a, b, c;
	scanf("%d%d", &n, &m);
	while (m--) {
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
		if (a > b) swap(a, b);
		if (a == 1)
			dis[b][3] = c;
	}
	dij();
	for (int i = 2; i <= n; ++i)
		printf("%lld ", dis[i][3]);
}
posted @ 2021-01-15 13:12  DreamW1ngs  阅读(71)  评论(0编辑  收藏  举报