首页 > 其他分享 >20230318模拟赛(jnxxhzz)

20230318模拟赛(jnxxhzz)

时间:2023-04-08 23:22:25浏览次数:40  
标签:head 颜色 int 20230318 tot edge maxn jnxxhzz 模拟

T1.彩虹树

对于每一个u,v,我们都要去算u->v路径上有多少个不同的元素

很显然,n\leqslant 10^5<span class="cke_reset cke_widget_drag_handler_container"><img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" width="15" height="15" class="cke_reset cke_widget_drag_handler" title="点击并拖拽以移动" data-cke-widget-drag-handler="1" data-mce-src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw=="><span class="cke_image_resizer" title="点击并拖拽以改变尺寸">​<span class="cke_widget_edit_container" title="编辑图片">编辑每一个去枚举是不现实的

所以我们要考虑换个方法枚举

既然是要求每条路径上不同颜色的数量

我们可以考虑枚举每一个颜色在多少条路径上出现过

考虑每一种颜色,我们把那种颜色的点在树上删掉

那么就会形成若干个块,我们把这些块相乘

这样的思路是有关正着算,是一个一个往上加的

我们还可以倒着来想,考虑在那些方案中是没有这个颜色的

对于每个节点v,它子树中的节点连出去一定会经过这个颜色

那在与v颜色相同的上一个节点间,这些节点两两相连是不会经过col[v]的,是被减去的

这样就可以用一个dfs来计算每次减少的数量

时间复杂度为O(n)

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=2e5+10;
int n,col[maxn],head[maxn],tot=0;
ll sum[maxn],ans=0,cnt=0,sz[maxn];
bool vis[maxn];
struct edge{
  int v,nxt;
}e[maxn*2];

void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
  e[++tot]=(edge){u,head[v]};
  head[v]=tot;
}

void init(){
  for(int i=1;i<=n;i++)
    vis[i]=false,col[i]=sz[i]=head[i]=sum[i]=0;
  tot=ans=cnt=0;
}

void dfs(int u,int fa){
  sz[u]=1;//sum[col[u]]指枚举到此时,树中以col[u]这一颜色的节点为根的节点数量 
  ll pre=sum[col[u]],c=0,tmp=0,szv;
  for(int i=head[u];i;i=e[i].nxt){
      int v=e[i].v;
      if(v==fa) continue;
      dfs(v,u);
      sz[u]+=sz[v];
      tmp=sum[col[u]]-pre;//计算以v为根的子树中,以col[u]颜色为根的子树的数量 
      szv=sz[v]-tmp;//以v为根的子树中,互相连通且不经过col[u]颜色的点的数量 
      pre=sum[col[u]];//记得更新pre 
      ans-=1ll*szv*(szv-1)/2;
      c+=tmp;//去重,防止每次更新sum是都要多加tmp 
  }
  sum[col[u]]+=sz[u]-c;
}

int main(){
  scanf("%d",&n);
  init();
  for(int i=1;i<=n;i++){
      scanf("%d",&col[i]);
      if(!vis[col[i]]) vis[col[i]]=true,cnt++;
  }
  for(int i=1;i<n;i++){
      int u,v;
      scanf("%d%d",&u,&v);
      add(u,v);
  }
  ans=1ll*cnt*n*(n-1)/2;//每条路径都会经过所有颜色的总数
  dfs(1,0); 
  for(int i=1;i<=n;i++){
      if(!sum[i]) continue;
      ll tmp=n-sum[i];
      ans-=1ll*tmp*(tmp-1)/2;
  }//最后在进行一次整棵树的去重 
  printf("%lld\n",ans);
  return 0;
}

 

T2.最小生成树

给定了生成树,可以去模拟最小生成树的过程

为什么要选这条边?

因为它在连通u,v两点中w最小

我们令给定生成树上的边叫树边,那么其他的边都是非树边

对于每条非树边,我们要让树上它所连接的两点上的路径最大值小于等于非树边的值

这样这条非树边才不会被选

然而我们可以先把非树边从小到大排序,那么对于每一条树边,就只会进行一次修改

这样就可以边缩点边修改,节约时间

时间复杂度O(mlogm)

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=1e5+10;
int n,m,cnt=0,head[maxn],tot=0;
ll ans=0;
map<pair<int,int>,int> mp;
struct edge{
  int u,v,val,nxt;
  bool operator < (const edge &rhs) const{
    return val<rhs.val;
  }
}e[maxn],t[maxn]; 
struct node{
  int fa,val,dep;
  void mmset(int w){//每一次更新答案 
      if(w<val) ans+=1ll*(val-w);
      val=w;
  }
}a[maxn];

void add(edge p){//对树边进行建树 
  int u=p.u,v=p.v,val=p.val;
  t[++tot]=(edge){u,v,val,head[u]};
  head[u]=tot;
  t[++tot]=(edge){v,u,val,head[v]};
  head[v]=tot;
}

