23ICPC澳门站补题

原题链接

补题思路参考 1 2 3

J BFS

传送系统两个操作

  • 立即传送到 ( i + a i )   m o d   n (i+a_i)\bmod n (i+ai)modn
  • 调整 a i ← a i + 1 a_i\gets a_i+1 ai←ai+1,也就相当于传送到 a i + 1 a_i+1 ai+1的位置,到 a i a_i ai后再向前跳一个
    BFS遍历找最短路
cpp 复制代码
const int N=1e5+5,mod=1e9+7,inf=1e9+10;
int vis[N][2],dis[N][2];
void solve() {
    memset(dis,0x3f,sizeof dis);
    int n,x;cin>>n>>x;
    vector<int>a(n+1);
    forr(i,0,n-1)cin>>a[i];
    queue<pii>q;
    q.push({0,0});//{位置,是否可以向前跳}
    //此时还没跳到a_i的地方 不能向前
    dis[0][0]=0;
    while (q.size())
    {
        auto [i,st]=q.front();
        q.pop();
        
        if(vis[i][st])continue;
        vis[i][st]=1;
			//向下一个点跳
        int nxt=(i+a[i])%n;//跳到a_i
        if(dis[nxt][1]>dis[i][st]+1){
            dis[nxt][1]=dis[i][st]+1;
            q.push({nxt,1});
        }
        
        if(st==1){//跳到这个点 可以+1
            nxt=(i+1)%n;
            if(dis[nxt][1]>dis[i][1]+1){
                dis[nxt][1]=dis[i][1]+1;
                q.push({nxt,1});
            }
        }
    }
    
    cout<<dis[x][1]<<endl;
}

I 贪心 dp

cpp 复制代码
void solve(){
    int a,b,m;cin>>a>>b>>m;
    vector<int>dp(m+1,0);
    
    dp[0]=2;//dp[i] 到时间i 两道具都用  用前面的转移到后面
    /*两种思路
    1.等到R刷新好了就用 周期是b
    2.R刷新好了之后 等待H冷却好了再用R 周期是 ceil(b/a)*a=(b+a-1)/a *a
    */
    int ans=0;
    forr(i,0,m){
        if(dp[i]){
            ans=max(dp[i]+(m-i)/a,ans);//后面的不再用b
            //向后转移
            if(i+b<=m)dp[i+b]=max(dp[i+b],dp[i]+1+b/a);//+1是因为H冷却好了
            if(i+(b+a-1)/a*a<=m)dp[i+(b+a-1)/a*a]=max(dp[i+(b+a-1)/a*a],dp[i]+1+(b+a-1)/a);
        }
    }
    cout<<ans*160<<endl;
    //废片 一开始只想了一种以b为周期的策略
    // if(a>b){
    //     cnt=2+m/b;
    //     cout<<1<<' '<<cnt<<endl;
    // }else {
    //     int rd=m/b,dis=m%b;
    //     int cnta=(b+a-1)/a;
    //     cout<<cnta<<' '<<rd<<endl;
    //     cnt=cnta*rd+(dis/a+1)+1;
    //     cout<<cnta*rd<<' '<<(dis/a+1)<<' '<<(rd+1)<<endl;
    //     cout<<cnt<<endl;
    // // if(a==b){
    // //     cnt=2*(m/b+1);
    // //     cout<<2<<' '<<cnt<<endl;
    // // }else{
    // //     int per=(b+a-1)/a;
    // //     cnt=per*(m/b+1)+1;
    // //     cout<<3<<' '<<cnt<<endl;
    // }
}

D 思维 并查集 暴力

注意题设条件:可以保证 G 的最大度数最多为 3

诱导子图有n个点,红蓝总共最多有 3 n 2 3n\over 2 23n条边

满足合法,最少有 2 ( n − 1 ) 2(n-1) 2(n−1)
3 n 2 ≥ 2 ( n − 1 ) {3n\over 2}\geq 2(n-1) 23n≥2(n−1),得 n ≤ 4 n\leq 4 n≤4

枚举n暴力找点

