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,l-1],[l,r],[r+1,n]\),且因为变换时是连续的,所以知道变换过程中的极值即可
- 假设用\(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])\)
- 对于后半部分,可以考虑动态规划求解,以最大值为例子,如果从\(i+1\sim n\)的操作极大值为\(eh[i+1]\),那么显然如果第\(i\)个操作为\(+1\),那么\(eh[i]=eh[i+1]+1\)(相当于垫高了后序值),若为\(-1\),则同理\(eh[i]=eh[i+1]-1\),但要注意最大值不可能小于0。(区间最小值同理)
- 答案取\(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\)到各点的路径权值最小值
解析:
- 可以先不管是否为最大值或者最小值,将题目抽象为,路径权重之和+某条边的权重-某条边的权重,很明显这个问题的最优解(最小解)也就是加上最小边,减去最大边时
- 假设\(state\)表示状态,用第一位表示路径中是否已减去某条边的权重,第二位表示路径中是否已加上某条边的权重,那么就可以获得多个状态转移方程(假设\(u\)为当前点,\(v\)为可达点)
同时每个转移方程可以类比dijkstra,使用堆优化,寻找当前最短路径,保证每次可以确定一个最终状态
3. 最终答案输出状态3对应的值即可
注意:
- 可以使用\(bfs\)但这样会可能会重复修改某个状态对应的值,运行相对慢一点点
- 由于可能存在最大值和最小值都是本身的情况,在上述做法中,每次是将一个新的点加入时确定他为(删去边、增加边、普通边)三种可能性状态中的一个,但也有可能同时一次性成为两种状态(删去边、增加便),这样的话上述转移方程未能考虑到,需要添加转移方程\(dis[v][state|3]=\min(dis[v][state|3],dis[u][state]+w)\quad state\&3==0\),或者考虑到这样的情况下必然是路径上的权值相同,且在通过点数超过一个点的情况下,遇到第二个新点时也能将路径需要的状态补全,因此只有一个点的情况下无法进行转移,所以可以在读入到与\(1\)直接相连的点时,直接将\(dis[u][3]\)赋上\(w\)
- 权值需要开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]);
}