VP Educational Codeforces Round 166 (Rated for Div. 2)(A~D)

比赛地址

前言

C 赛时模拟觉得很复杂,弃了。赛后想了好一会才想通。

D 一眼会,但发现没想仔细。仔细想想后虽然重构了下代码,但思路还是差不多。可惜的是赛时没a,赛后几分钟a了,,

A

注意一下数字也要非降(wa了一发呜呜呜)

Code

#include<bits/stdc++.h>
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
void solve() {
	int n = read();
	string s;
	cin >> s;
	int now = 0, f = 0, now2 = 0;
	for(int i=0;i<n;++i) {
		if('a'<=s[i] && s[i]<='z') {
			f = 1;
			if((int)s[i] < now) {
				puts("NO"); return ;
			}
			now = max(now, (int)s[i]);
		}
		if('0'<=s[i] && s[i]<='9') {
			if(f) {
				puts("NO"); return ;
			}
			if((int)s[i] < now2) {
				puts("NO"); return;
			}
			now2 = (int)s[i];
		}
	}
	puts("YES");
}
int main()
{
	int T = read();
	while(T--) solve();
	return 0;
}

B

模拟一下样例3即可通过本题。

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N =  2e5+7;
const int INF = 1e9;
int n;
int a[N],b[N];
void solve() {
	n = read();
	for(int i=1;i<=n;++i) a[i] = read();
	for(int i=1;i<=n+1;++i) b[i] = read();
	int ans = 1, f = INF;
	for(int i=1;i<=n;++i) {
		int x = a[i], y = b[i];
		if(x > y) swap(x, y);
		if(x<=b[n+1] && b[n+1]<=y) f = min(f, 0ll);
		f = min(min(f, abs(b[n+1]-y)), abs(b[n+1]-x));
		ans += y - x;
	}
	printf("%lld\n",ans + f);
}
signed main()
{
	int T = read();
	while(T--) solve();
	return 0;
}

C

首先要理解到这个题目的意思其实是说前面有一部分人是有选择权的,后面剩下的人无选择权,都必须选同一个。我们不妨假设后面的人选的是“冷门”,而“热门”是被提前用完的。

考虑钦定最后一个人(令其为\(N=n+m+1\)号)被排除,计算出答案 \(ans\)。那么其他人 \(i\) 就只要考虑:排除 \(i\),加入 \(N\)的影响。

考虑到我们前面将人分类为有选择权无选择权(注意:无选择权的选择的一定是“冷门”),且要注意到 有\无选择权 的人会有一条分解线,在这之前的都是有选择权,而之后的都是无选择权

那么我们来考虑 \(i\)

首先考虑 \(i\)无选择权(因为这个简单),那么 \(i\) 一定会空出一个“冷门”,此时全局不会发生任何变化,\(N\) 选择空出来的这个“冷门”

其次考虑 \(i\)有选择权

  • \(i\)有选择权且当时选择的是“冷门”(“冷门”是 \(i\) 的最优解),则空出一个“冷门”的位置,“热门”依旧“抢手”(指提前用完),对后面无选择权的人没有任何影响,\(N\) 分配到 \(i\) 空出来的这个“冷门”(不严谨但可以这样理解)

  • \(i\)有选择权且当时选择的是“热门”,空出一个“热门”的位置。不得了!如果后面无选择权的人里面有一个是被迫选择“冷门”的话(记作 \(p\)),他就会争取到这个“热门”的名额(来实现自己的最大化);当然,如果没有这个 \(p\) 的出现,那么 \(N\) 只能选择这个“热门”(即使不是 \(N\) 的最优解)

(总之 \(N\) 没有选择,只能接受分配)