cpp 复制代码
const int N=1e5+2,mod=1e9+7,inf=1e9+10;
int fa[N],vis[N];
vector<pii>ve[N];
set<pii>e[N];
int find(int x){
    return fa[x]==x?x:find(fa[x]);
}
void merge(int x,int y){
    int fx=find(x),fy=find(y);
    fa[fx]=fy;
}
int check(vector<int>v){//验证蓝边是否也联通
    for(auto i:v)fa[i]=i;
    //两两枚举红联通集中的点 在蓝边里合并 看是否能合成一个联通集
    forr(i,0,3){
        forr(j,0,3)if(e[1].count({v[i],v[j]}))merge(v[i],v[j]);
    }
    forr(i,1,3)if(find(v[i])!=find(v[0]))return 0;
    return 1;
}
void dfs(int now,vector<int>&v){
    vis[now]=1;
    v.push_back(now);
    if(v.size()==4)return;
    for(auto [to,c]:ve[now]){
        if(c==0&&vis[to]==0)dfs(to,v);//只找红边
    }
}
//关键在于找出两种颜色最小联通的情况 边的数量2(m-1)
//根据题设每个点最多3条边 3m/2>=2(m-1)  m<=4
void solve(){
    int n,m;cin>>n>>m;
    forr(i,1,m){
        int u,v,c;
        cin>>u>>v>>c;
        ve[u].push_back({v,c}),ve[v].push_back({u,c});
        e[c].insert({u,v});
    }
    int ans=0;
    //sz=2,3
    for(auto [u,v]:e[0]){
        if(e[1].count({u,v})||e[1].count({v,u})){
            ans++;//2
            pii rnxt,bnxt;
            rnxt=bnxt=pii(-1,-1);
            for(auto [to,c]:ve[u])if(to!=v)rnxt={to,c};
            for(auto [to,c]:ve[v])if(to!=u)bnxt={to,c};
            if(rnxt.fir==-1||bnxt.fir==-1)continue;
            if(rnxt.fir==bnxt.fir&&rnxt.sec!=bnxt.sec)ans++;//3
        }
    }
    // sz=1
    ans+=n;
    // sz=4 暴力找四个点 验证是否合法
    forr(i,1,n){
        if(vis[i]==0){
            vector<int>getv;dfs(i,getv);
            // for(auto x:getv)cout<<x<<' ';cout<<endl;
            if(getv.size()==4)ans+=check(getv);
        }
    }
    cout<<ans<<endl;
}

A 思维 问题转化

cpp 复制代码
const int N=4e3+2,mod=1e9+7,inf=1e9+10;
string s[N];
int a[N][N],nd[N][N];
//关键在于发现取与不取后值的变化
//-1如果先都取上(达到最小值) 不取就相当于+1 
//取一个1也相当于+1 
//关注从最小值到目标值需要多少个+1
void solve(){
    int n;cin>>n;
    vector<int>row(n+1),col(n+1);
    forr(i,1,n){
        cin>>s[i];
        forr(j,0,n-1)a[i][j+1]=(s[i][j]=='-'?-1:1);
    }
    forr(i,1,n)cin>>row[i];
    forr(i,1,n)cin>>col[i];
    
    //统计每行-1的和
    priority_queue<pii>q;//使用堆 优先填补需求+1多的
    forr(i,1,n){
        int cnt=0;
        forr(j,1,n)if(a[i][j]<0)cnt+=a[i][j];
        if(cnt>row[i])rno;//return no;
        else {
            q.push({row[i]-cnt,i});//需要多少个+1
        }
    }
    
    //统计每列需要+1的位置 和每行需要+1的位置交叉
    forr(j,1,n){
        int cnt=0;
        forr(i,1,n)if(a[i][j]<0)cnt+=a[i][j];
        if(cnt>col[j])rno;
        else if(col[j]-cnt>q.size())rno;//每列的+1不能满足需要的

        vector<pii>tp;//把当前需要+1的行全遍历一遍
        while(q.size())tp.push_back(q.top()),q.pop();
        for(auto [d,id]:tp){
            if(cnt<col[j]){
                cnt++;
                d--,nd[id][j]=1;//这里需要一个+1
            }
            if(d>0)q.push({d,id});//还没填满该行需要的1 放回去
            
        }
    }
    if(q.size())rno;
    cout<<"yes"<<endl;
    forr(i,1,n){
        forr(j,1,n){
            if(nd[i][j]==1)cout<<(a[i][j]==1);//需要1且正好有1
            else cout<<(a[i][j]==-1);//不需要1 把-1算上
        }cout<<endl;
    }
}

E 单调栈找逆序对 拓扑排序

a:1 3 2 (3->2)

b:3 2 1

逆序一定有边

cpp 复制代码
const int N = 1e5+10, C = 1e6 + 10, mod =998244353, inf = 1e9 + 10;
vector<int>tr[N];
void solve()
{
  int n;cin>>n;
  vector<int>a(n+1,0),b(n+1,0),deg(n+1,0);
  forr(i,1,n)cin>>a[i];
  forr(i,1,n)cin>>b[i];
  int ans=0;
  //单调栈找逆序 逆序的一定有边
  stack<int>st;
  //小字典序 
  forr(i,1,n){
    while (st.size()&&st.top()<a[i])st.pop();
    if(st.size()&&st.top()>a[i]){
      tr[st.top()].push_back(a[i]);
      deg[a[i]]++;
      ans++;
    }
    st.push(a[i]);
  }
  while(st.size())st.pop();
  //大字典序 前面大->小的边在大字典序中不会产生逆序 不会重复计算
  forr(i,1,n){
    while (st.size()&&st.top()>b[i])st.pop();
    if(st.size()&&st.top()<b[i]){
      tr[st.top()].push_back(b[i]);
      deg[b[i]]++;
      ans++;
    }
    st.push(b[i]);
  }
  
  //成图后 大小字典序分别拓扑验证是否合理
  int ac,bc;ac=bc=0;
  int ai,bi;ai=bi=0;
  vector<int>aj(n+1,0),bj(n+1,0);
  priority_queue<int,vector<int>,greater<int>>qa;//小根堆
  priority_queue<int>qb;//默认大根堆
  auto degt=deg;
  forr(i,1,n)if(deg[i]==0)qa.push(i),qb.push(i);
  while (qa.size())
  {
    int now=qa.top();qa.pop();
    aj[++ai]=now;
    for(auto x:tr[now]){
      if(--deg[x]==0)qa.push(x);
    }
  }
  while (qb.size())
  {
    int now=qb.top();qb.pop();
    bj[++bi]=now;
    for(auto x:tr[now]){
      if(--degt[x]==0)qb.push(x);
    }
  }
  if((ai!=n||bi!=n)&&ans!=0)rno;
  else if(aj!=a||bj!=b)rno;
  else {
    cout<<"Yes"<<endl<<ans<<endl;
    forr(i,1,n){
      for(auto x:tr[i]){
        cout<<i<<' '<<x<<endl;
      }
    }
  }
}

