Educational Codeforces Round 102 (Rated for Div. 2)

比賽地址ios

A(水題)

題目連接
c++

題目:
給出一個數組\(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(思惟)

題目連接
spa

題目:
給出兩個字符串,定義\(LCM(S_1,S_2)\)是由這兩個串做爲循環節的最小長度串,問\(LCM(S_1,S_2)\)是什麼,若是不存在則輸出\(-1\)code

解析:
若是\(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中知足要求的且爲最大字典序的排列是什麼ci

解析:
這樣的變換能夠理解爲\(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]);
}
相關文章
相關標籤/搜索