Educational Codeforces Round 138

Educational Codeforces Round 138

这把是真的丢了大脸。

Dashboard

$\color{Green}{★}\ \ $ 表示赛时做出。
$\color{Yellow}{★}\ \ $ 表示赛后已补。
$\color{Red}{★}\ \ $ 表示 \(\mathcal{To\ be\ solved}\)

A

A. Cowardly Rooks

\(\color{Green}{★}\ \ \mathcal{*Difficulty: ?}\)

Description

\(n \times n\) 的棋盘,有 \(m\) 原本不会互相攻击的车,询问是否能移动一辆车且依然不会互相攻击。

Solution

显然,只要 \(m \ne n\),就必然有解。

code
void work(){
  int n,m;cin>>n>>m;
  L(i,1,m) cin>>x>>y;
  cout<<((n^m)?"YES\n":"NO\n");
}

B

B. Death's Blessing

$\color{Green}{★}\ $ \(\ \mathcal{*Difficulty: ?}\)

Description

\(n\) 个怪物,血量等于数组 \(a\),被打死时给相邻怪物回 \(b_i\) 的血,求最少要打的血量。

Solution

显然,只要无限壁咚边缘的怪,并把 \(b\) 中的最大值留到最后,就能使 \(b\) 数组造成的贡献最小。

最终答案为:\(\sum_{i=1}^n a_i+b_i - \max{b_i} (1 \le i \le n)\)

code
void work(){
  int n;cin>>n;vi a(n),b(n);
  L(i,0,n-1) cin>>a[i];L(i,0,n-1) cin>>b[i];
  int ans=0;L(i,0,n-1) ans+=a[i]+b[i];
  ans-=*max_element(all(b));cout<<ans<<'\n';
}

C

C. Number Game

$\color{Yellow}{★}\ $ \(\ \mathcal{*Difficulty: ?}\)

Description

对给定数组进行 \(k\) 轮的游戏,在第 \(i\) 轮时,A 取走一个小于等于 \(k-i+1\) 的数字,B 对一个数加上 \(k-i+1\),问 \(k\) 的最大值。

Solution

首先,不难看出,在第 \(i\) 轮,无论 \(B\) 对什么数进行加,都能使被加的数在后面的轮次无法被删除。

所以对于 \(B\),最佳决策就是从小开始操作数,对于 \(A\),自然就是从大数开始删,每次选最大的满足条件的数删去即可。

因为 \(n \le 100\),所以完全可以模拟整个过程,时间复杂度 \(O(n^3)\)

当然,因为满足单调性,所以可以二分答案,时间复杂度 \(O(n^2 \log_2n)\)

code
bool sol(int k){
  b.resize(n);
  L(i,0,n-1) b[i]=a[i];
  L(i,1,k){
    int g=-1;R(j,n-1,0)
      if(b[j]<=k-i+1&&(b[j]!=-1)){
        g=b[j];b[j]=-1;break;
      }
    if(g==-1) return 0;
    for(int &i:b) if(~i){i=-1;break;}
  }return 1;
}void work(){
  cin>>n;a.resize(n);
  for(int &i:a) cin>>i;
  sort(all(a));
  L(i,0,n)
    if(sol(i)) s=i;
    else break;
  cout<<s<<'\n';
}

D

D. Counting Arrays

$\color{Yellow}{★}\ $ \(\ \mathcal{*Difficulty: ?}\)

Description

对于数组 \(a\) 中所有元素,如果满足 \(\gcd(a_i,i)=1\),就可以删除 \(a_i\),然后更新后面元素的下标。

给定数组大小 \(n\) 和数大小范围 \(m\),求问数组大小在 \([1,n]\) 范围内,所有元素在 \([1,m]\) 范围内,能用不止一种方式删光整个数组的数组数量。

Solution

首先,无论数组如何,都能够无限删除第一个元素。

如果这是唯一的删除方法,那么其元素 \(a_i\) 必须满足:\(\gcd(a_i,j) \ (2\le j \le i)\),因为在下标改变的过程中,会从 \(i\) 逐步移动到 \(1\),一直不能删就只能一直互质。

所以,能够满足只有单一删除方式的数组中元素 \(i\) 的取值种类有

