AtCoder 题目集2

AtCoder 题目集2

终于迈入了一个新的阶段,接下来希望质量能高一点吧。

现在我主要刷的是1600左右的题,毕竟实力太拉,只能按照 ”分上200“ 的策略。

(我觉得类型标个 “思维” 貌似没啥意义,毕竟AT几乎全是思维啊...)

编号(NO.) 题目 难度 类型
1 ABC201E 1694,medium 思维,XOR
2 ABC243E 1637,medium 最短路
3 ABC083D 1646,hard- 思维
4 ABC092D 1445,hard- 构造
5 ABC081D 1567,hard- 前缀和
6 ABC185E 1468,medium DP
7 ARC102D 1810,easy 构造
8 AGC016B 1626,easy 分类讨论,思维
9
10

NO.1 ABC201E

有关 \(XOR\) 的题目很多都考察了按位解决的思想...确实,很常见的一种解决办法。

所以对于这道题,只要能发现按位解决就OK了。

最后复杂度(我写的) 是 \(O(60n)\) 左右...但是慢的一比...不知道为啥...

const int N=200010,mod=1e9+7;

int n,cnt,head[N];
struct Edge{
  int to,nxt; ll val;
}edge[N<<1];
void Add(int fo,int to,ll val){
  edge[++cnt]={to,head[fo],val};
  head[fo]=cnt;
}

ll f[N][60],ans,sz[N];

void dfs(int pos,int fa,ll dis){
  sz[pos]=1;
  for(int i=0;i<60;++i) f[pos][i]=!!(dis&(1ll<<i));
  for(int i=head[pos];i;i=edge[i].nxt){
    int &to=edge[i].to;
    if(to^fa){
      dfs(to,pos,dis^edge[i].val);
      for(int i=0;i<60;++i)
        f[pos][i]+=f[to][i];
      sz[pos]+=sz[to];
    }
  }
}

void solve(int pos,int fa,int wei,bool tag){
  if(tag) ans=(ans+(sz[1]-f[1][wei])*((1ll<<wei)%mod)%mod)%mod;
  else ans=(ans+f[1][wei]*((1ll<<wei)%mod)%mod)%mod;
  for(int i=head[pos];i;i=edge[i].nxt){
    int &to=edge[i].to;
    if(to^fa){
      if(edge[i].val&(1ll<<wei))
        solve(to,pos,wei,tag^1);
      else solve(to,pos,wei,tag);
    }
  }
}

void check(int wei){
  solve(1,1,wei,0);
}

signed main(){
  // freopen("gen.txt","r",stdin);
  // freopen("");
  IOS
  //int T;

  cin>>n;
  for(int i=1;i<n;++i){
    int fo,to; ll val;
    cin>>fo>>to>>val;
    Add(fo,to,val);
    Add(to,fo,val);
  }

  dfs(1,1,0);
  for(int i=0;i<60;++i)
    check(i);
  cout<<ans*q_Pow(2,mod-2,mod)%mod<<'\n';
  
  return 0;
}

NO.2 ABC243E

看到 \(n\leqslant 300\) 就知道先来个 \(Floyd\) 。然后对于一条边,如果可以丢掉它,那当且仅当我们任何一条最短路都不会经过它,更进一步地说,就是存在另一条路,使得路径长度不长与这条边。思路就是这样。开始我想了求最短路的时候维护一下每条边是否会使用,但是没法排除等长的情况,会少统计...

const int N=305;

int n,m;
ll dis[N][N];
struct Edge{int fo,to,val;}edge[N*N];

signed main(){
  // freopen("gen.txt","r",stdin);
  // freopen("a.txt","w",stdout);
  IOS
  //int T;
  cin>>n>>m; memset(dis,0x3f3f,sizeof(dis));
  for(int i=1,fo,to,val;i<=m;++i){
    cin>>fo>>to>>val;
    edge[i]={fo,to,val};
    dis[fo][to]=dis[to][fo]=val;
  }

  F(1,i,1,n) F(1,j,1,n) F(1,k,1,n)
    dis[j][k]=Min(dis[j][k],dis[j][i]+dis[i][k]);

  int ans=0;
  F(1,i,1,m){
    int fo=edge[i].fo,to=edge[i].to,val=edge[i].val;
    for(int j=1;j<=n;++j)
      if(j!=fo&&j!=to&&dis[fo][to]>=dis[fo][j]+dis[j][to]){
        ++ans;
        break;
      }
  }
  cout<<ans<<'\n';

  return 0;
}

