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
,看来直接暴搜是不太可能
考虑 一个成功的方案, 把它的非首行的两行交换,也是合法的
所以考虑第一列也定为1
到N
,最后乘上(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\n"); 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\n", int(ans.size())); rep(i,0,ans.size()){ if(ans[i] == N - 1) { printf("%d %d %d %d\n", P[0].first, P[0].second, P[ans[i]].first, P[ans[i]].second); }else{ printf( "%d %d %d %d\n", 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
- 这里的一个技巧是,想象在左边在多出一列,把这列从上连到下,这样原题就变成确定了一部分路线的欧拉回路的插头dp了,这样 起始和终点的就也和中间的过程块,看作联通其它两个块的 来处理了
- 状态表示,最无脑的是,相同数字,但是 这样的话状态转移会比较难写 比如
100120332
,注意到我们会一直维持合法性,所以可以用左右括号表示(__)(_())
,这样的化就是3进制
即可,然后4进制
更容易操作,比如我下面用的位运算<<2 或 >>2
- 在上面两个优化下 初始状态是
(______)
这样的
时间复杂度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\n",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题
本文其它链接
我的 cnblogs: https://www.cnblogs.com/CroMarmot/p/11149313.html
我的 github: https://yexiaorain.github.io/Blog/2019-07-07-USACO-6.5/