zoukankan      html  css  js  c++  java
  • USACO 6.5 章节 世界上本没有龙 屠龙的人多了也便有了

    All Latin Squares

    题目大意

    n x n矩阵(n=2->7)

    第一行1 2 3 4 5 ..N

    每行每列,1-N各出现一次,求总方案数

    题解

    n最大为7 显然打表

    写了个先数值后位置的暴搜

    #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 = "latin";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n;
    int v[10][10]; // [row][col]
    
    int vis[10][10]; // [val][col]
    
    int anscnt =0;
    
    void dfs(int val,int row){
      if(val > n){
        anscnt++;
        return ;
      }
      rep(j,1,n+1){
        if(v[row][j] == 0 && !vis[val][j]){
          v[row][j] = 1;
          vis[val][j] = 1;
          if(row+1 < n){
            dfs(val,row+1);
          }else{
            dfs(val+1,1);
          }
          vis[val][j] = 0;
          v[row][j] = 0;
        }
      }
    }
    
    int main(){
      //  usefile();
      cin>>n;
      rep(i,1,n+1){
        v[0][i]=i;
        vis[i][i]=1;
      }
      dfs(1,1);
      cout<<anscnt<<endl;
      return 0;
    }
    

    6能搜出来,7要等好久,考虑改算法

    但是数列嘛,当然先上OEIS看看http://oeis.org/search?q=1%2C2%2C24%2C1344%2C1128960&sort=&language=english&go=Search

    7的时候答案是12198297600,看来直接暴搜是不太可能

    考虑 一个成功的方案, 把它的非首行的两行交换,也是合法的

    所以考虑第一列也定为1N,最后乘上(N-1)!

    这样计算的次数是16942080

    在我i7-7700HQ上跑是

    > time echo 7 | ./6.5.1
    12198297600
    
    real	0m17.194s
    user	0m17.190s
    sys	0m0.006s
    

    固定第一列的代码

    #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 = "latin";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n;
    int v[10][10]; // [row][col]
    
    // [0->n-1][1->n]
    int vis[10][10]; // [val][col]
    
    long long anscnt =0;
    
    void dfs(int val,int row){
      if(val > n){
        anscnt++;
        return ;
      }
      if(val == row+1){
        if(row+1 < n){
          dfs(val,row+1);
        }else{
          dfs(val+1,1);
        }
        return ;
      }
      rep(j,1,n+1){
        if(v[row][j] == 0 && !vis[val][j]){
          v[row][j] = 1;
          vis[val][j] = 1;
          if(row+1 < n){
            dfs(val,row+1);
          }else{
            dfs(val+1,1);
          }
          vis[val][j] = 0;
          v[row][j] = 0;
        }
      }
    }
    
    int main(){
      //  usefile();
      cin>>n;
      rep(j,1,n+1){
        v[0][j]=j;
        vis[j][j]=1;
      }
      rep(i,1,n){
        v[i][1]=i+1;
        vis[i+1][1]=1;
      }
      dfs(1,1);
      rep(i,1,n){
        anscnt*=i;
      }
      cout<<anscnt<<endl;
      return 0;
    }
    

    网上搜了一下方法,有一种是 靠循环节 置换圈 大概群论相关

    而我决定 放弃,打表吧XD

    #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 = "latin";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n;
    
    map<int,long long >ans;
    
    int main(){
      usefile();
      ans[2]= 1;
      ans[3]= 2;
      ans[4]= 24;
      ans[5]= 1344;
      ans[6]= 1128960;
      ans[7]= 12198297600;
      cin>>n;
      cout<<ans[n]<<endl;
      return 0;
    }
    

    Closed Fences

    题目大意

    计算几何

    逆时针给一系列点(<=200个),坐标范围(| |<=2^16)检查 这些点是否能围成一个围栏

    如果可行,再给一点 输出 从该点发出射线,能照到的线段 (只要部分能被照到 就输出整段)

    如果 给的点,和一条线段共线,视作无法照到

    题解

    看上去像是可以枚举,按照角度,200次遍历射线,每次射线遍历200条边,一共就n方左右的复杂度

    我们先 过一边所有线段如果不相交,那么可以围成(这里没有判断顺时针还是逆时针)

    然后枚举所有线段,对每一条线段1000等分,枚举从给定的点 向等分点是否与其它线段有交点

    时间复杂度O(n*n*1000);

    [这题的核心还是说写计算几何的算法

    #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 = "fence4";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    
    pair<int,int> p;
    pair<int,int> P[210];
    int N;
    
    
    double cross(pair<double,double> a,pair<double,double> b){
      return a.first*b.second-a.second*b.first;
    }
    
    pair<double,double> operator + (pair<double,double> a,pair<double,double> b)  {
      return {a.first+b.first,a.second+b.second};
    }
    
    pair<double,double> operator - (pair<double,double> a,pair<double,double> b)   {
      return {a.first-b.first,a.second-b.second};
    }
    
    bool isIntersect(pair<pair<double,double>,pair<double,double>> line1,pair<pair<double,double>,pair<double,double>> line2){
      return  !(
          (cross(line1.first -line2.first,line2.second-line2.first) > 0) ==
          (cross(line1.second-line2.first,line2.second-line2.first) > 0) ||
          (cross(line2.first -line1.first,line1.second-line1.first) > 0) ==
          (cross(line2.second-line1.first,line1.second-line1.first) > 0)
          );
    
    }
    
    bool isValid(){
      rep(i,0,N){
        rep(j,i+2,N){
          if(i == 0 && j == N-1)continue;
          if(isIntersect({P[i],P[i+1]}, {P[j],P[(j+1)%N]})){
            return false;
          }
        }
      }
      return true;
    }
    
    // 共线
    bool isColine(int idx){
      return cross(P[idx]-P[(idx+1)%N],p) == cross(P[idx],P[(idx+1)%N]);
    }
    
    bool isSeen(int idx){
      int x1 = P[idx].first;
      int y1 = P[idx].second;
      int nsegments = 1000;
      rep(i,1,nsegments){
        pair<double,double> point_ =  {
          x1 + i * 1.0 / nsegments * (P[(idx + 1) % N].first  - x1),
          y1 + i * 1.0 / nsegments * (P[(idx + 1) % N].second - y1)};
        bool blocked = false;
        rep(j,0,N){
          if(j == idx)      {
            continue;
          }
          if(isIntersect({p,point_}, {P[j],P[(j+1)%N]})){
            blocked = true;
            break;
          }
        }
        if(!blocked)    {
          return true;
        }
      }
      return false;
    }
    
    vector<int> ans;
    int main(){
      usefile();
      scanf("%d", &N);
      scanf("%d %d", &p.first,&p.second);
      rep(i,0,N){
        scanf( "%d %d", &P[i].first, &P[i].second);
      }
      if(!isValid())  {
        printf( "NOFENCE
    ");
        return 0;
      }
      rep(i,0,N){
        if(!isColine(i) && isSeen(i))    {
          ans.push_back(i);
        }
      }
      if(ans.size() >= 2 && ans[ans.size() - 2] == N - 2){
        ans[ans.size() - 2] = N - 1;
        ans[ans.size() - 1] = N - 2;
      }
      printf("%d
    ", int(ans.size()));
      rep(i,0,ans.size()){
        if(ans[i] == N - 1)    {
          printf("%d %d %d %d
    ", P[0].first, P[0].second, P[ans[i]].first, P[ans[i]].second);
        }else{
          printf( "%d %d %d %d
    ", P[ans[i]].first, P[ans[i]].second, P[ans[i] + 1].first, P[ans[i] + 1].second);
        }
      }
      return 0;
    }
    

    Betsy's Tour

    题目大意

    n x n(n<=7) 的矩阵,从左上走到坐下,每个格子最多经过一次的方案数

    题解

    暴搜+打表

    插头dp ? 像是

    实现了一下 果然过了,注意处理n = 1

    插头dp的话 直接百度文库 应该能搜到不少文章,我感觉我的边界处理状态转移的代码写得好丑QAQ

    1. 这里的一个技巧是,想象在左边在多出一列,把这列从上连到下,这样原题就变成确定了一部分路线的欧拉回路的插头dp了,这样 起始和终点的就也和中间的过程块,看作联通其它两个块的 来处理了
    2. 状态表示,最无脑的是,相同数字,但是 这样的话状态转移会比较难写 比如100120332,注意到我们会一直维持合法性,所以可以用左右括号表示(__)(_()),这样的化就是3进制即可,然后4进制更容易操作,比如我下面用的位运算<<2 或 >>2
    3. 在上面两个优化下 初始状态是(______)这样的

    时间复杂度O(i*j*state) = O(7*7*3^7)

    #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 = "betsy";
    
    void usefile(){
      freopen((filename+".in").c_str(),"r",stdin);
      freopen((filename+".out").c_str(),"w",stdout);
    }
    
    int n;
    
    // 中间的 每个块 连接 两个方向,
    
    map<pair<int,long long>,long long> res;
    
    int get_state(long long state,int pos){
      return (state >> (2*pos)) % 4;
    }
    
    int set_state(long long state,int pos){
      return state << (2*pos);
    }
    
    // ((    ) ())
    // block: 的2进制位 0b3210
    //   3
    //  0 2
    //   1
    long long insert_state(long long old_state,int pos,int block){
      int arr[10];
      // 栈计算括号对
      int p[10];
      int stk[10];
      int stki = 0;
      rep(i,0,n+1){
        arr[i] = get_state(old_state,i);
        if(arr[i] == 0)continue;
        if(arr[i] == 1)stk[stki++]=i;
        if(arr[i] == 2){
          p[i] = stk[stki-1];
          p[stk[stki-1]] = i;
          stki--;
        }
      }
      int cnt = 0;
      rep(i,0,4){
        cnt+= !!(block & (1<<i));
      }
      assert(cnt == 2);
      // 插入的块和 被插入的位置 同时有或没有
      if( (!(block & 0b1000) != !arr[pos]) || (!(block & 0b0001) != !arr[pos+1]) ){
        return -1;
      }
      // 原来为空 新建边
      if( (block & 0b0100) && (block & 0b0010)){
        arr[pos] = 1;
        arr[pos+1] = 2;
      }else if( (block & 0b0100) || (block & 0b0010) ){ // 引出一条原来的边
        if(block & 0b0100){
          arr[pos] = arr[pos]+arr[pos+1];
          arr[pos+1] = 0;
        }else {
          arr[pos+1] = arr[pos]+arr[pos+1];
          arr[pos] = 0;
        }
      }else{ // 把原来两段 连接起来
        if(arr[pos] == 1 &&  arr[pos+1] == 2){
          arr[pos] = 0 ;
          arr[pos+1] = 0 ;
        }else{
          arr[pos] = 0;
          arr[pos+1] = 0;
          int p1 = p[pos];
          int p2 = p[pos+1];
          if(p1 > p2)swap(p1,p2);
          arr[p1] = 1;
          arr[p2] = 2;
        }
      }
      long long new_state = 0;
      rep(i,0,n+1){
        new_state += set_state(arr[i],i);
      }
      return new_state;
    }
    
    long long dp(int idxi,int idxj,long long state){
      long long &ret = res[{(idxi<<3)+idxj,state}];
      if(ret != 0){
        return ret;
      }
        // 右下角允许自连
      if(idxj == n-1 && idxi == n-1) {
        return ret = (get_state(state,n-1) == 1 && get_state(state,n) == 2);
      }
      // 过程中不允许自连
      //
      if (get_state(state,idxi) == 1 && get_state(state,idxi+1) == 2)return 0;
      rep(i,0,4){
        rep(j,i+1,4){
          int new_state = insert_state(state,idxi,(1<<i)+(1<<j));
          if(new_state == -1)continue;
          // 最后一列
          if(idxj == n-1) {
            if(get_state(new_state,idxi) != 0)continue;
          }
          // 最后一行
          if(idxi == n-1){
            if(get_state(new_state,idxi+1) != 0)continue;
            new_state <<=2;
            ret+=dp(0,idxj+1,new_state);
          }else{
            ret+=dp(idxi+1,idxj,new_state);
          }
        }
      }
      // printf("(%d,%d) {%lld,%lld,%lld} = %lld
    ",idxi,idxj,(state)%4,(state>>2)%4,(state>>4)%4,ret);
      return ret;
    }
    
    int main(){
      usefile();
      cin>>n;
      if(n == 1){
        cout<<1<<endl;
      }else{
        cout<<dp(0,0,set_state(1,1)+set_state(2,n))<<endl;
      }
      return 0;
    }
    

    总结

    6.5章其实有5个题,但后面两题 我很久很久很就以前做了 就不想再做一次了,所以这里只写了3题

    本文其它链接

    牛客:https://blog.nowcoder.net/n/c7f0a70d69254c9180dd0b7e475d1248

    github:https://yexiaorain.github.io/Blog/2019-07-07-USACO-6.5/

  • 相关阅读:
    [转载]Sublime Text 3 搭建 React.js 开发环境
    浏览器缓存之Expires Etag Last-Modified max-age详解
    第16周作业
    第15周作业
    第14周作业
    第13周作业集
    软件工程结课作业
    第13次作业--邮箱的正则表达式
    第12次作业--你的生日
    第11次作业--字符串处理
  • 原文地址:https://www.cnblogs.com/CroMarmot/p/11149313.html
Copyright © 2011-2022 走看看