2019-2020 ICPC Southwestern European Regional Programming Contest (SWERC 2019-20)部分题解

比赛链接
躺狗实锤,只会两题
G.Swapping Places
大意:给你一个长度为 n n n的字符串序列,字符串种类为 s s s,给你 l l l组关系。每组关系由两个字符串 a , b a,b a,b构成,若 a , b a,b ab相邻,则可以交换这两个字符串。问你这个字符串序列的最终能 变成的字典序最小的序列是什么。
思路:字符串不好看,所以先离散化成数组再搞。
一个比较明显的思路是 由于种类数最多200,所以可以考虑枚举每一位最小能放的字符串是什么就行。
然后考虑具体实现。
我们考虑在枚举到第i位的时候,放第j小的字符串是否合法? [ 1 , i − 1 ] [1, i-1] [1,i1]位已经放完了。那么肯定是要把下标最小的且没用过的 j j j移到第 i i i位,那么显然, 1 − i 1-i 1i中所有没用过的字符串都要能与j互相交换才行。暴力来 c h e c k check check显然是不可取的。一种可以实现的优化方式是,当用过一个第 x x x位的字符串的时候, x x x位的字符串会对 x + 1 − n x+1-n x+1n的字符串产生影响,因为,后面的字符串可能因为这个字符串而不能移动到 x x x位之前。那么用一个 b i t bit bit维护第 i i i种字符串在区间 [ 1 , r ] , 1 ≤ r ≤ n [1,r],1\leq r\leq n [1,r]1rn内已经用掉的且不能交换的字符串数量。
之前预处理一下每一位上的字符串,在他前面不能交换的字符串的数量 就行。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
#define fi first
#define se second
#define pb push_back
string a[N],c[N];
int n,b[N],s,l,ed[222][222],ans[N];
vector<int>v[202];
int len[202],vis[N],cnt[N],dis[N];
int t[202][N];
void add(int x,int y,int z){
  for(;y<N;y+=y&-y)t[x][y]+=z;
}
int get(int x,int y){
  int z=0;
  for(;y;y-=y&-y)z+=t[x][y];
  return z;
}
int main() {
  ios::sync_with_stdio(false);
  cin>>s>>l>>n;
  for(int i=1;i<=s;i++){
    cin>>a[i];
    c[i]=a[i];
    ed[i][i]=1;
  }
  sort(c+1,c+1+s);
  for(int i=1;i<=l;i++){
    string x,y;
    cin>>x>>y;
    int dx=lower_bound(c+1,c+1+s,x)-c;
    int dy=lower_bound(c+1,c+1+s,y)-c;
    ed[dx][dy]=1;
    ed[dy][dx]=1;
  }
  for(int i=1;i<=n;i++){
    string x;
    cin>>x;
    b[i]=lower_bound(c+1,c+1+s,x)-c;
    v[b[i]].pb(i);
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=s;j++){
      if(!ed[b[i]][j])dis[i]+=cnt[j];
    }
    cnt[b[i]]++;
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=s;j++){
      if(len[j]>=v[j].size())
        continue;;
      int x=v[j][len[j]];
      int st=0;
      if(dis[x]!=get(j,x)){
        st=1;
      }
      if(!st){
        ans[i]=j;
        vis[x]=1;
        len[j]++;
        for(int k=1;k<=s;k++){
          if(!ed[j][k]){
            add(k,x,1);
          }
        }
        break;
      }
    }
  }
  for(int i=1;i<=n;i++){
    cout<<c[ans[i]]<<' ';
  }
  return 0;
}

K.Birdwatching
题意:自己读
思路:
暴力的做法:枚举每一条跟t的边,然后随便dfs一下。
显然会T。
那么仔细考虑一下。如果一条边 ( s , t ) (s,t) (s,t)不合法的话,那么 s s s必然可以通过经过其他边到达 t t t,就会存在类似这样的路径 s − > . . . . − > q − > t s->....->q->t s>....>q>t
那我们建反边,从与t相邻的点出发dfs,设起始点为 a a a,dfs的过程中不能第二次经过 a a a,剩下能到达的点显然都可以通过 x − > . . . . − > a − > t x->....->a->t x>....>a>t的路径到达t,所以都是不合法的点。另外每个点在所有dfs中最多只需要访问两次。因为,在从某个起始点出发之后,可以访问到其他点,但是,这个起始点需要其他起始点来check自己是不是合法点。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
#define fi first
#define se second
#define pb push_back
vector<int> e[N],ans,V;
int vis[N],n,m,t,is_link[N],al[N];
void dfs(int x,int _y){
  for (auto y : e[x]){
    if (vis[y] || y==_y ||al[y]>1)
      continue;
    V.pb(y);
    al[y]++;
    vis[y] = 1;
    is_link[y]=1;
    dfs(y,_y);
  }
}
int main(){
  ios::sync_with_stdio(false);
  cin>>n>>m>>t;
  for(int i=1;i<=m;i++){
    int x,y;
    cin>>x>>y;
    e[y].pb(x);
  }
  vis[t] = 1;
  for (auto x : e[t]){
    V.clear();
    dfs(x,x);
    for(auto k:V)vis[k]=0;
  }
  for (auto x : e[t]){
    if (!is_link[x]){
      ans.pb(x);
    }
  }
  cout<<(int)ans.size()<<'\n';
  sort(ans.begin(),ans.end());
  for (auto x : ans){
    cout<<x<<'\n';
  }
  return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值