NO.3 ABC083D

这题真的很烧我的脑(😄)...一直不是很明白...

可以发现,实际上 \(0/1\) 是没有区别的,可以先把 \(0\) 变成 \(1\) ,也可以反着,所以主要是要把所有不同的数变成相同的。那么对于第 \(i/(i+1)\) 这两个位置,如果字符不相同,那么一定要有个操作去把 \((i+1)\) 变为 \(i\) 或者 \(i\) 变为 \((i+1)\) ,而既然求的是最长区间,所以要么就先把 \(i+1 \rightarrow n\) 变,再 \(i \rightarrow n\) 变(也可以1~i-1这么搞),如此 \(i\) 位就变了,当然也可以变 \(j\) 位。所以只需要对每个不同相邻点对进行求min(max)即可。因为假设最后求出的长度为 \(k\) ,那么对于任意一对相邻不同点对,一定可以化为相同。

可以具体举个例子: \(1100100\) ,此时对于第四位 \(0\) 有最小 \(k=3\)

int main(){
  ios::sync_with_stdio(false);
  cin.tie(NULL); cout.tie(NULL);
  
  string s;
  cin>>s; int ans=s.length();
  for(int i=1;i<s.length();++i)
    if(s[i]!=s[i-1]) ans=min(ans,max(i,(int)s.length()-i));
  cout<<ans<<'\n';

  return 0;
}

NO.4 ABC092D

实际上答案很直接,但是上来确实不好想到...(太菜)

直接搞100*100,然后上半为黑,下半为白,然后在白的里面跳着插黑,黑里插白

😄

const int N=105;

int a,b,mp[N][N];

signed main(){
  IOS

  cin>>a>>b;
  for(int i=1;i<=50;++i) for(int j=1;j<=100;++j)
    mp[i][j]=1;//black
  for(int i=51;i<=100;++i) for(int j=1;j<=100;++j)
    mp[i][j]=0;//white
  --a,--b;

  int x=1,y=1;
  while(a--){
    mp[x][y]=0;
    y+=2;
    if(y>100) x+=2,y=1;
  }
  x=100,y=100;
  while(b--){
    mp[x][y]=1;
    y-=2;
    if(!y) x-=2,y=100;
  }

  cout<<100<<' '<<100<<'\n';
  for(int i=1;i<=100;++i){
    for(int j=1;j<=100;++j)
      cout<<(mp[i][j]?"#":".");
    cout<<'\n';
  }

  return 0;
}

NO.5 ABC081D

发现如果全是正数,直接跑一边前缀和即可,负数则后缀和,所以先看最大值和最小值,所有数变为同号。

const int N=55;

int n,a[N];
vector<pair<int,int>> ans;

signed main(){
  //freopen();
  //freopen();
  IOS
  //int T;
  cin>>n;
  int mn=1e6,mx=-1e6,pmx,pmn;
  for(int i=1;i<=n;++i){
    cin>>a[i],mx=Max(mx,a[i]),mn=Min(mn,a[i]);
    if(a[i]==mx) pmx=i;
    if(a[i]==mn) pmn=i;
  }
  
  if(mx<=0){
    for(int i=n;i>1;--i)
      ans.push_back(make_pair(i,i-1));
  } else if(mn>=0){
    for(int i=2;i<=n;++i)
      ans.push_back(make_pair(i-1,i));
  } else{
    if(mx+mn>=0){
      for(int i=1;i<=n;++i) if(a[i]!=mx){
        a[i]+=mx;
        ans.push_back(make_pair(pmx,i));
      }
      for(int i=2;i<=n;++i)
        ans.push_back(make_pair(i-1,i));
    } else{
      for(int i=1;i<=n;++i) if(a[i]!=mn){
        a[i]+=mn;
        ans.push_back(make_pair(pmn,i));
      }
      for(int i=n;i>1;--i)
        ans.push_back(make_pair(i,i-1));
    }
  }

  cout<<ans.size()<<'\n';
  for(auto x:ans){
    cout<<x.first<<' '<<x.second<<'\n';
  }
  
  return 0;
}

NO.6 ABC185E