具体细节看代码,时间复杂度 \(O(n)\)。当然只要主要到第一句话的性质,可以写一个逻辑更为简单的的 \(O(n \log n)\) 的做法。

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 2e5+7;
int n,m;
int a[N],b[N],f[N],q[N];
void solve() {
	n = read(), m = read();
	for(int i=1;i<=n+m+1;++i) a[i] = read();
	for(int i=1;i<=n+m+1;++i) b[i] = read();
	int N = n+m+1, ans = 0;
	int t = 1, na = 0, nb = 0;
	while(na<n && nb<m) {
		if(a[t] > b[t]) ++na, f[t] = 1;
		else ++nb, f[t] = 2;
		ans += max(a[t], b[t]);
		++t;
	}
	int p = 0;
	for(int i=t;i<N;++i) {
		if(na == n) {
			f[i] = 2; ans += b[i];
		} else {
			f[i] = 1; ans += a[i];
		}
		if((na==n) == (a[i]>b[i]) && !p) p = i;
	}
	q[N] = ans;
	#define hot(x) ((na==n) ? a[x] : b[x])
	#define cold(x) ((na==n) ? b[x] : a[x])
	int getp = hot(p) - cold(p);
	for(int i=1;i<t;++i) {
		if((na==n) == (a[i]>b[i])) {	//i is hot but max
			if(p) {
				q[i] = ans - max(a[i], b[i]) + getp + cold(N);
			} else {
				q[i] = ans - max(a[i], b[i]) + hot(N);
			}
		} else {	//i is cold but max
			q[i] = ans - max(a[i], b[i]) + cold(N);
		}
	}
	for(int i=t;i<N;++i) {
		q[i] = ans - cold(i) + cold(N);
	}
	
	for(int i=1;i<=N;++i) printf("%lld ",q[i]);
	cout << endl;
	
	for(int i=1;i<=N;++i) {
		a[i] = b[i] = f[i] = q[i] = 0;
	}
}
signed main()
{
	int T = read();
	while(T--) solve();
	return 0;
}

D

\('('\) 视作 \(+1\)\(')'\) 视作 \(-1\),再对这个 “\(\pm 1\)序列”做前缀和,得到的数列画成一个函数图像(不太严谨,理解一下)

易发现:合法的括号序列 \(\Leftrightarrow\) 函数图像 \(y>0\) 且起点终点均 \(=0\)

显然取反操作可以视为 \([l,r]\)\(\pm 1\) 全部取相反数,那么原函数图像会发生什么变化呢?

答案是:会以起点 \(l\) 对应的这个高度———\(y=l\) 这条线———为对称轴翻转 \([l,r]\) 的函数图像。很容易我们会想到,只要这个翻转后的图像仍然满足性质“函数图像 \(y>0\) 且起点终点均 \(=0\),那么这个 \([l,r]\) 肯定是合法的。

但同时我们要发现,有可能翻转之后的 \(r\) 这个点的数值 \(f'_r \not = f_r\)\(f_r\) 是原来 \(r\) 这个点的值)。那么这样也是不合法的。(实际上这样就不会满足上面这条性质,自己推一下就知道)。

因此我们发现,\([l,r]\) 合法的第一个条件就是:\(f_l = f_r\);第二个条件就是 \(\max_{j=l}^{r}\{f_j\}<=2f_l\)。满足这两个条件是合法 \([l,r]\)

因此我们可以考虑值域,把相同的值放入一个 vector 处理,ST表快速求区间 \(\max\)。时间复杂度 \(O(n \log n)\)

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 2e5+7;
int n;
int a[N],lg[N];
int f[N][30];
vector<int> num[N];
void RMQ() {	
	for(int i=2;i<=n;++i)
		lg[i] = lg[i>>1] + 1;
	for(int i=1;i<=n;++i)
		f[i][0] = a[i];
	for(int j=1;j<=21;++j)
		for(int i=1;i<=n;++i) if(i+(1<<j)-1 <= n) {
			f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	}
}
int Query(int l,int r) {
	int k = lg[r-l+1];
	return max(f[l][k], f[r-(1<<k)+1][k]);
}
int calc(int x) {
	if(x == 0) return 0;
	return x*(x-1)/2;
}
void solve() {
	string s;
	cin >> s;
	n = s.length();
	for(int i=1;i<=n;++i) a[i] = a[i-1] + (s[i-1]=='(' ? 1 : -1), num[a[i]].push_back(i);
	RMQ();
	int ans = 0;
	for(int i=1;i<=n;++i) {
		if(!num[i].size()) continue;
		int cnt = 1, last = num[i][0];
		for(int j=1;j<(int)num[i].size();++j) {
			int x = num[i][j];
			if(Query(last, x) <= 2*i) {
				++cnt;
			}
			else {
				ans += calc(cnt);
				last = x;
				cnt = 1;
			}
		}
		ans += calc(cnt);
	}
	printf("%lld\n",ans);
	
	for(int i=0;i<=n;++i) num[i].clear();
	for(int i=1;i<=n;++i) {
		a[i] = 0;
		for(int j=0;j<=21;++j) {
			f[i][j] = 0;
		}
	}
}
signed main()
{
	int T = read();
	while(T--) solve();
	return 0;
}
/*
1
()((()))
*/
posted @ 2024-06-27 21:13  基地AI  阅读(15)  评论(0编辑  收藏  举报