【题解】Educational Codeforces Round 148(CF1832)
A.New Palindrome
题目描述:
给你一个由小写字母组成的回文字符串,问你是否能在重排其之后构造出另一个与原串不同的回文字符串。
多测,\(t \le 1000,2 \le |s| \le 50\)
题目分析:
考虑其实就是前 \(\lfloor \frac{n}{2} \rfloor\) 个位置存在两种或以上的不同字符,因为这样直接交换对应的位置就好了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 200;
char s[N];
map<char,bool> mp;
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%s",s+1);
int n;n = strlen(s+1);
int cnt = 0;
for(int i=1; i<=n/2; i++){
if(!mp[s[i]]){
mp[s[i]] = true,++cnt;
}
}
mp.clear();
if(cnt >= 2) printf("YES\n");
else printf("NO\n");
}
return 0;
}
B.Maximum Sum
题目描述:
本题有多组测试数据
给定一个长度为 \(n\) 的数列,其中每个元素互不相同,进行 \(k\) 次操作,每次可以选择删除序列中最小的两个数或最大的一个数。求操作后剩余数的和的最大值。
\(3 \leq n \leq 2 \times 10^{5},1 \leq k \leq 99999,2k \leq n\)。
题目分析:
一个想法就是贪心,每次选择较小的删除。
但是还有一种想法就是,虽然此时最大的数很大,但是我们多删几个最大数然后就可能变的很小,然后就有了一种更优的方案。
所以贪心不行。
发现我们其实直接暴力就好了,因为我们要求必须进行 \(k\) 次操作,所以不妨设进行了 \(i\) 次删最大值的操作和 \(k-i\) 次删除较小两个数的操作,那么如果让 \(i-1 \to i\) 对答案的贡献是很好维护的,所以直接做就可以了。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5+5;
int a[N];
signed main(){
int T;scanf("%lld",&T);
while(T--){
int n,k;scanf("%lld%lld",&n,&k);
int ans = 0;
for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
sort(a+1,a+n+1);
int sum = 0;
for(int i=1; i<=n-k; i++) sum += a[i];
ans = max(ans,sum);
int l = 1,r = n - k + 1;
for(int i=1; i<=k; i++){
sum -= a[l] + a[l+1];
sum += a[r];
l+=2;r += 1;
ans = max(ans,sum);
}
printf("%lld\n",ans);
}
return 0;
}
C.Contrast Value
题目描述:
定义序列 \(a_1,a_2,\dots,a_n\) 的权值是
\(|a_1-a_2|+|a_2-a_3|+\dots+|a_{n-1}-a_n|\)。
\(T\) 次询问,每次给一个序列 \(a\) ,一个 \(a\) 的子序列 \(b\) 合法当且仅当 \(b\) 权值和 \(a\) 相等,求 \(b\) 的最小长度。
\(1 \le \sum n \le 3\cdot 10^5,0 \le a_i \le 10^9\)
题目分析:
肯定是要想办法把绝对值搞没,随便手模一下就会发现对于一个递增或递减的序列 \(a_l,a_{l+1},...,a_r\),其中间部分全部都会被消没,产生的贡献就相当于 \(|a_l - a_r|\)。
所以可以直接找到递增和递减的序列,只保留两边的部分,也就是可以得到最小的 \(b\)。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int a[N];
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
n = unique(a+1,a+n+1) - a - 1;
int ans = 2;
for(int i=2; i<n; i++){
if((a[i+1] > a[i]) != (a[i] > a[i-1])) ++ans;
}
if(n == 1) printf("1\n");
else printf("%d\n",ans);
}
return 0;
}
E.Combinatorics Problem
题目描述:
回顾一下二项式系数 $ \binom{x}{y} $ 的计算方法(其中 $ x $ 和 $ y $ 是非负整数):
- 如果 $ x < y $ ,则 $ \binom{x}{y} = 0 $ ;
- 否则,$ \binom{x}{y} = \frac{x!}{y! \cdot (x-y)!} $ 。
给定一个数组 $ a_1, a_2, \dots, a_n $ 和一个整数 $ k $ ,你需要计算一个新数组 $ b_1, b_2, \dots, b_n $ ,其中
- $ b_1 = (\binom{1}{k} \cdot a_1) \bmod 998244353 $ ;
- $ b_2 = (\binom{2}{k} \cdot a_1 + \binom{1}{k} \cdot a_2) \bmod 998244353 $ ;
- $ b_3 = (\binom{3}{k} \cdot a_1 + \binom{2}{k} \cdot a_2 + \binom{1}{k} \cdot a_3) \bmod 998244353 $ ,依此类推。
具体而言,$ b_i = (\sum\limits_{j=1}^{i} \binom{i - j + 1}{k} \cdot a_j) \bmod 998244353 $ 。
注意,数组以一种修改的方式给出,你也需要以一种修改的方式输出。
输入格式
输入的第一行包含六个整数 $ n $ , $ a_1 $ , $ x $ , $ y $ , $ m $ 和 $ k $ ( $ 1 \le n \le 10^7 $ ; $ 0 \le a_1, x, y < m $ ; $ 2 \le m \le 998244353 $ ; $ 1 \le k \le 5 $ )。
数组 $ [a_1, a_2, \dots, a_n] $ 的生成方式如下:
- 输入给定了 $ a_1 $ ;
- 对于 $ 2 \le i \le n $ , $ a_i = (a_{i-1} \cdot x + y) \bmod m $ 。
输出格式
由于输出最多可能有 $ 10^7 $ 个整数,可能会导致速度过慢,因此你需要进行以下处理:
设 $ c_i = b_i \cdot i $ (不在乘法后取模 $ 998244353 $ )。输出整数 $ c_1 \oplus c_2 \oplus \dots \oplus c_n $ ,其中 $ \oplus $ 表示按位异或运算。
题目分析:
感觉这个题的难点就在于我们会直接根据数据生成和输出结果的格式去考虑发现一些性质,但是其实只要跳出这个思路就简单了。
考虑推式子:
最后一步就是为了尽可能地化成我们熟悉的形式。
设 \(b_{i,j}\) 代表 \(k = j\) 时 \(b_i\) 的值,则有:
可以直接暴力递推,复杂度 \((nk)\)
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7+5;
const int MOD = 998244353;
int a[N],b[N][6];
signed main(){
int n,x,y,m,k;scanf("%lld%lld%lld%lld%lld%lld",&n,&a[1],&x,&y,&m,&k);
for(int i=2; i<=n; i++) a[i] = (a[i-1] * x + y)%m;
for(int i=1; i<=n; i++) b[i][0] = (b[i-1][0] + a[i])%MOD;
for(int j=1; j<=k; j++){
for(int i=1; i<=n; i++){
b[i][j] = (b[i-1][j] + b[i-1][j-1] + ((j == 1) ? a[i] : 0))%MOD;
//注意三目运算符优先级啊!!!!!
}
}
int ans = 0;
for(int i=1; i<=n; i++) ans ^= (b[i][k] * i);
printf("%lld\n",ans);
return 0;
}
F.Zombies
题目描述:
给定 \(n\) 个左闭右开区间 \(I_i = [l_i, r_i)\)(\(1\leq i\leq n\))。
找出 \(k\) 个左闭右开区间 \(J_j = [a_j, b_j)\)(\(1\leq j\leq k\))满足 \(a_j + m = b_j\) 且 \(0\leq a_j < b_j \leq x\),以及一个长度为 \(n\) 的数组 \(p_i\in [1, k]\),最大化 \(\sum_{i = 1} ^ n |[0, x) \backslash (I_i\cup J_{p_i})|\)。
你只需求出最大化的值,不需要给出构造方案。
区间 \([l, r)\) 的长度为 \(r - l\),即 \(|[l, r)| = r - l\)。
\(1\leq k\leq n\leq 2000\),\(1\leq m\leq x \leq 10 ^ 9\),\(0\leq l_i < r_i \leq x\)。
题目分析:
题意要求让我们最小化区间并,但是并显然不如交好做,所以可以转化为最大化区间交。
考虑如果我们已经给定了 \(k\) 个区间 \(J\),怎么找到两两匹配的最大值呢。显然,因为 \(J\) 的长度都一样,所以我们要尽可能让区间的中点接近,这样交才会更大。
至此我们发现了一个关键的性质,也就是说将 \(I\) 按区间中点排序之后,对于每一个匹配 \(J_i\) 最优的区间 \(I_j\) 满足排序后连续,因为这样做可以让中点尽可能接近。
所以这个东西就有了一种 \(dp\) 的感觉,也就是可以设 \(f[i][j]\) 表示 \(I\) 中前 \(i\) 个区间匹配了 \(j\) 个区间 \(J\) 的最优答案,\(g[i][j]\) 表示若将 \(I_i,I_{i+1},...,I_{j}\) 匹配同一个 \(J\) 的情况下的最优贡献是多少。
转移就显然是:
首先考虑 \(g\) 数组怎么求:
如果直接枚举区间的话(其实就是枚举区间左端点)复杂度为 \(O(x)\),显然就寄了,考虑能不能减少一点区间的数量。
发现如果区间 \([l,r]\) 使得 \(l\) 不为任意一个 \(I\) 的左端点,且 \(r\) 不为任意一个 \(I\) 的右端点,那么将这个区间移动到端点上一定更优,因为我们求的是区间交,所以通过这个我们就可以将区间数优化到 \(O(n)\)。
直接对于每一个 \(g\) 暴力枚举哪一个区间最优的复杂度为 \(O(n^3)\),但是我们发现设 \(g[i][j]\) 的最优决策点为 \(t[i][j]\) 则必然满足 \(t[i-1][j] \le t[i][j] \le t[i][j+1]\),因为显然越靠近对应区间越优,所以将决策单调性用上复杂度就是 \(O(n^2)\)。
然后就是 \(f\) 该怎么快速转移。
一个想法就是我们要求恰好选择 \(k\) 个,显然考虑是否满足凸性,然后打表会发现的确满足,所以可以直接 wqs 二分,复杂度 \(O(n^2\log n)\)。
另一个想法看这个 \(dp\) 的转移方程就很决策单调性,观察得 \(g\) 满足四边形不等式,所以 \(f\) 满足决策单调性,复杂度 \(O(n^2)\)。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
using namespace std;
const int N = 2005;
const int INF = 1e18;
int n,k,x,m,g[N][N],val[N*2][N],opt[N][N],sz;
vector<int> L;
PII f[N];
struct node{
int l,r;
}a[N];
bool cmp(node a,node b){
return a.l + a.r < b.l + b.r;
}
int get_val(node a,node b){ //获得区间交
int l = max(a.l,b.l);
int r = min(a.r,b.r);
if(l <= r) return r - l;
return 0;
}
void get_g(){
for(int i=1; i<=sz; i++){ //考虑候选区间 i 对 A[i,j] 这些区间产生的贡献是多少
for(int j=1; j<=n; j++){
val[i][j] = val[i][j-1] + get_val((node){L[i],L[i]+m},a[j]);
// printf("%lld ",val[i][j]);
}
// printf("\n");
}
for(int len=n; len>=1; len--){
for(int l=1; l + len - 1<=n; l++){ //处理 g[l][r] 的答案
int r = l + len - 1;
int nl = l == 1 ? 1 : opt[l-1][r];
int nr = r == n ? sz : opt[l][r+1];
g[l][r] = -INF;
for(int j=nl; j<=nr; j++){
if(g[l][r] < val[j][r] - val[j][l-1]){
g[l][r] = val[j][r] - val[j][l-1];
opt[l][r] = j;
}
}
}
}
// for(int i=1; i<=n; i++){
// for(int j=i; j<=n; j++){
// printf("%lld ",g[i][j]);
// }
// printf("\n");
// }
}
PII get_f(int x){ //最优答案,以及最优答案下的最小区间数
for(int i=1; i<=n; i++){
f[i] = {g[1][i] - x,-1};
for(int j=1; j<i; j++){
f[i] = max(f[i],{f[j].first - x + g[j+1][i],f[j].second - 1});
}
}
return {f[n].first,-f[n].second};
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%lld%lld%lld%lld",&n,&k,&x,&m);
int tmp = x * n - m * n; //统计问题转化过程中的不被计算的贡献
for(int i=1; i<=n; i++){
scanf("%lld%lld",&a[i].l,&a[i].r);
tmp -= (a[i].r - a[i].l);
L.push_back(a[i].l + m > x ? x-m : a[i].l); //得到候选的区间的左端点
L.push_back(a[i].r - m < 0 ? 0 : a[i].r - m);
}
sort(L.begin(),L.end());
L.resize(unique(L.begin(),L.end())-L.begin());
sz = L.size();
L.insert(L.begin(),0); //这样可以非常方便地确定循环边界
// for(auto i : L) printf("%d ",i);
// printf("\n");
sort(a+1,a+n+1,cmp);
get_g(); //预处理 g 数组
int res1 = 0,res2 = 0;
int l = -1e9,r = 1e9; //wqs 二分(边界别开太大会炸)
while(l <= r){
int mid = (l + r) >> 1;
PII ans = get_f(mid);
if(ans.second <= k){
res1 = ans.first,res2 = mid,r = mid-1;
}
else l = mid + 1;
}
printf("%lld\n",res1 + tmp + res2 * k);
return 0;
}