【Codeforces】【Div2】1067 (cf 2158)
A
读题即出答案
B
题意:有一个长为 \(2n\) 的序列,序列中的数在 \([1,2n]\) 之间。同时 \(f(a)\) 代表一个序列 \(a\) 中出现次数为奇数的不同的数的个数,现在你需要将这个序列分为两个长度都为 \(n\) 的序列 \(p\),\(q\),现在请你给出 \(f(p)+f(q)\) 的最大值。
题解:鉴定为ABCD最难的一题,卡了我整整50min才出。具体的策略是对每个数的出现次数 \(\%\) 4,如果模后的数为奇数,那么它的贡献只能为 1;如果为 2,那么它可以对两个序列分别分配奇数个数,贡献为 2;如果为 0,那么它想要贡献为 2,则必须两边都分配奇数,否则贡献只能为0。因为两个序列的长度为 \(n\),所以不能随意分配。具体地,如果模为 0 的数共有偶数个,那么肯定将所有数配对,每一对可以互补,如果有奇数个,那么将这些数配对,还剩下最后一个数,如果前面有模 4 为奇数的数,那么就可以用这个数使得最后这个数贡献为 2,否则其贡献只能为 0。具体详细证明请移步 codeforces 题解。
C
题意:有两个人在一个长度为 \(n\) 的序列 \(a\) 上进行 \(k\) 轮的游戏,先手可以任选一个 \(a_i\) 将 \(a_i\) 加上 \(b_i\) ,后手可以任选一个数 \(a_i\) 将其减上 \(b_i\) ,序列 \(b\) 也是一个长度为 \(n\) 的序列。先手期望最后这个序列的子区间的最大和尽可能地大,后手要将其尽可能地变小,问 \(k\) 轮游戏后序列 \(a\) 的子区间的最大和。(注意每人的一次操作视为经过一轮,即先手只在奇数轮操作,后手只在偶数轮操作)
题解:最直观的想法就是偶数轮情况下先手和后手的操作不能改变这个序列,在奇数轮的情况下,可以看成只有先手进行了一轮操作,然后这个题就 AC 了。接下来给出证明。
假设 \(k\) 为偶数。证明:(1)先手不能将原来的值变的更大。很好证明,只要后手每次选择先手加的那个数,将其加的 \(b_i\) 再减去即可。(2)后手不能将原来的值变得更小。假设先手操作的数的位置是 \(j\) ,操作后序列 \(a\) 子区间和最大的区间为 \([l,r]\) 。因为后手如果想要将值变劣,那么他必须选 \([l,r]\) 中的位置。同时最优的操作是选这些位置中 \(b_i\) 最大的,假设 \(j\) 位于 \([l,r]\) 中,那么先手也必然选择 \([l,r]\) 中 \(b_i\) 最大的位置,因为这样才能将值变得更优,此时后手只是将先手加上的数减回去,不能将值变得更劣。假设 \(j\) 不在 \([l,r]\) 中,那么很容易得知先手的操作是没有意义的,因为他没有将数变得更优也可能使得后手操作后将值变得更劣,所以先手应该选择 \([l,r]\) 中 \(b_i\) 最大的位置进行操作,之后流程同上。所以后手不能将原来的值变得更小。证明在偶数轮下先后手不能将这个序列的值进行改变。如果 \(k\) 为奇数,那么 \(k-1\) 为偶数,在 \(k-1\) 轮后,序列 \(a\) 没有改变,所以此时先手可以选择一个位置加上 \(b_i\) ,使得答案最大。
考虑如何求出这个值,维护两个数组 \(pre\) 和 \(aft\) ,分别表示序列 \(a\) 的前缀和和后缀和。对于一个位置 \(i\) ,其包含该位置的最大子区间和为 \(\sum a_i-min\{pre_1,pre_2,...,pre_{i-1}\}-min\{aft_{i+1},aft_{i+2},...,aft_{n}\}+b_i*[k\%2=1]\) 然后可以在两个数组上二分或用堆维护即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,INF=2e18;
ll T,n,k;
void solve(){
cin>>n>>k;
vector<ll> a(n+1),b(n+1),pre(n+2),aft(n+2),rs(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
for(int i=n;i>=1;i--) aft[i]=aft[i+1]+a[i];
priority_queue<ll,vector<ll>,greater<ll>> pq1,pq2;
pq1.push(pre[0]);
for(int i=1;i<=n;i++){
rs[i]-=pq1.top();
pq1.push(pre[i]);
}
pq2.push(aft[n+1]);
for(int i=n;i>=1;i--){
rs[i]-=pq2.top();
pq2.push(aft[i]);
}
ll s=0;
for(int i=1;i<=n;i++) s+=a[i];
if(k%2==0){
ll res=-INF;
for(int i=1;i<=n;i++){
res=max(res,rs[i]+s);
}
cout<<res<<endl;
}
else{
ll res=-INF;
for(int i=1;i<=n;i++){
res=max(res,rs[i]+s+b[i]);
}
cout<<res<<endl;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
D
题意:你有两个01字符串 \(s\) 和 \(t\) 。你可以在 \(s\) 上进行一种操作,选择一个区间 \([l,r]\) \((1\le l<r \le n)\) 并且要保证 \(s[l,r]\) 是一个回文串,然后将 \(s[l,r]\) 的01进行翻转。你最多有 \(2n\) 次操作机会,将 \(s\) 变成 \(t\) 并给出操作过程,或输出 \(-1\) 代表没有一种操作过程使得 \(s\) 变为 \(t\) 。\(4\le n \le 100\) 且 \(\sum n \le 5 \cdot 10^5\)。
题解:诈骗题,在 \(n \ge 4\) 的情况下没有无解情况,且时间复杂度是 \(O(n)\) 的。考虑 \(s\) 串有一对相邻的 0 或 1,显而易见,全 0 或全 1 的字符串为回文串。那么通过这对相邻的 0 或 1 ,我们可以将整个字符串 \(s\) 变为一个全 0 或全 1 的串。方法为假设 \(s[l,r]\) 是一个全 0 或 全 1 的子区间,且 \(s[r+1]\) 的字符与 \(s[l,r]\) 不同,那我们即可通过操作 \([l,r]\) 使得 \(s[l,r]\) 翻转使得 \(s[l,r+1]\) 为一个全 0 或全 1 的子区间。这个过程最多需要 \(n-1\) 次操作。这时我们得到一个全 0 或全 1 的字符串,假设 \(t\) 也有一对相邻的 0 或 1 ,那么我们就可以以这个位置为起点进行上述操作的逆操作,将这个全 0 或全 1 的串变为字符串 \(t\) ,这个过程最多需要 \(n-1\) 次操作。现在我们还剩两种情况,分别是 \(s\) 没有相邻的 0 或 1,\(t\) 没有相邻的 0 或 1。如果 \(s\) 没有相邻的 0 或 1,那么 \(s\) 必然是 101010101... 或 010101010... 。我们发现 101 和 010 是回文串(废话),所以我们能选操作 \([1,3]\) 将 \(s[1,3]\) 翻转,然后我们就能得到 010010101... 或 101101010... ,然后我们就得到了一对相邻的 0 或 1。对于 \(t\) ,我们可以将字符串还原成 010010101... 或 101101010... ,然后再来个操作 \([1,3]\) 得到 101010101... 或 010101010... 。这是在原来的基础上多加了 2 次操作,总共最多 \(n-1+n-1+2=2n\) 次操作。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,INF=2e18;
ll T,n,m;
vector<PLL> res;
ll get_s(ll xl,ll st,string s){
ll l=xl-1;
for(int i=xl+1;i<=n;i++){
ll c=s[i]-'0';
if(c!=st){
st=1-st;
res.push_back({l,i-1});
}
}
ll r=n;
for(int i=xl-2;i>=1;i--){
ll c=s[i]-'0';
if(c!=st){
st=1-st;
res.push_back({i+1,r});
}
}
return st;
}
void get_t(ll xl,ll st,string t){
ll l=xl-1;
for(int i=n;i>=xl+1;i--){
ll c=t[i]-'0';
if(c!=st){
st=1-st;
res.push_back({1,i});
}
}
ll r=xl;
for(int i=1;i<xl-1;i++){
ll c=t[i]-'0';
if(c!=st){
st=1-st;
res.push_back({i,r});
}
}
if((t[xl]-'0')!=st){
st=1-st;
res.push_back({xl-1,xl});
}
}
void solve(){
cin>>n;
string s,t;
cin>>s>>t;
ll xl=0;
s="?"+s,t="?"+t;
for(int i=2;i<=n;i++){
if(s[i-1]==s[i]){
xl=i;
break;
}
}
ll st=s[xl]-'0';
if(xl!=0){
st=get_s(xl,st,s);
}
else{
res.push_back({1,3});
for(int i=1;i<=3;i++){
if(s[i]=='0') s[i]='1';
else s[i]='0';
}
xl=4;
st=s[xl]-'0';
st=get_s(xl,st,s);
}
xl=0;
for(int i=2;i<=n;i++){
if(t[i-1]==t[i]){
xl=i;
break;
}
}
if(xl!=0){
get_t(xl,st,t);
}
else{
for(int i=2;i<=4;i++){
if(t[i]=='0') t[i]='1';
else t[i]='0';
}
xl=2;
get_t(xl,st,t);
res.push_back({2,4});
}
cout<<res.size()<<endl;
for(auto t:res) cout<<t.first<<" "<<t.second<<endl;
res.clear();
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
E
题意:有一个 \(n*m\) 的棋盘,每个格子有自己的值 \(a_{i,j}\) ,你可以选择 \(k\) 个点,使得这 \(k\) 个点成为特殊点。特别地,如果有一个格子满足它的相邻点(此处相邻的定义为四方向)是特殊点且这个各自的值大于等于特殊点的值,那么它也会被感染成特殊点。对于给定的棋盘找到最小的 \(k\) 值。且给你 \(q\) 个询问,每个询问给定三个值 \(r,c,x\) ,代表对格子 \((r,c)\) 的值减去 \(x\) ,题目保证每个格子的值不会减到 0 及以下。对于每个询问后,给出最小 \(k\) 值。询问改变的棋盘是持久的。数据范围: \(n*m\le 2\cdot 10^5\) ,\(0\le q\le 2 \cdot 10^5\) ,\(1 \le a_{i,j} \le 10 ^9\)。
题解:赛时没时间做了,之后看了看不会,看了题解有一个关键观察。假设这个棋盘的每个值都是不同的,那么特殊点的最小取法就是将每个自己的值都严格小于其邻居的值的格子都取上,最小值 \(k\) 即为这些格子的数量。
证明:如果一个格子其邻居中有比自己小的值,那么它一定可以被该邻居感染;如果不存在邻居小于自己的值的情况,那么这个格子必须被选上,否则一定不会被感染。把所有不存在邻居小于自己的格子选上,那么剩下的都是至少有一个邻居比自己小的格子。假设这些剩下的格子中没有被完全感染,取其中没有感染的一个格子为 \(a\) ,其至少有一个邻居 \(b\) 小于 \(a\) ,且邻居 \(b\) 也不能被感染,否则 \(b\) 就可以感染 \(a\) 。重复上述流程,只有 \(b\) 是自己严格小于邻居时,这个流程才会停止,否则将会无限递归下去。而严格小于邻居的格子都被我们选取了,所以反过来,\(a\) 也一定会被感染。证明剩下的格子一定被完全感染了。
如果棋盘的值可以相等,那么很容易想到只要把所有相等且相连的块合成一个块即可,可以用并查集维护。剩下的操作即是合并相邻值相等的块,维护每个块的最小邻居值。对于每个询问,因为每个询问只会改变一个块,且这个块只会影响自己和邻居的答案,所以我们可以将修改的块及其邻居从整个棋盘中扣出,然后先处理这几个块,然后再把这几个块加入棋盘求出答案即可。时间复杂度为 \(O(nm+q)\)
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define LL __int128
#define PLL pair<ll,ll>
const ll N=4e5+10,INF=2e18;
ll T,n,m;
ll dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
ll p[N],idx;
ll mnnei[N];
ll get_id(ll x,ll y){
return (x-1)*m+y;
}
void init_p(ll x){
idx++;
p[idx]=idx,mnnei[idx]=INF;
}
ll find(ll x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void merge(ll a,ll b){
a=find(a),b=find(b);
if(a!=b){
p[b]=a;
mnnei[a]=min(mnnei[a],mnnei[b]);
find(b);
}
}
vector<array<ll,2>> nei(ll x,ll y){
vector<array<ll,2>> ne;
for(int i=0;i<4;i++){
ll X=x+dx[i],Y=y+dy[i];
if(1<=X&&X<=n&&1<=Y&&Y<=m){
ne.push_back({X,Y});
}
}
return ne;
}
void solve(){
cin>>n>>m;
vector<vector<ll>> f(n+1,vector<ll>(m+1)),ls(n+1,vector<ll>(m+1));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>f[i][j];
init_p(f[i][j]);
ls[i][j]=idx;
}
}
unordered_set<ll> s;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(auto [x,y]:nei(i,j)){
mnnei[find(ls[i][j])]=min(mnnei[find(ls[i][j])],f[x][y]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(auto [x,y]:nei(i,j)){
if(f[i][j]==f[x][y]){
merge(ls[i][j],ls[x][y]);
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(f[i][j]<=mnnei[find(ls[i][j])]){
s.insert(find(ls[i][j]));
}
}
}
cout<<s.size()<<endl;
ll q;
cin>>q;
for(int i=1;i<=q;i++){
ll r,c,x;
cin>>r>>c>>x;
x=f[r][c]-x;
for(auto [x,y]:nei(r,c)){
s.erase(find(ls[x][y]));
}
s.erase(find(ls[r][c]));
init_p(x);
ls[r][c]=idx;
f[r][c]=x;
for(auto [_x,_y]:nei(r,c)){
mnnei[idx]=min(mnnei[idx],f[_x][_y]);
mnnei[find(ls[_x][_y])]=min(mnnei[find(ls[_x][_y])],x);
}
for(auto [_x,_y]:nei(r,c)){
if(x==f[_x][_y]){
merge(find(ls[r][c]),find(ls[_x][_y]));
}
}
for(auto [_x,_y]:nei(r,c)){
if(f[_x][_y]<=mnnei[find(ls[_x][_y])]){
s.insert(find(ls[_x][_y]));
}
}
if(f[r][c]<=mnnei[find(ls[r][c])]){
s.insert(find(ls[r][c]));
}
cout<<s.size()<<endl;
}
idx=0;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
F1
题意:给定一个 \(n\) ,你需要构造一个序列,满足 \(gcd(a_i,a_{i+1})\) \((1\le i < n)\) 都不相同,要求这个序列的值的集合的大小最小。\(1\le a_i \le 10^{18}\) ,easy version 保证 \(2\le n \le 700\) ,hard version 保证 \(2\le n \le 5000\) 。
题解:赛后给林神说了下题意,然后林神就把 F1 秒了。一个重要的观察是,序列中不重复的数的数量至少是 \(\sqrt{n}\) 级别的。显然,如果小于 \(\sqrt{n}\) 级别,那么这个序列就必然会出现 \(a_i=a_j,a_{i+1}=a_{j+1}\) 的情况,或者两者顺序互换,所以一定不能满足 \(gcd(a_i,a_{i+1})\) 互不相同。考虑 \(a_i=2^p\cdot 3^q\) 的情况,设有一个 \(k\),\(a_1=2^0 \cdot 3^{k},a_2=2^1 \cdot 3^{k-1},...,a_i=2^{i-1} \cdot 3^{k-i+1},...\) ,我们发现这样构造时,对于不同的 \((i,j)\) 有 \(gcd(a_i,a_j)\) 两两不同,这里不证明(因为很显然)。然后我们发现 \(3^{37}\) 是不超过 \(10^{18}\) 的最大值,也就是说 \(k\) 最大取 37,可以造出的不同 \(gcd\) 值的有 \(38 * 37/2=704\) 刚好大于 700。那么答案就很显然了,现在我们就要构造出一种方案保证能取到最大可能值。
我们将 \(gcd(a_i,a_j)\) 看成 \((i,j)\) 之间连了一条边,问题就转换成在完全图上找欧拉路径。当 \(k+1\) 为奇数时,每个点都有偶数度数,那么这个图就有一个欧拉回路,随便选一个点找欧拉回路即可。当 \(k+1\) 为偶数时,每个点都有奇数度数,此时这个图没有欧拉路径,我们需要在这个图上删去最少的边使得这个图有欧拉路径。显然删除一条边最多可以将两个点的度数减1,所以我们需要至少删除 \((k+1)/2-1\) 条边,使得这个图有欧拉路径。一种删除方案是,删除边 \((i,i+1)\) \((i \ne 1 \&\& i\%2=1)\) 。然后对于每个 \(n\) 枚举所有 \(k\) ,然后找到最小的能构造出序列长度大于等于 \(n\) 的 \(k\) ,然后取这个序列的前 \(n\) 个数即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=1e3+10,mod=998244353,INF=1e18;
ll T,n,m;
ll ct[50];
stack<ll> stk;
vector<vector<ll>> res(1);
ll h[N],e[N*2],ne[N*2],idx;
ll st[N*2];
ll p2[100],p3[100];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!st[i]&&!st[i^1]){
st[i]=st[i^1]=1;
dfs(j);
}
}
stk.push(u);
}
void init(){
ct[1]=2;
ct[2]=4;
for(int i=3;i<50;i++){
if(i%2==1){
ll bs=i*(i+1)/2;
ct[i]=bs+1;
}
else{
ll bs=i*(i+1)/2-(i/2-1);
ct[i]=bs+1;
}
}
res.push_back({1,1});
res.push_back({1,1,2,2});
for(int i=3;ct[i]<=710;i++){
// cout<<i<<endl;
memset(h,-1,sizeof h);
idx=0;
ll n=i;
if(i%2==0){
for(int j=1;j<=n;j++){
for(int k=j;k<=n;k++){
if(k==j+1&&j%2==1) continue;
add(j,k),add(k,j);
}
}
add(1,2),add(2,1);
}
else{
for(int j=1;j<=n;j++){
for(int k=j;k<=n;k++){
add(j,k),add(k,j);
}
}
}
if(i==3){
ll s=1;
}
dfs(1);
// cout<<stk.size()<<endl;
vector<ll> a;
while(stk.size()){
a.push_back(stk.top());
stk.pop();
}
res.push_back(a);
for(int i=0;i<=idx;i++) st[i]=0;
}
p2[0]=p3[0]=1;
for(int i=1;i<=50;i++){
p2[i]=p2[i-1]*2LL;
p3[i]=p3[i-1]*3LL;
}
}
void solve(){
cin>>n;
ll d=0;
for(int i=1;i<=n;i++){
if(n<=ct[i]){
d=i;
break;
}
}
vector<ll> a;
for(int i=0;i<n;i++){
a.push_back(res[d][i]);
}
for(int i=0;i<n;i++){
ll t=d-1;
ll p2p=a[i]-1,p3p=t-p2p;
a[i]=p2[p2p]*p3[p3p];
}
for(auto t:a) cout<<t<<" ";
cout<<endl;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
init();
while(T--){
solve();
}
return 0;
}

浙公网安备 33010602011771号