Educational Codeforces Round 169 (Rated for Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/2004

又是手速场,最后把 F 口了没时间写了。小号又能上紫了爽,要是手快点还能更爽。

置顶广告:中南大学 ACM 集训队绝赞招新中!

有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!

没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!

到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜

A

签到。

数据范围很小,懒得多想了于是直接枚举插入点的位置检查。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n; std::cin >> n;
    std::vector<int> a(n + 1), b;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      if (i >= 2) b.push_back(a[i] - a[i - 1]);
    }
    // std::sort(b.begin(), b.end());

    int fl = 0;
    for (int i = -100; i <= 200; ++ i) {
      int maxd = 0;
      for (int j = 1; j <= n; ++ j) {
        maxd = std::max(maxd, abs(i - a[j]));
      }
      int flag = 1;
      for (auto d: b) 
        if (maxd >= d) flag = 0;
      if (flag == 1) {
        fl = 1;
        break;
      }
    }
    std::cout << (fl ? "YES\n" : "NO\n");
  }
  return 0;
}

B

结论,特判。

恶心特判题,我写了一坨。

特别注意端点重合的情况!

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int l, r, L, R, ans; std::cin >> l >> r >> L >> R;
    if (l > R || L > r) {
      ans = 1;
    } else if (l == L && r == R) {
      ans = R - L; 
    } else if (l <= L && R <= r && (l == L || R == r)) {
      ans = R - L + 1;
    } else if (L <= l && r <= R && (l == L || R == r)) {
      ans = r - l + 1;
    } else if (l < L && R < r) {
      ans = R - L + 2;
    } else if (L < l && r < R) {
      ans = r - l + 2;
    } else if (l <= L) {
      ans = r - L + 2;
    } else  {
      ans = R - l + 2;
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
1 2
2 3
*/

C

贪心,排序

先不考虑风灵月影宗师 Bob 企图开挂的行为,发现在一局游戏中两人一定是将 \(a\) 按照权值递减排序,并交替地选择此时的最大值。

然后考虑 Bob 的修改。显然他应当保证他修改后增加的权值一定全部被自己拿到,则应当保证修改后各个权值降序排序后相对位置不发生改变。显然此时他的最优操作是对于降序排序后的数列 \(a_1\sim a_n\),对于他可以取到的所有 \(i\bmod 2 = 0\) 的位置 \(a_i\),都尝试将每个 \(a_{i}\) 修改为 \(a_{i - 1}\)。于是直接模拟即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n, k; std::cin >> n >> k;
    LL ans = 0;
    
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) std::cin >> a[i];
    std::sort(a.begin(), a.end(), std::greater<int>());
    for (int i = 0; i < n; ++ i) {
      if (i % 2 == 0) ans += a[i];
      else {
        int delta = std::min(k, a[i - 1] - a[i]);
        a[i] += delta, k -= delta;
        ans -= a[i];
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

D

枚举,结论

发现在大多数情况下给定的询问 \(x, y\) 均可以直达,此时的代价即为 \(|x - y|\)

无法直达的情况 \(x, y\) 所在城市的种类只有三种:BGRYBRGYBYGR。容易发现对于这三种情况,仅需找到任意一个类型与它们均不同的城市 \(p\),就可以作为中转点使得 \(x, y\) 可互相到达,代价即为 \(|x - p| + |y - p|\)

若中转城市恰好位于 \([x, y]\) 中则代价即为 \(|x - y|\),否则仅需检查距离 \(x, y\) 最近的中转城市即可。于是考虑按照位置升序维护每种城市的位置,对于每次询问先判断 \(x, y\) 是否可以直达,若无法直达则枚举中转城市的类型,检查 \([x + 1, y - 1]\) 中有无中转城市,若没有则在 \([1, x - 1], [y + 1, n]\) 中二分找到最优的中转城市即可。

总时间复杂度 \(O(n + q\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kInf = 1e9;
//=============================================================
int n, q, t[kN], cnt[7][kN];
std::map<std::string, int> type;
std::vector<int> pos[7];
//=============================================================
int oppo(int x_) {
  return (x_ <= 3 ? x_ + 3 : x_ - 3);
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  //BG, BR, BY, GR, GY, or RY
  type["BG"] = 1;
  type["BR"] = 2;
  type["BY"] = 3;
  
  type["RY"] = 4;
  type["GY"] = 5;
  type["GR"] = 6;

  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> q;
    for (int i = 1; i <= 6; ++ i) pos[i].clear();
    for (int i = 1; i <= n; ++ i) {
      std::string s; std::cin >> s;
      t[i] = type[s];
      pos[t[i]].push_back(i);
      for (int j = 1; j <= 6; ++ j) cnt[j][i] = cnt[j][i - 1];
      cnt[t[i]][i] ++;
    }
    while (q --) {
      int x, y; std::cin >> x >> y;
      if (x > y) std::swap(x, y);
      if (oppo(t[x]) != t[y]) {
        std::cout << abs(x - y) << "\n";
        continue;
      }
      int ans = kInf;
      for (int i = 1; i <= 6; ++ i) {
        if (i == t[x] || i == t[y]) continue;
        if (cnt[i][y - 1] - cnt[i][x] != 0) ans = y - x;
        if (cnt[i][x - 1]) {
          int p = std::lower_bound(pos[i].begin(), pos[i].end(), x) - pos[i].begin();
          -- p;
          ans = std::min(ans, x - pos[i][p] + y - pos[i][p]);
        }
        if (cnt[i][n] - cnt[i][y]) {
          int p = std::lower_bound(pos[i].begin(), pos[i].end(), y) - pos[i].begin();
          ans = std::min(ans, pos[i][p] - y + pos[i][p] - x);
        }
      }
      if (ans >= kInf) ans = -1;
      std::cout << ans << "\n";
    }
  }
  return 0;
}

E

博弈论,SG 函数

比较裸的 SG 函数问题,妈的标题不是 Not a Nim Problem 吗。

手玩下发现,对于单堆石子当且仅当个数 \(x\) 为奇数时有先手必胜策略,此时仅需一步取走 \(x - 2\) 个石子即可。然后考虑通过对 \(1\sim a_i\) 求 SG 函数拓展到多堆石子即可。

直接大力枚举转移求 SG 函数显然不行,互质对的数量级会很大。但是发现这个数据范围非常像是给线性筛跑的,于是考虑寻找每个值与其最小质因子的关系。手玩下发现:

  • \(\operatorname{SG}(0) = 0\)\(\operatorname{SG}(1) = \operatorname{mex}\{0\} = 1\)
  • 对于所有偶数 \(x\)\(\operatorname{SG}(x) = 0\)
  • 对于所有大于 2 的质数 \(p\),有 \(\forall 1\le i<p, \gcd(p, i) = 1\),则 \(\operatorname{SG}(p) = \operatorname{mex}\{\operatorname{SG}(1), \operatorname{SG}(2), \cdots, \operatorname{SG}(p-1)\} = \operatorname{rank}(p)\),其中 \(\operatorname{rank}(p)\) 代表 \(p\)所有质数(包括 2)中的排名。
  • 对于所有奇数 \(x\),设其最小质因子为 \(p\),发现有 \(\gcd(x, x - p) \not= 0\),且 \(\forall 1\le y< p, \gcd(i, x) = 1\)。则 \(x\) 第一个不能转移到的位置是 \(p\)。通过观察转移对象可知有 \(\operatorname{SG}(x) = \operatorname{SG}(p)\)

上述规律赛时是大力手玩到 10 发现的,如果打表的话可以更快发现规律。

于是仅需通过线性筛,对于每个数求得其最小质因子,即可求得所有 \(\operatorname{SG}\) 函数,则仅需判断 \(\operatorname{SG}(a_1)\oplus\cdots \oplus \operatorname{SG}(a_n) \not= 0\) 是否成立即可。

总时间复杂度 \(O\left(v + \sum n\right)\) 级别。

//
/*
By:Luckyblock

https://www.luogu.com.cn/problem/P3383
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e7 +10;
//=============================================================
int n, q;
bool vis[kN];
int pnum, p[kN], sg[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
void Getprime() {
  n = 1e7;
  sg[0] = 0, sg[1] = 1;
  for (int i = 2; i <= n; ++ i) {
    if (i % 2 == 0) sg[i] = 0;
    if (!vis[i]) {
      p[++ pnum] = i, sg[i] = pnum;
      if (i == 2) sg[i] = 0;
    }
    for (int j = 1; j <= pnum; ++ j) {
      if (i * p[j] > n) break;
      vis[i * p[j]] = true;
      if (i * p[j] % 2 == 1) sg[i * p[j]] = sg[p[j]];
      if (i % p[j] == 0) break;
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  Getprime();

  int T = read();
  while (T --) {
    n = read();
    int sum = 0;
    for (int i = 1; i <= n; ++ i) {
      int a = read();
      sum ^= sg[a];
    }
    std::cout << (sum == 0 ? "Bob" : "Alice") << "\n";
  }
  
  return 0;
}

F

结论,模型转化,枚举

最后还剩二十分钟本来想下班了,但是看到群友发的玛丽涩图之后灵光一闪就会了,以后应当在赛时就多看玛丽涩图!

首先对于某个区间 \([l, r]\),操作次数上界即合并为同一个数,次数为 \(r-l\)

如果从回文串等价于两端对应位置相等的角度考虑——会发现题目给定的操作既会影响串的长度又会影响串的元素的值,这很几把麻烦,所以要换个判定回文串的思路:

某个串是回文串的另一个充要条件是:该串从前往后遍历与从后往前遍历对应位置完全相同,这又等价于该串的前缀和与后缀和对应位置完全相同。由于前后缀和一定是单调递增的,发现此时整个值域被前后缀和划分成了若干子区间。对于给定的两种操作:

  • 操作 1 将某两个位置合并,可看做选择某个位置 \(i\),并将该位置对应权值 \(v\) 前缀和左侧区间,与后缀和右侧区间的一个权值删除。
  • 操作 2 将某个位置分裂,可以看做选择某个位置 \(i\),在该位置对应权值 \(v\) 前缀和右侧区间,与后缀和左侧区间里分别加一个值并满足 \(x+y=b_i\) 的限制。

例如:

IMG_20240816_014638_compressed.jpg

发现此时通过操作将区间转化为回文串,即通过上述增删权值的操作,使得前后缀和对应位置完全相同。手玩下发现此时需要的次数实际上即操作次数上界,减去未操作时,前后缀和里相等的权值的个数。

于是考虑先记答案为所有区间操作次数的上界 \(\frac{(n+1)n(n-1)}{6}\),然后考虑减去每个区间的前后缀和中权值相等的个数即可。此时可以通过维护每种权值出现的次数,分别考虑前缀和与后缀和即可。具体实现详见以下江队的提交:https://codeforces.com/contest/2004/submission/276668467

总时间复杂度 \(O(n^2\log (n^2))\) 级别。

#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
#define pii std::pair<int,int> 
typedef int LL;
const signed maxn=(signed)2e3+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int ar[maxn],pre[maxn];
int ur[maxn*maxn],tot;
int all[maxn*maxn];
int idx[maxn][maxn];
signed main()
	{	 
		LL T=Read();
		while (T--){
			int n=Read();
			for(int i=1;i<=n;++i) ar[i]=Read();
			pre[0]=0;
			for(int i=1;i<=n;++i) pre[i]=pre[i-1]+ar[i];
			tot=0;
			for(int i=1;i<=n;++i){
				for(int j=i;j<=n;++j){
					ur[++tot]=pre[j]-pre[i-1];
				}
			}
			std::sort(ur+1,ur+1+tot);
			int cnt=std::unique(ur+1,ur+1+tot)-ur-1;
			for(int i=0;i<=cnt;++i) all[i]=0;
			for(int i=1;i<=n;++i){
				for(int j=i;j<=n;++j){
					idx[i][j]=std::lower_bound(ur+1,ur+1+cnt,pre[j]-pre[i-1])-ur;
					++all[idx[i][j]];
					//printf("idx[%d][%d]=%d\n",i,j,idx[i][j]);
				}
			}
			ll sum=1ll*n*(n*n-1)/6;
			//printf("%lld\n",sum);
			for(int i=1;i<=n;++i){
				for(int j=i;j<=n;++j) --all[idx[i][j]];
				for(int j=i;j<=n;++j) sum-=all[idx[i][j]];
				//printf("%lld\n",sum);
			}
			printf("%lld\n",sum);
		}
		return 0;
	}	

写在最后

学到了什么:

  • E:观察 sg 函数的转移形式,或打表大眼观察 sg 函数的规律。
  • F:回文串的多个充要条件。

唉最近忙着准备暑假结训赛没什么时间补题,欠了两场牛客一场 div1+2 没补好爽。

我们人手十分不足的弱校是这样的唉,现在处于一个又想各位牛逼 OI 大神来报考中南大专再度出线 WF 重铸 ACM 辉煌,又不想大家因为各种原因受苦的矛盾心理状态。希望从今年招新开始修补一下吧,希望到时候会有人记得当年有一个叫 Lb 的大傻逼在这里待过。

结尾广告:中南大学 ACM 集训队绝赞招新中!

有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!

没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!

到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜

posted @ 2024-08-16 02:26  Luckyblock  阅读(890)  评论(2编辑  收藏  举报