void dfs(int u,int fa){//dfs生成树 
  a[u].fa=fa;a[u].dep=a[fa].dep+1;
  for(int i=head[u];i;i=t[i].nxt){
      int v=t[i].v,val=t[i].val;
      if(v==fa) continue;
      a[v].val=val;
      dfs(v,u);
  }
}

int getans(int u,int v,int w){//每一条非树边分别对树边进行优化 
  if(u==v) return u;
  int p=0;
  if(a[u].dep==a[v].dep){
      a[u].mmset(w);
      a[v].mmset(w);
      p=getans(a[u].fa,a[v].fa,w);
  }
  if(a[u].dep>a[v].dep){
      a[u].mmset(w);
      p=getans(a[u].fa,v,w);
  }
  if(a[u].dep<a[v].dep){
      a[v].mmset(w);
      p=getans(u,a[v].fa,w);
  }
  if(u!=p) a[u].fa=p;
  if(v!=p) a[v].fa=p;//缩点 
  return p; 
}

int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++){
    scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].val);
    if(e[i].u>e[i].v) swap(e[i].u,e[i].v);
    mp.insert(make_pair(make_pair(e[i].u,e[i].v),i));
  }
  for(int i=1,u,v;i<n;i++){
      scanf("%d%d",&u,&v);
      cnt=mp[make_pair(u,v)];
      mp[make_pair(u,v)]=-1;
      add(e[cnt]);//建树 
  }
  dfs(1,0);
  sort(e+1,e+m+1);
  for(int i=1;i<=m;i++){
      if(mp[make_pair(e[i].u,e[i].v)]==-1) continue;
      getans(e[i].u,e[i].v,e[i].val);
  }
  printf("%lld\n",ans);
  return 0;
}

 

T3.快速排序

 

总结

1.从多角度分析问题,如T1中每条路径的颜色可以转换成每个颜色所在的路径数量

然而有可以反向操作,算出最大方案数再减去不合理的,是常见的思路

2.在做题过程中可以去模拟样例,模拟算法过程,从具体实现中找条件

标签:head,颜色,int,20230318,tot,edge,maxn,jnxxhzz,模拟
From: https://www.cnblogs.com/hewanying0622/p/17299550.html

相关文章

  • 20230225模拟赛(jnxxhzz)
    A.bubble冒泡排序考虑k次冒泡中的每一次,会把最大的数移到最右边而只有最大数在变吗?以14352为例5的右边相对顺序是不变的,而5的左边是要变的发现在不断地把小的往前面移,且每一个较小的数都会往前最多移动k个但我们不好算每个i往前移k个的数考虑反向处理:算有哪些点可以被......
  • 1223. 掷骰子模拟
    题目链接:1223.掷骰子模拟方法:回溯+记忆化搜索解题思路回溯要点参数列表根据题目中的操作确定在递归中需要用到的上一层的某个变量或性质。递归边界即递归的最底层,确定其返回值。记忆化搜索由于在递归中会重复计算某一状态的值,那么我们在第一次计算出来后将其保存......
  • 基于matlab模拟雷达信号检测中的恒虚警处理方法(慢门限和快门限)
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 基于Matlab模拟圆周阵列天线
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 数组模拟环形队列的思路及代码
    JAVA实现数组模拟环形队列的思路及代码前言在对Java实现数组模拟队列零了解的情况下,建议先去阅读《JAVA实现数组模拟单向队列的思路及代码》一文,可以辅助理解本文核心思想。一、环形数组队列实现:让数组达到复用的效果,即:当我们从数组队列中取出了数据,那取出数据后后这个空间可......
  • 数组模拟单向队列的思路及代码
    JAVA实现数组模拟单向队列的思路及代码一、什么是队列?队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称......
  • C/C++模拟ATM机存取款管理系统[2023-04-07]
    C/C++模拟ATM机存取款管理系统[2023-04-07]2、模拟ATM机存取款管理系统模拟银行的自动取款机使用过程中的界面和用户交互过程。实现查询银行卡余额、取款修改密码、退出系统等功能。(一)功能要求及说明:(1)将银行账户的卡号,户名,密码和账户余额从外部文件(银行账户.txt)中读入......
  • 模拟循环批量请求后台接口
    使用async,await处理异步请求。用Promise,setTimeout函数模拟后台接口<!DOCTYPEhtml><html><scripttype="text/javascript">vararr=[];varbatchSize=10;for(i=0;i<30;i++){arr.push(i);}functionb(startIdx,endIdx){ returnnewPromise((res......
  • 邮箱密码-2020模拟
    【题目描述】小明的电子邮箱密码忘记了。请你帮他找出密码。他零星记得密码的信息如下:1)密码是六位数字,前面两位是31;2)最后两位数字相同;3)能被16和46整除;请你找出所有可能的密码并统计个数。【评分标准】30分︰正确打印出一组符合的六位数(程序不报错);80分∶在满足30基础......
  • JS 模拟鼠标事件mouse over、click
     <!DOCTYPEhtml><html><head><metacharset="utf-8"><metahttp-equiv="content-type"content="text/html;charset=utf-8"><metaname="renderer"content="webkit&quo......