zoukankan      html  css  js  c++  java
  • USACO 5.4 章节

    Canada Tour

    题目大意

    双向连通图,点从左向右排列,

    你需要先从最左的点到最右的点,(过程中只能从左向右走)

    然后再从最右的点返回最左的点,(过程中只能从右向左走)

    过程中除了最左的点,其它点都至多能经过一次

    求最多能经过的点的个数

    题解

    从右向左走反过来,就是说从左向右走,题目变成从最左两条不相交到达最右的路径,经过最多的点

    一个问题是如何解决没有重复的点

    这里的解决方案是

    dp[i][j]表示没有重复的点的情况下 一条路径走到点i,一条路径走到点j,经过的点的最大的个数

    在状态转移的时候需要保证新的状态有i<j,

    dp[i][j] = dp[i][k]+1 ,如果k->j有路径, 我们保证了除了初始点dp[0][0]=1以外,任何i不等于0,有dp[i][i] = 0,

    证明一下

    首先任何可达的状态不会遗漏,假设存在路径 一边到i,一边到j,(不妨设i<j)那么有它的来源一定能从[i][k]

    再不重复点证明

    抛开初始点

    因为保证了i<j,dp[i][j]的来源仅为dp[i][k],我们有k一定不等于i,所以只要dp[i][k]是没有重复点的即可

    因此递归可证明,这样的dp是不会经过重复点,

    最后考虑都到达最右的点,那么发现和dp[i][最右]的 经过的点数一致,注意的是 注意判断点i到最右点是否有路径

    #include <bits/stdc++.h>
    #define rep(i,a,n) for (int i=a;i<n;i++)
    #define per(i,a,n) for (int i=n-1;i>=a;i--)
    
    using namespace std;
    
    const string filename = "tour";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    char s[210],t[210];
    map<string,int>str2idx;
    int n,m,mp[110][110],dp[110][110];
    int main(){
      usefile();
      scanf("%d%d",&n,&m);
      rep(i,0,n){
        scanf("%s",s);
        str2idx[s]=i;
      }
      rep(i,0,m){
        scanf("%s %s",s,t);
        mp[str2idx[s]][str2idx[t]]=1;
        mp[str2idx[t]][str2idx[s]]=1;
      }
      int ans=1;
      dp[0][0]=1;
      rep(i,0,n){
        rep(j,i+1,n){
          rep(k,0,j){
            if(mp[j][k]&&dp[i][k]&&dp[i][k]+1>dp[i][j]){
              dp[i][j]=dp[j][i]=dp[i][k]+1;
            }
          }
        }
        if(mp[i][n-1]){
          ans=max(ans,dp[i][n-1]);
        }
      }
      printf("%d
    ",ans);
    }
    

    Character Recognition

    题目大意

    先提供空格和26个小写字母的 字符画01矩阵,每个字符都是20*20

    然后 你需要解析一段n*20字符矩阵,n行20列

    这段矩阵和标准的差异是,

    1. 对于一个字符,可能某一行被倍增了 变成21行,它紧接着倍增那行
    2. 对于一个字符,可能某一行被吞了 变成19行
    3. 0 和 1 和真实值不同

    上面问题可以存在的组合有,和原始完全一致,单纯1,单纯2,1+3,2+3,其中 0和1 的改变率小于等于30%

    题目呢,可以说相当于 USACO帮我们建了个OCR的模型!!!我们在该模型下实现算法

    题解

    f[i]表示从最开始到第i行最小误差

    f[i] = min(f[i-19]+19行来匹配,f[i-20]+20行来匹配,f[i-21]+21行来匹配)

    我们预先处理 所有字符的行(27*20) 和 目标匹配的行N

    O(N*27*20)

    然后 直接dp,O(N*(20*3)) 理论上如果做了前缀和后缀和优化

    实现如下

    #include <bits/stdc++.h>
    #define rep(i,a,n) for (int i=a;i<n;i++)
    #define per(i,a,n) for (int i=n-1;i>=a;i--)
    
    using namespace std;
    
    const string filename = "charrec";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n,m;
    char str[]=" abcdefghijklmnopqrstuvwxyz";
    char s[30][30][30];// 标准字符集[idx][i][j]
    char t[1210][30]; // 目标字符串[i][j]
    int diff[30][30][1210]; // 预处理 [字符idx][字符的i行][目标的j行] = 01差异和
    tuple<int,int,int>dp[1210]; // {最小代价,父节点,字符}
    const int SUP = 1000000;
    
    
    // 从st行开始匹配len行,返回{最小的代价,匹配的字符};
    pair<int,int> solve(int st,int len){
      pair<int,int>ret= {SUP,-1};
      rep(i,0,27){
        if(len==20){
          int sum=0;
          rep(k,0,20){
            sum+=diff[i][k][st+k];
          }
          ret= min(ret,{sum,i});
        }else{
          // 这边重复计算了, 这里可以用前缀和 后缀和继续优化, 目测可以优化掉约10-20倍性能
          // 不过因为USACO的数据比较小 这样已经是0.1s内了 就没写优化了
          rep(j,0,20){ // 枚举删掉或增加的行
            int p=st,sum=0;
            rep(k,0,j){
              sum+=diff[i][k][p++];
            }
            if(len==21){ // 19为删掉 21为增加
              sum+=diff[i][j][p++];
              sum+=diff[i][j][p++];
            }
            rep(k,j+1,20){
              sum+=diff[i][k][p++];
            }
            ret= min(ret,{sum,i});
          }
        }
      }
      return ret;
    }
    
    int main() {
      ios::sync_with_stdio(false);
      freopen("font.in","r",stdin);
      freopen("charrec.out","w",stdout);
      scanf("%d",&n);
      rep(idx,0,27){
        rep(i,0,20){
          scanf("%s",s[idx][i]);
        }
      }
      fclose(stdin);
      freopen("charrec.in","r",stdin);
      scanf("%d",&m);
      rep(i,0,m){
        scanf("%s",t[i]);
        dp[i] = {SUP,0,0};
      }
      // 预处理 把每个字符的每一行 都和 目标字符比
      // 目标k行 和 第x个字符 的y行 比较不同的01个数
      rep(idx,0,27){
        rep(i,0,20){
          rep(mm,0,m){
            rep(j,0,20){
              diff[idx][i][mm]+=s[idx][i][j]!=t[mm][j];
            }
          }
        }
      }
      rep(i,18,m){
        rep(len,19,22){
          auto [cost,idx]=solve(i-len+1,len);
          dp[i] = min(dp[i], {cost+(i-len<0?0:get<0>(dp[i-len])),i-len,idx});
        }
      }
      vector<char>ans;
      int i=m-1;
      do{
        ans.push_back(str[get<2>(dp[i])]);
      }while((i=get<1>(dp[i]))>0);
      per(itr,0,ans.size()){
        printf("%c",ans[itr]);
      }
      printf("
    ");
      return 0;
    }
    

    Telecowmunication

    题目大意

    100点,无向图

    网络流,最小字典序的最小割点

    记得前不久才有一个USACO的 最大流问题

    题解

    老生常谈了,=。=难道是我练题的顺序不对,感觉在刚刚学完最大流 最小割的时候,就会学到拆点啊。

    然后直接最小割点就出来了,然后字典序就依次枚举 再计算?想了想编码似乎不可行 1 + 100 vs 2+3若都是可行的,显然前面的字典序小

    #include <bits/stdc++.h>
    #define rep(i,a,n) for (int i=a;i<n;i++)
    #define per(i,a,n) for (int i=n-1;i>=a;i--)
    
    using namespace std;
    
    const string filename = "telecow";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n,m,c1,c2;
    
    int p2p[210][210];
    
    int vis[210];
    int flow[210][210];
    
    void clearvis(){
      rep(i,1,2*n+1){
        vis[i]=false;
      }
    }
    
    void dup(){
      rep(i,1,2*n+1){
        rep(j,1,2*n+1){
          flow[i][j]=p2p[i][j];
        }
      }
    }
    
    int stk[210];
    
    int bfs(int idx,int dst){
      clearvis();
      int st = 0,rear=0;
      stk[rear++]=idx;
      vis[idx] = true;
      while(st<rear){
        int p = stk[st];
        rep(i,1,n*2+1){
          if(vis[i])continue;
          if(flow[p][i]){
            if(i == dst){
              return true;
            }
            stk[rear++]=i;
            vis[i]=true;
          }
        }
        st++;
      }
      return false;
    }
    
    int dfs(int idx,int dst){
      if(idx == dst){
        return 1;
      }
      vis[idx] = true;
      rep(i,1,2*n+1){
        if(vis[i])continue;
        if(!flow[idx][i])continue;
        int r = dfs(i,dst);
        if(r){
          flow[idx][i] -= r;
          flow[i][idx] += r;
          return r;
        }
      }
      return 0;
    }
    
    int maxflow(){
      int ret =0;
      while(bfs(c1*2,c2*2-1)){
        clearvis();
        ret+=dfs(c1*2,c2*2-1);
      }
      return ret;
    }
    
    void addp(int p1,int p2){
      int p1i=p1*2-1;
      int p1o=p1*2;
      int p2i=p2*2-1;
      int p2o=p2*2;
      if(p1!=c1 && p2 != c2){
        p2p[p2o][p1i] = 1;
      }
      if(p1!=c2 && p2 != c1){
        p2p[p1o][p2i] = 1;
      }
    }
    
    int main(){
      usefile();
      cin>>n>>m>>c1>>c2;
      rep(i,0,m){
        int a,b;
        scanf("%d %d",&a,&b);
        addp(a,b);
      }
      rep(i,1,n+1){
        p2p[i*2-1][i*2]=1;
      }
      dup();
      int ans = maxflow();
      cout<<ans<<endl;
      vector<int>ps;
      rep(i,1,n+1){
        if(i== c1 || i==c2){
          continue;
        }
        dup();
        flow[i*2-1][i*2]=0;
        int ret = maxflow();
        if(ret == ans-1){
          ps.push_back(i);
          ans-=1;
          p2p[i*2-1][i*2]=0;
        }
      }
      rep(i,0,ps.size()){
        printf("%d%c",ps[i]," 
    "[i==ps.size()-1]);
      }
      return 0;
    }
    

    总结

    第一题的DP的方法,我要是打cf没遇到,估计是想不出怎么处理路径不重复点 的 这样的状态转移

    第二题的DP实现没啥好说的,但这样一个OCR模型 感觉也是很“实际”

    第三题 emmmm 感觉刚学完网络流的时候 就知道拆点,好像没什么特别的。

  • 相关阅读:
    MyBatis(六)缓存机制 之 缓存的相关属性设置
    MyBatis(六)缓存机制 之 缓存机制简介
    MyBatis(五)动态SQL 之 批量操作(插入)
    MyBatis(五)动态SQL 之 批量操作(删除)
    MyBatis(六)缓存机制 之 整合第三方缓存
    MyBatis(六)缓存机制 之 二级缓存
    MyBatis(五)动态SQL 之 批量操作(查询)
    MyBatis(六)缓存机制 之 缓存原理图
    MyBatis(六)缓存机制 之 一级缓存
    MyBatis(五)动态SQL 之 批量操作(更新)
  • 原文地址:https://www.cnblogs.com/CroMarmot/p/11001252.html
Copyright © 2011-2022 走看看