\[\left\lfloor \dfrac{m}{\prod_{j=2}^i j \cdot \text{isPrime}_j}\right\rfloor \]

之后就是统计答案,每次加上全排列数,然后减去每个位置的满足单一删除的数值之积。

如果用了快速幂之类的算法统计答案,时间复杂度就是 \(O(n \log_2 n)\)

如果预处理出幂表,就能做到 \(O(n)\) 的时间复杂度。

下面给出的是 \(O(n)\) 做法,仅展示核心代码。

code
signed main(){
  FST;cin>>n>>m;prime_(n);
  a.resize(n+1);a[0]=1;
  L(i,1,n) a[i]=a[i-1]*m;
  L(i,1,n){
    if(isp[i]) g*=i;lst*=m/g;
    ans+=a[i]-lst;
  }cout<<ans;
}//用了封装的 modint

E

E. Cactus Wall

$\color{Yellow}{★}\ $ \(\ \mathcal{*Difficulty: ?}\)

Description

\(n \times m\) 的网格图中,加入一些障碍且所有障碍两两不相邻(四联通),原本已有一些障碍,求问是否能构造出一种障碍方案,使得不存在从第一行到最后一行的路径。

Solution

我的思路有点偏向于网络流。

”不存在路径“的要求就像网络流中的最小割,用障碍将图拦腰截断就行。

考虑建图,先标记所有原本已有的障碍的四联通,因为这些点是不能放障碍的。

然后对所有未被标记的点进行连边,因为障碍不能相邻,所以每个点对周围的四个角连边,边权由目标点是否原本有障碍决定。

然后像网络流那样引入一个源点和汇点。

源点向第一列的未标记点连边,最后一列的未标记点向汇点连边。

之后就可以跑 01BFS 或者 Dijskra,如果汇点的距离不为无穷大说明有解。

至于输出方案,就要记录路径,并将路径上的点记录并输出即可。

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

code
#include <bits/stdc++.h>
using namespace std;
#define F(i) (i).first
#define S(i) (i).second
#define si(i) ((int)i.size())
#define pb push_back
#define vi vector<int>
#define pi pair<int,int>
#define id(x,y) (x*m+y)
#define all(x) (x).begin(),(x).end()
#define me(t,x) memset(t,x,sizeof(t))
#define L(i,j,k) for(int (i)=(j);i<=(k);(i)++)
#define R(i,j,k) for(int (i)=(j);i>=(k);(i)--)
#define FST ios::sync_with_stdio(0);cin.tie(0);

const int N=5e5+100,INF=1e9;

int n,m,st,ed,pre[N],dis[N];
bool vis[N];vector<bool>b[N];vector<pi>g[N];
vector<pi>d={{-1,0},{1,0},{0,-1},{0,1}};
vector<pi>k={{-1,-1},{1,1},{1,-1},{-1,1}};
void work(){
  cin>>n>>m;
  st=n*m;ed=n*m+1;
  vector<string>s(n);
  L(i,0,n) b[i].resize(m+1);
  L(i,0,n) fill(all(b[i]),0);
  L(i,0,ed) g[i].clear();
  for(auto &i:s) cin>>i;
  L(i,0,n-1) L(j,0,m-1)
    if(s[i][j]=='#')
      for(pi x:d){
        int p=i+F(x),q=j+S(x);
        if(p<0||q<0||p>=n||q>=m) continue;
        b[p][q]=1;
      }
  L(i,0,n-1) L(j,0,m-1) if(!b[i][j])
    for(pi x:k){
      int p=i+F(x),q=j+S(x);
      if(p<0||q<0||p>=n||q>=m||b[p][q]) continue;
      g[id(i,j)].pb({id(p,q),s[p][q]=='.'});
    }
  L(i,0,n-1) if(!b[i][0]) g[st].pb({id(i,0),s[i][0]=='.'});
  L(i,0,n-1) if(!b[i][m-1]) g[id(i,m-1)].pb({ed,0});
  fill(dis,dis+n*m+5,INF);
  fill(vis,vis+n*m+5,0);
  fill(pre,pre+n*m+5,0);
  deque<int>q;
  q.push_back(st),dis[st]=0;
  while(!q.empty()){
    int u=q.front();q.pop_front();
    if(vis[u]) continue;vis[u]=1;
    for(pi x:g[u]){
      if(dis[F(x)]<=dis[u]+S(x)) continue;
      dis[F(x)]=dis[u]+S(x);pre[F(x)]=u;
      if(!S(x)) q.push_front(F(x));
      else q.push_back(F(x));
    }
  }if(dis[ed]==INF) return cout<<"NO\n",void();
  cout<<"YES\n";
  for(int i=ed;i^st;i=pre[i]) 
    if(i<n*m) s[i/m][i%m]='#';
  for(auto i:s) cout<<i<<'\n';
}signed main(){
  FST;int t;cin>>t;
  L(i,1,t) work();
}