H 树形dp 组合数学 可重复排列

约束:选择停在子树now的车数量范围 s z n o w ≤ c u r ≤ s z n o w + d e p n o w sz_{now}\leq cur\leq sz_{now}+dep_{now} sznow≤cur≤sznow+depnow,多出来的可以向根节点推最多 d e p n o w dep_{now} depnow

cpp 复制代码
const int N = 1e5+10, C = 1e6 + 10, mod =998244353, inf = 1e9 + 10;
vector<int>tr[N];
int sz[N],dep[N],fact[N],ifac[N];
int qpow(int x,int p=mod-2){
  int res=1;
  for(;p;p>>=1,(x*=x)%=mod)if(p&1)(res*=x)%=mod;
  return res;
}
void init(int n){
  fact[0]=1;
  forr(i,1,n)fact[i]=fact[i-1]*i%mod;
  ifac[n]=qpow(fact[n]);
  reforr(i,0,n-1)ifac[i]=(i+1)*ifac[i+1]%mod;
}
vector<int>dp[N];//dp[i] i点的状态
void dfs(int now=1){
  //本节点子树数据更新
  sz[now]=1;
  for(auto x:tr[now])dep[x]=dep[now]+1,dfs(x),sz[now]+=sz[x];

  //子树合并
  int L=0,R=0;//L,R是已合并子树的车数范围
  static int g[N],tmp[N];//g已合并子树的方案数 
  g[0]=1;//now一个点的情况
  for(auto x:tr[now]){
    forr(i,L+sz[x],R+sz[x]+dep[x])tmp[i]=0;//新合并的用tmp存用到哪里初始化哪里
    forr(i,L,R){
      forr(j,sz[x],sz[x]+dep[x]){//枚举停在x子树中的车数
        // sz[x](把x子树填满)<=j<=sz[x]+dep[x](可以向上填最多dep[x]个)
        // 乘法原理 已合并的子树方案数*目前子树方案数
        (tmp[i+j]+=g[i]*dp[x][j-sz[x]])%=mod;//用子树的dp更新本层 j-sz[x]是向上超出的层数
      }
    }
    L+=sz[x],R+=sz[x]+dep[x];//更新子树范围
    forr(i,L,R)g[i]=tmp[i];//新子树合并进去
  }

  //计算本节点dp:本节点方案数
  dp[now].resize(dep[now]+1);
  forr(ch,L,R)//枚举now的子树车数
    forr(cur,sz[now],sz[now]+dep[now]){//枚举now下面的子树车数 更新方案数
      if(ch<=cur)(dp[now][cur-sz[now]]+=g[ch]*ifac[cur-ch])%=mod;
      //*ifac[cur-ch] cur-ch是选择停在now点的车数 用到了可重全排列 
      /*
      可重全排列 eg. 1 1 2 1
      有4!/3!=4个排列情况 n!/val! val是重复元素的个数
      n!可以在最后计算总结果时乘上,进行答案的全排列
      */
    }
}
void solve()
{
  int n;cin>>n;
  forr(i,2,n){
    int f;cin>>f;
    tr[f].push_back(i);
  }
  init(n);
  dfs();
  int ans=(dp[1][0]*fact[n])%mod;
  cout<<ans<<endl;
}
相关推荐
Brookty4 小时前
【算法】二分查找(一)朴素二分
java·学习·算法·leetcode·二分查找
黑色的山岗在沉睡5 小时前
LeetCode 2761. 和等于目标值的质数对
算法·leetcode·职场和发展
bawangtianzun5 小时前
重链剖分 学习记录
数据结构·c++·学习·算法
T1an-19 小时前
力扣70.爬楼梯
算法·leetcode·职场和发展
T1an-19 小时前
力扣169.多数元素
数据结构·算法·leetcode
_dindong14 小时前
动规:回文串问题
笔记·学习·算法·leetcode·动态规划·力扣
wangwangmoon_light14 小时前
0.0 编码基础模板
java·数据结构·算法
共享家952715 小时前
Leetcode刷题
算法·leetcode·职场和发展
simon_skywalker15 小时前
第7章 n步时序差分 n步时序差分预测
人工智能·算法·强化学习