Codeforces Round #782 (Div. 2)

Codeforces Round #782 (Div. 2)

A

\(\color{Gray}{1000}\)

CF1659A Red Versus Blue

有一个长度为 \(n\) 的字符串由 \(r\) 个字符 R\(b\) 个字符 B 组成,要求其中连续相同的字串长度最小。
\(n\le 100,1\le b < r,b+r=n\)

考虑小学数学中的植树问题,将字符 B 插入 R 之间,把 R 分成 \(b+1\) 段。
每段 R 的长度为 \(\left\lfloor \dfrac{x}{y+1} \right\rfloor\),剩余的间隔插入即可。

时间复杂度: \(O(n)\)

#define L(i,j,k) for(int i=(j);i<=(k);i++)
int n,x,y;
void work(){
  cin>>n>>x>>y;
  int t1=x/(y+1);
  int t2=x%(y+1);
  L(i,1,y){
    L(j,1,t1) cout<<'R';
    if(i<=t2) cout<<'R';
    cout<<'B';
  }L(i,1,t1) cout<<'R';
  cout<<'\n';
}

B

\(\color{ForestGreen}{1300}\)

CF1659B Bit Flipping

给定 \(01\) 串,每次操作翻转整个序列除某一指定元素,求 \(k\) 次操作后使字典序最大的结果并输出方案。
$n \le 2 \cdot 10^5 $

考虑贪心,从低位向高位枚举,分配最少使当前位为 \(1\) 的操作次数即可。

举个例子:如果当前位初始为 1,操作数 \(k\) 为奇数,就需要有一次指定当前位不翻转餐能保证最终的值为 \(1\)
最后特判一下最后一位要用完所有剩余操作次数的情况。

时间复杂度: \(O(n)\)

#define L(i,j,k) for(int i=(j);i<=(k);i++)
int n,k;
const int N=2e5+10;
char s[N],o[N];int a[N];
void work(){
  cin>>n>>k>>(s+1);
  int x=k;
  L(i,1,n) a[i]=0;
  L(i,1,n){
    if(s[i]=='1'){
      if((k&1)&&x) a[i]=1,x--,o[i]='1';
      else if(!(k&1)) o[i]='1';else o[i]='0';
    }else{
      if(!(k&1)&&x) a[i]=1,x--,o[i]='1';
      else if((k&1)) o[i]='1';else o[i]='0';
    }
  }if(x) a[n]+=x;
    if(a[n]%2==k%2) o[n]=s[n];
    else o[n]=(s[n]=='0'?'1':'0');
  L(i,1,n) cout<<o[i];cout<<'\n';
  L(i,1,n) cout<<a[i]<<" \n"[i==n];
}

朴素思想引出正解

C

\(\color{Aquamarine}{1500}\)

CF1659C Line Empire

\(n\) 个城市,位置分别为 \(a_i\),可以把任何占领的城市作为首都,初始首都位置为 \(0\)
每次可以选择花费 \(\left\vert a_i - p \right\vert \times x\) 将首都移动到城市 \(i\)。(\(p\) 为当前首都位置)
每次可以选择花费 \(\left\vert a_i - p \right\vert \times y\) 攻占城市 \(i\)。求攻占所有城市的最小花费。

\(n\le 2 \cdot 10^5,a_i \le 10^9\)

还是贪心思路。
如果转移首都可以使答案更优就转移。
假设当前首都在编号为 \(i\) 的城市。
如果将首都转移到 \(i+1\),消耗 \(dis(i,i+1) \times y\)
如果不转移首都,则之后相当于转移要多消耗 \(dis(i,i+1) \times (n-i-1)\)
比较两者的值确定决策即可。

时间复杂度: \(O(n)\)

#define L(i,j,k) for(int i=(j);i<=(k);i++)
int n,a,b;
const int N=2e5+10;
int x[N],s[N];
int dis(int a,int b){return x[b]-x[a];}
void work(){
  cin>>n>>b>>a;int ans=0,c=0;
  L(i,1,n) cin>>x[i];
  L(i,1,n){
    ans+=dis(c,i)*a;
    if((n-i)*a*dis(c,i)>dis(c,i)*b)
      ans+=dis(c,i)*b,c=i;
  }cout<<ans<<'\n';
}

D

\(\color{Purple}{1900}\)

CF1659D Reverse Sort Sum

有 01 序列 \(A\),进行 \(n\) 次操作,第 \(i\) 次将前 \(i\) 位排序,每次操作得到的序列相加得到序列 \(C\)
已知序列 \(C\),求序列 \(A\)
\(n\le 2 \cdot 10^5\)