F

F. Distance to the Path

$\color{Yellow}{★}\ $ \(\ \mathcal{*Difficulty: ?}\)

Description

给定一棵有 \(n\) 个节点的树,每个点有点权,初始全为 \(0\),进行 \(m\) 次操作,操作分两种:

  1. 输出给定节点 \(v\) 的权值。

  2. 给定节点 \(u,v\),权值 \(k\),距离 \(d\),对所有距离 \(u,v\) 之间的简单路径小于等于 \(d\) 的点的权值加上 \(k\)

Solution

暂时咕着。

code
#include <bits/stdc++.h>
using namespace std;
#define lb(x) (x&-x)
#define pb push_back
#define vi vector<int>
#define pi pair<int,int>
#define L(i,j,k) for(int (i)=(j);i<=(k);(i)++)
#define FST ios::sync_with_stdio(0);cin.tie(0);
const int N=2e5+100;
int n,m,tot,u,v,x,r;vi g[N];
int id[N],f[N],h[N],d[N],si[N],t[N],a[N],b[N];
struct BIT{
  int c[N];void add(int x,int k){for(;x<=n;x+=lb(x)) c[x]+=k;}
  int ask(int x){int s=0;for(;x;x-=lb(x)) s+=c[x];return s;}
}T[21];
void dfs1(int u,int fa){
  f[u]=fa;si[u]=1;d[u]=d[fa]+1;
  for(int v:g[u]) if(v^fa){
    dfs1(v,u);si[u]+=si[v];
    if(si[h[u]]<si[v]) h[u]=v;
  }
}void dfs2(int u,int tp){
  t[u]=tp;id[u]=++tot;
  if(!h[u]) return ;dfs2(h[u],tp);
  for(int v:g[u]) if(v^f[u]&&v^h[u]) dfs2(v,v);
}int lca(int x,int y){
  while(t[x]^t[y])
    if(d[t[x]]<d[t[y]]) y=f[t[y]];
    else x=f[t[x]];return d[x]<d[y]?x:y;
}void change(int u,int v,int k,int x){
  while(t[u]^t[v]){
    if(d[t[u]]<d[t[v]]) swap(u,v);T[x].add(id[t[u]],k);
    T[x].add(id[u]+1,-k);u=f[t[u]];
  }if(d[u]>d[v]) swap(u,v);
  T[x].add(id[u],k);T[x].add(id[v]+1,-k);
}int main(){
  FST;cin>>n;L(i,1,n-1)
    cin>>u>>v,g[u].pb(v),g[v].pb(u);
  L(i,n+1,n+19) g[i].pb(i+1),g[i+1].pb(i);
  g[n+1].pb(1);g[1].pb(n+1);n+=20;
  dfs1(n,n);dfs2(n,n);cin>>m;
  L(i,1,m){
    int o,u,v,k,x;cin>>o;
    if(o==1){
      cin>>x;int g=0;L(j,0,20) 
        g+=T[j].ask(id[x]),x=f[x];
      cout<<g<<'\n';
    }else{
      cin>>u>>v>>k>>x;change(u,v,k,x);
      if(!x) continue;u=lca(u,v);
      change(u,u,-k,x);int g=0;
      L(j,0,x){
        L(p,0,x-j) change(u,u,k,p);
        if(g&&(j^x)) L(p,0,x-j-1)
          change(g,g,-k,p);
        g=u;u=f[u];
      }
    }
  }
}

最近一直在下分,希望中国场能上回来点吧。

posted @ 2022-10-22 14:02  AIskeleton  阅读(23)  评论(0编辑  收藏  举报