\(f_{i,j}\) 表示 \(a\) 中前 \(i\)\(b\) 中前 \(j\) 最优。

则只有三种情况:

  1. \(a_i\)
  2. \(b_j\)
  3. 都不去
const int N=1005;
 
int n,m,a[N],b[N],f[N][N];
 
signed main(){
  //freopen();
  //freopen();
  IOS
  //int T;
  cin>>n>>m;
  F(1,i,1,n) cin>>a[i];
  F(1,i,1,m) cin>>b[i];
 
  memset(f,0x3f,sizeof f);
  f[0][0]=0;
  for(int i=1;i<=n;++i) f[i][0]=i;
  for(int i=1;i<=m;++i) f[0][i]=i;
  for(int i=1;i<=n;++i)
    for(int j=1;j<=m;++j){
      f[i][j]=Min(Min(f[i-1][j]+1,i+j),Min(f[i][j-1]+1,f[i-1][j-1]+(a[i]!=b[j])));
    }
  cout<<f[n][m]<<'\n';
  
  return 0;
}

NO.7 ARC102D

这题做了一年...之前就做了,结果WA了,然后又翻不了墙,看不了数据,也不知道错哪里。看题解又看不懂 \(3logn\),所以就放弃了。结果今天看了下发现,怎么会呢??

直接二进制就好了...

int n;
vector<tuple<int,int,int>> ans;
 
signed main(){
  IOS
  int l;
  cin>>l;
  int sum=1; n=1;
  for(int i=1;(1<<i)<=l;++i){
    sum<<=1,++n;
    ans.pb(make_tuple(i,i+1,1<<(i-1)));
    ans.pb(make_tuple(i,i+1,0));
  }
  int k=sum;
  for(int i=n;i;--i){
    if(l-sum>=k){
      ans.pb(make_tuple(i,n,sum));
      sum+=k;
    } k>>=1;
  }
  if(sum!=l) ans.pb(make_tuple(1,n,sum++));
 
  cout<<n<<' '<<ans.size()<<'\n';
  for(auto x:ans)
    cout<<get<0>(x)<<' '<<get<1>(x)<<' '<<get<2>(x)<<'\n';
  return 0;
}

NO.8 AGC016B

这题感觉真的很经典耶...看着好简单,但是却很搞心态啊...还是太逊了...

首先最后没有要求输出方案,所以不妨先排个序,从大到小(无所谓的)。

  1. 思考(手推)一下,如果某个颜色有重复,则这个位置的 \(ans\) 就是总颜色种类数;如果是单一颜色呢?那么就为 \(ans-1\)。所以最大值和最小值应该差不超过1。

  2. 如果最大和最小相等,则有两种情况。一是所有的颜色互不相同,则每个 \(ans\) 应该都等于 \(n-1\);二是每个颜色都有重复,那么总颜色数为 \(ans\),判断一下每个颜色是否能重复即可。

  3. 如果最大和最小不同,那么最大一定对应的是有重复的颜色,最小一定对应单一颜色。所以单一颜色数可以得到,总颜色数为 \(ans_1\),看看是否匹配即可。

const int N=100010;

int n,a[N];

signed main(){
  IOS
  cin>>n;
  for(int i=1;i<=n;++i)
    cin>>a[i];
  sort(a+1,a+1+n,greater<int>());

  int ans=1;
  if(a[1]-a[n]>1) ans=-1;
  else if(a[1]!=a[n]){//有重复颜色又有单一颜色
    //因为相同的看到的一定是最多的
    int t1=1,t2;
    for(int i=2;a[i]==a[i-1];++i)
      ++t1;
    t2=n-t1;//后t2种颜色一定不同,a[n]=a[1]-1
    
    int a1=a[1];//由a[1]推出的总颜色数
    int res=a1-t2;//有重复颜色的种类数
    if(res*2>t1||t1<2||a1<t2+1) ans=-1;//不存在此方案
  } else if(a[1]!=n-1){//要么都不同,要么都重复
    //考虑都重复,a[1]为总颜色种类数
    int dif=n-a[1];
    if(dif<a[1]) ans=-1;
  }
  
  cout<<(ans==-1?"No":"Yes")<<'\n';
  return 0;
}/*5 1 2 1 2 1*/

posted @ 2023-08-20 16:18  ComplexityMFC  阅读(45)  评论(0编辑  收藏  举报