写了一些没有具体算法的搜索题。
这种题目在离线赛中遇到可能会很难办了,
完全没有办法搞清楚最后能得多少分。
只能说是尽可能地剪枝并卡常,
然后祈祷能AC之类的。
某NOIP几乎不会有这种题,
不过可以用来写暴力(以及说不定就过了
附暴力踩标程一张:
T1 stick
分析
N≤65
,并且没有什么可以高效求解的算法。
搜索不加剪枝的话会被卡成智障。
那加剪枝啊:
1. 可行性:当前的答案肯定要整除所有木棍的长度和。
2. 最优性:搜索答案升序,搜到即退出。
3. 最大限制:木棍降序排序,以满足最快得到结果。
4. 排除重复:如果当前木棍是组成新木棍的第一根木棍,则其不可行代表当前方案不可行。
5. 排除重复:如果许多根木棍一样长,使用它们的效果是一样的,如果用某根不可行,其他同样不可行。
6. 排除重复:如果当前木棍完成了一根完整木棍的组装,而之后不可行代表当前方案不可行。
7. 奇偶性:如果答案为奇数,检查每次至少需要多少根奇数的木棒,若过少则代表当前方案不可行。
然后大约要加这么多剪枝才能过掉这道题的鬼畜数据。
代码
普通的DFS。
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 104
#define Mx 3444
#define INF 0x3f3f3f3f
#define chkmax(a,b) a=max(a,b)
void Rd(int &res){
char c;res=0;
while((c=getchar())<48);
do res=(res<<3)+(res<<1)+(c^48);
while((c=getchar())>47);
}
int n,A[M],Num[4];
bool Vis[M];
bool Cmp(int a,int b){return a>b;}
bool DFS(int need,int sum,int l,int len){
if(need==0)return 1;
if(len&1 && Num[1]<need-1)return 0;
REP(i,l,n){
if(Vis[i] || (i && !Vis[i-1] && A[i]==A[i-1]))continue;
if(sum+A[i]==len){
Vis[i]=1;Num[A[i]&1]--;
if(DFS(need-1,0,0,len))return 1;
Vis[i]=0;Num[A[i]&1]++;
return 0;
}
else if(sum+A[i]<len){
Vis[i]=1;Num[A[i]&1]--;
if(DFS(need,sum+A[i],i+1,len))return 1;
Vis[i]=0;Num[A[i]&1]++;
if(!sum)return 0;
}
}
return 0;
}
int main(){
while(1){
Rd(n);
if(!n)break;
int Sum=0;
memset(Num,0,sizeof(Num));
REP(i,0,n){
Rd(A[i]);
Sum+=A[i];
Num[A[i]&1]++;
}
sort(A,A+n,Cmp);
REP(i,A[n-1],Sum+1){
if(Sum%i)continue;
memset(Vis,0,sizeof(Vis));
if(DFS(Sum/i,0,0,i)){
printf("%d\n",i);
break;
}
}
}
return 0;
}
T2 buses
分析
根据某论文一大片的分析说明了某种写法的可行与另一种写法的不可行
大体来说即为先单确定路线的第一辆车,
搜索得到其第二辆车,再判断其可行性。
剪枝:
1. 可行性:一条线路必须可行(废话
2. 最优性:线路数大于当前答案时返回
3. 排除重复:如果两辆车到达时间相同,此时若某辆车是第一辆车,则之后的车只能成为第一辆车。
然后我们可以知道对车进行的搜索比对线路进行的搜索要优秀。
大约就是这样。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 304
#define INF 0x3f3f3f3f
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
int n,A[M],Ans=17;
int L[M],R[M];
bool Vis[M],Use[M];
void DFS(int num,int x){
if(num>=Ans)return;
if(x==n){
Ans=num;
return;
}
if(Vis[x]){
DFS(num,x+1);
return;
}
if(x && Use[x-1] && A[x]==A[x-1]){
Use[x]=1;
L[num]=A[x],R[num]=-1;
DFS(num+1,x+1);
Use[x]=0;
return;
}
REP(i,0,num){
if(R[i]==-1 && A[x]!=L[i]){
int Len=A[x]-L[i],Pos=A[x];
REP(j,x,n)if(!Vis[j] && A[j]==Pos)Pos+=Len;
if(Pos<A[n-1])continue;
R[i]=A[x];
Pos=R[i];
REP(j,x,n)if(!Vis[j] && A[j]==Pos)Vis[j]=1,Pos+=Len;
DFS(num,x+1);
Pos-=Len;
DREP(j,n-1,x-1)if(Vis[j] && A[j]==Pos)Vis[j]=0,Pos-=Len;
R[i]=-1;
}
}
Use[x]=1;
L[num]=A[x],R[num]=-1;
DFS(num+1,x+1);
Use[x]=0;
}
int main(){
scanf("%d",&n);
REP(i,0,n)scanf("%d",&A[i]);
sort(A,A+n);
DFS(0,0);
printf("%d\n",Ans);
return 0;
}
T3 Betsy’s tour
求矩阵从左上角到左下角的且遍历完所有格子的方案数。
剪枝是很明显的,
所有未经过的格子均应至少相邻两个未经过的格子(除终点
即不走将剩余格子分为两个连通块的那种格子。
然而每次扫一遍全图判断是很慢的,
然后发现每次只会修改周围四个格子的相邻格子数,
于是判断改为单次的。
再加上上面那个剪枝的修改型判断方式就能过了。
代码
防止数组越界就修改一下矩阵的位置。
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 14
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
int n,Sn;
int Ans;
bool Vis[M][M];
int Cnt[M][M];
int Xr[]={1,0,-1,0},Yr[]={0,1,0,-1};
bool Check(int x,int y){return x>1 && y>1 && x<n && y<n;}
void DFS(int x,int y,int t,int r){
if(x==n-1 && y==2){
Ans+=t==Sn;
return;
}
int px,py,xp=x+Xr[r],yp=y+Yr[r],a1=(r+1)%4,a2=(r+3)%4,mst=0;
if(Vis[xp][yp] && !Vis[x+Xr[a1]][y+Yr[a1]] && !Vis[x+Xr[a2]][y+Yr[a2]])return;
if(Vis[xp+Xr[a1]][yp+Yr[a1]] && !Vis[xp][yp] && !Vis[x+Xr[a1]][y+Yr[a1]])return;
if(Vis[xp+Xr[a2]][yp+Yr[a2]] && !Vis[xp][yp] && !Vis[x+Xr[a2]][y+Yr[a2]])return;
REP(g,0,4){
xp=x+Xr[g],yp=y+Yr[g];
if(xp==n-1 && yp==2)continue;
if(Check(xp,yp) && !Vis[xp][yp] && Cnt[xp][yp]==1)mst++;
}
if(mst>1)return;
REP(g,0,4){
xp=x+Xr[g],yp=y+Yr[g];
if(!Check(xp,yp) || Vis[xp][yp])continue;
if(mst && Cnt[xp][yp]!=1)continue;
Vis[xp][yp]=1;
REP(f,0,4){
px=xp+Xr[f],py=yp+Yr[f];
Cnt[px][py]--;
}
DFS(xp,yp,t+1,g);
REP(f,0,4){
px=xp+Xr[f],py=yp+Yr[f];
Cnt[px][py]++;
}
Vis[xp][yp]=0;
}
}
int main(){
scanf("%d",&n);Sn=n*n;n+=2;
REP(i,2,n) REP(j,2,n) REP(g,0,4)
Cnt[i][j]+=Check(i+Xr[g],j+Yr[g]);
Cnt[2][2]=0,Cnt[2][3]=Cnt[3][2]=2;
Vis[2][2]=1;
DFS(2,2,1,0);
printf("%d\n",Ans);
return 0;
}
T4 Backward Digit Sums
原题没有这么鬼畜的啊( n≤20
分析
各项系数是杨辉三角,
然后普通的枚举全排列就妥当地爆炸。
然后稍微不普通一点的DFS仍然会爆炸。
然后据说最后的代码随便卡一下还是会爆炸。
剪枝:
1. 对当前已枚举位作状态压缩,预处理出其上下界用于可行性剪枝
O(2n∗n)
2. 由于杨辉三角是对称的,题目又要求字典序最小,因此需要保证对称位上一直是左侧小于右侧。
3. 然后加上各种玄学常数优化就能卡过数据(比方说寻址代替运算
实际上效率还是挺高的。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(register int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(register int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 24
#define Mm 1144444
int n,T,Use[M],P[M],V[M];
int C[M][M],With[M],Mid;
long long tim;
bool Vis[M],Find;
int Mn[Mm],Mx[Mm],Num[Mm],MxS;
int BNum[Mm];
void Init(){
memset(Mn,0,sizeof(Mn));
memset(Mx,0,sizeof(Mx));
REP(i,0,1<<n){
int x,k;x=k=0;
REP(j,0,n)if(i&(1<<j))x++;
else P[k++]=j+1;
Num[i]=x;
if(x==n)break;
int l=x,r=n-1;k=0;
while(l!=r){
if(C[n-1][r]<=C[n-1][l]) V[k++]=C[n-1][r--];
else V[k++]=C[n-1][l++];
}
V[k++]=C[n-1][l];
REP(j,0,k)Mn[i]+=P[k-j-1]*V[j];
REP(j,0,k)Mx[i]+=P[j]*V[j];
}
MxS=(1<<n)-1;
REP(i,0,n)BNum[1<<i]=i+1;
REP(i,Mid,n)With[i]=n-i-1;
}
void DFS(int S,int Sum){
if(Sum+Mx[S]<T || Sum+Mn[S]>T)return;
tim++;
if(S==MxS){
if(Sum==T){
REP(i,0,n)printf("%d ",Use[i]);puts("");
Find=1;
}
return;
}
if(Num[S]<Mid){
for(register int i=MxS^S,j=i&-i;i;i^=i&-i,j=i&-i){
Use[Num[S]]=BNum[j];
DFS(S|j,Sum+BNum[j]*C[n-1][Num[S]]);
if(Find)return;
}
}
else{
for(register int i=MxS^S,j=i&-i;i;i^=i&-i,j=i&-i){
if(BNum[j]<Use[With[Num[S]]])continue;
Use[Num[S]]=BNum[j];
DFS(S|j,Sum+BNum[j]*C[n-1][Num[S]]);
if(Find)return;
}
}
}
int main(){
REP(i,0,M-1){
C[i][0]=1;
REP(j,1,i+1)
C[i][j]=C[i-1][j-1]+C[i-1][j];
}
scanf("%d%d",&n,&T);
Mid=n&1?(n>>1)+1:(n>>1);
Init();
Find=0;
DFS(0,0);
return 0;
}
T5 ZPLHZ
分析
使用二分图匹配来进行判断的搜索,比较巧妙。
每次枚举当前区间加入到二分图中,查看其是否能够和所有炸弹匹配。
1. 降序枚举区间长度来加速到达近似解
2. 用DP解得炸弹重复使用的近似解来进行最优化剪枝。
3. 每次只对当前区间匹配,然后还原现场。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define M 104
void Rd(int &res){
char c;res=0;
while((c=getchar())<48);
do res=(res<<3)+(res<<1)+(c^48);
while((c=getchar())>47);
}
int n,m,k,x[M],y[M],u[M],v[M];
bool C[M][M][M];
int Dist[M];
int Ans;
int UL[M],UR[M];
int Re[M];
bool Vis[M];
bool Find(int l,int r,int x){
REP(i,0,m) if(!Vis[i] && C[i][l][r]){
Vis[i]=1;
if(Re[i]==-1 || Find(UL[Re[i]],UR[Re[i]],Re[i])){
Re[i]=x;
return 1;
}
Vis[i]=0;
}
return 0;
}
bool Check(int num){
memset(Vis,0,sizeof(Vis));
return Find(UL[num],UR[num],num);
}
void DFS(int pos,int num){
if(pos==n){
chkmin(Ans,num);
return;
}
if(Dist[pos+1]+num>=Ans)return;
UL[num]=pos;
DREP(i,n-1,pos-1){
UR[num]=i;
if(Check(num)){
DFS(i+1,num+1);
REP(i,0,m)if(Re[i]==num){
Re[i]=-1;
break;
}
}
}
}
inline int Sqr(int x){
return x*x;
}
int main(){
Rd(n),Rd(m),Rd(k);k*=k;
REP(i,0,n)Rd(x[i]),Rd(y[i]);
REP(i,0,m)Rd(u[i]),Rd(v[i]);
REP(i,0,m)REP(j,0,n)C[i][j][j]=(Sqr(x[j]-u[i])+Sqr(y[j]-v[i])<=k);
REP(i,0,m)REP(j,0,n)REP(k,j+1,n) C[i][j][k]=C[i][j][k-1]&&C[i][k][k];
memset(Dist,63,sizeof(Dist));
Dist[n]=0;
DREP(i,n-1,-1) REP(j,i,n) REP(k,0,m) if(C[k][i][j]) chkmin(Dist[i],Dist[j+1]+1);
memset(Re,-1,sizeof(Re));
Ans=m;
DFS(0,0);
printf("%d\n",Ans);
return 0;
}