首先很容易想到,\(s = \dfrac{\sum_{i=1}^n c_i}{n}\) 就是序列 \(A\)\(1\) 的个数。
从后向前考虑,\(C_n\) 的值只能是 \(n\)\(0\) (原因不难想到)
由此很容易知道 \(A_n\) 的值。
将其拓展到当前第 \(i\) 位的情况:如果 \(C_i=i\),则 \(A_i=1\),否则为 \(0\)
考虑如何向前递推:
因为已知第 \(i\) 为以及之后的情况,同时已知 \(s\) 的值,我们可以推算出之前区间 \(1\) 的个数。
考虑消除第 \(i\) 次操作产生的影响,假设有 \(k\)\(1\),回退操作相当于对 \(\left[ i-k+1,i \right]\) 区间内的 \(C_i\) 减去 \(1\)
这个可以用树状数组维护实现,每次单点修改 \(i-k+1\) 位置上的值,将其减一,之后对于当前的 \(C_i\),就能通过原先的 \(C_i\) 加上树状数组 \(\left[ 1,i \right]\) 的前缀和实现。即 \(C^\prime_i=C_i +ask(i)\)
时间复杂度:\(O(n \log_2 n)\)

#define L(i,j,k) for(int i=(j);i<=(k);i++)
int n,s;
const int N=2e5+10;
int a[N],c[N];
void change(int x,int k){for(;x<=n;x+=lb(x))a[x]+=k;}
int ask(int x){int s=0;for(;x;x-=lb(x))s+=a[x];return s;}
void work(){
  cin>>n;L(i,1,n) cin>>c[i];
  L(i,1,n) s+=c[i];s/=n;
  me(a,0);R(i,n,1){
    b[i]=(c[i]+ask(i)==i);
    change(i-s+1,-1);s-=b[i];
  }L(i,1,n) cout<<b[i]<<" \n"[i==n];
}

其实好像还有一种从前往后考虑的做法,在 CF 讨论区。

E

\(\color{Orange}{2200}\)

CF1659E AND-MEX Walk

给定 \(n\) 个点,\(m\) 条边的带权无向联通图。
\(q\) 次询问,每次询问给定两个点 \(u,v\),确定某条路径,使 \(MEX \left\{ w_1,w_1 \& w_2,\dots ,w_1 \& w_2 \& \dots \& w_k \right\}\) 最小,求最小值。
\(n,m \le 10^5,w_i < 10^9\)

首先观察样例,可以猜想是否答案只能是 \(0,1,2\) 中某数,即答案不大于 \(2\)

证明如下:
设某条路径上的权值组成数组 \(M\)
运用反证法,假设答案大于 \(2\),则在 \(M\) 中同时出现 \(1,2\)
由于与运算具有不升性,如果当前假设成立,则存在 \(M_i \& 2=1\),与基本事实不符。
Q.E.D

因为答案只有 \(3\) 种,只要排除了某两种可能,就可以确定答案。

  • 答案为 \(0\) 的情况:

判断从 \(u\)\(v\) 的路径中,是否存在二进制下某一位使得 \(W\) 中所有数在这一位上都是 \(1\)
考虑对于二进制下每一位开一个并查集维护,如果 \(u,v\) 某一位上处于同一个连通块,则答案为 \(0\)

  • 答案为 \(1\) 的情况:

可以想到答案为 \(1\) 必须满足一个性质:路径上存在一个点 \(r\),使得 \(\forall i \le r,W_i > 1 \text{and} W_{r+1}=0\)
即只要到达那个地方保证在到终点前经过一个使得与之和等于 \(0\)的点,接下来就与路径无关了。

所以,我们枚举不是最低位的每一位,找到有可能存在边 \((u,v,w)\) 使得 \(w\& 2^k,w\& 1\) 均为 \(0\)

这就要用到之前的并查集,同时对于每个点,记录经过这个点的所有边的边权与和 \(f_i\)
将同一个连通块内的所有点的 \(f\) 值取与和。如果与和为 \(0\) 则说明这个连通块内的点能使得答案为 \(1\)

时间复杂度:\(O(30n\alpha(n))\)

#define L(i,j,k) for(int i=(j);i<=(k);i++)
int n,m,q;
const int N=1e5+100,lim=(1<<30)-1;
int z[N],x[N];bool t[N];
struct DSU{
  int f[N];void cl(){L(i,1,n) f[i]=i;}
  int cz(int x){return x==f[x]?x:f[x]=cz(f[x]);}
  void hb(int x,int y){x=cz(x),y=cz(y);f[x]=y;}
  bool ch(int x,int y){return cz(x)==cz(y);}
}s[35];
void solve(){
  int l,r;cin>>l>>r;
  L(i,0,29) if(s[i].ch(l,r))
    return cout<<0<<'\n',void();
  cout<<(t[l]?1:2)<<'\n';
}void work(){
  cin>>n>>m;L(i,1,n) z[i]=lim;
  L(i,0,30) s[i].cl();
  L(i,1,m){
    int u,v,w;cin>>u>>v>>w;
    z[u]&=w,z[v]&=w;
    L(j,0,29) if(w&(1<<j))
      s[j].hb(u,v);
  }L(i,1,29){
    L(j,1,n) x[j]=lim;
    L(j,1,n) x[s[i].cz(j)]&=z[j];
    L(j,1,n) t[j]|=(!(x[s[i].cz(j)]));
  }cin>>q;L(i,1,q) solve();
}

大胆猜想,小心证明

posted @ 2022-08-23 20:33  AIskeleton  阅读(26)  评论(0编辑  收藏  举报