怎么枚举
字面意思,枚举可能出现的各种情况。
example
枚举全排列
枚举某一种错误的情况
枚举是否被标记过及标记的数量
枚举初始情况
…
为什么枚举
1. 1. 1.骗分(使用搜索算法过小数据)
2. 2. 2.数据范围很小,可以打表
3. 3. 3.正解(具体方法将在下文讲到)
什么时候枚举
1. 1. 1.数据某一个参数的范围很小
2. 2. 2.枚举后可以 O ( 1 ) O(1) O(1)或 O ( n ) O(n) O(n)做。
3. 3. 3.不枚举的话没有任何最优解
具体每一种思路的实现
枚举全排列
典例:搬迁
题目大意:一张图,有 k k k个特殊点 ( 1 ≤ k ≤ 5 ) (1\le k\le 5) (1≤k≤5),要求选一个点,使得从这个点出发,经过所有的特殊点,再回到该点的距离最小。
解析:观察到 k k k的范围极小,故我们可以想到枚举经过 k k k个点的顺序,再枚举每一个初始点,跑单源最短路径。
但是对每一个初始点跑单源最短路并不现实,所以我们对每个特殊点跑一遍单源最短路,枚举全排列 a [ ] a[] a[],在枚举我们的初始点,设初始点为 s s s,特殊点为 t 1 , t 2 , . . . , t k t_1,t_2,...,t_k t1,t2,...,tk,特殊点 k i k_i ki到每个点的最短路为 d [ i ] [ ] d[i][] d[i][]那么我们的答案就为
d
[
a
[
1
]
]
[
s
]
+
d
[
a
[
1
]
]
[
a
[
2
]
]
+
d
[
a
[
2
]
]
[
a
[
3
]
]
+
.
.
.
+
d
[
a
[
k
−
1
]
]
[
a
[
k
]
]
+
d
[
a
[
k
]
]
[
s
]
d[a[1]][s]+d[a[1]][a[2]]+d[a[2]][a[3]]+...+d[a[k-1]][a[k]]+d[a[k]][s]
d[a[1]][s]+d[a[1]][a[2]]+d[a[2]][a[3]]+...+d[a[k−1]][a[k]]+d[a[k]][s]
更新最大值即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int first[100005],nxt[100005],to[100005],w[100005],tot=0,ans=2147483647;
int n,m,k,d[6][100005],vis[100005],a[100005],s[15],VIS[15];
int Read(){
int x;
scanf("%lld",&x);
return x;
}
void Add(int x,int y,int z){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
void dijkstra(int f){
priority_queue<pair<int,int> > q;
memset(vis,0,sizeof(vis));
d[f][a[f]]=0;
q.push(make_pair(0,a[f]));
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]==1) continue;
vis[u]=1;
for(int e=first[u];e;e=nxt[e]){
int v=to[e];
if(d[f][v]>d[f][u]+w[e]){
d[f][v]=d[f][u]+w[e];
q.push(make_pair(-d[f][v],v));
}
}
}
}
void check(){
int dis=2147483647;
for(int i=1;i<=n;i++){
if(i==a[1]||i==a[2]||i==a[3]||i==a[4]||i==a[5]) continue;
int q=d[s[1]][i];
for(int j=2;j<=k;j++){
q+=d[s[j]][a[s[j-1]]];
}
q+=d[s[k]][i];
//cout<<i<<" "<<q<<" "<<s[1]<<" "<<s[2]<<" "<<s[3]<<endl;
dis=min(dis,q);
}
ans=min(ans,dis);
}
void dfs(int x){
if(x==k+1){
check();
return ;
}
for(int i=1;i<=k;i++){
if(!VIS[i]){
VIS[i]=1;
s[x]=i;
dfs(x+1);
VIS[i]=0;
s[x]=0;
}
}
}
signed main(){
memset(d,0x7f,sizeof(d));
n=Read(),m=Read(),k=Read();
for(int i=1;i<=k;i++){
a[i]=Read();
}
for(int i=1;i<=m;i++){
int x=Read(),y=Read(),z=Read();
Add(x,y,z);
Add(y,x,z);
}
for(int i=1;i<=k;i++){
dijkstra(i);
}
/*for(int i=1;i<=k;i++){
for(int j=1;j<=n;j++){
cout<<d[i][j]<<" ";
}
cout<<endl;
}*/
dfs(1);
cout<<ans<<endl;
}
枚举每一种错误的情况
典例:Barracuda
题目大意:有 n n n个物品 ( n ≤ 100 ) (n\le 100) (n≤100),每个物品有一个重量,有 n + 1 n+1 n+1次称量结果,每次称一些物品,称出重量为 w w w,其中有一次是错误的,问是否有唯一确定解和最重的物品编号是多少。
考虑没有错误的情况,那么我们的题意就是求解关于 n n n个未知数的 n n n个方程,可以高斯消元 O ( n 3 ) O(n^3) O(n3)做,但现在有一个错误方程,我们容易想到 O ( n ) O(n) O(n)枚举错误的方程,由于 n ≤ 100 n\le100 n≤100, O ( n 4 ) O(n^4) O(n4)刚好可以卡过。
#include<bits/stdc++.h>
using namespace std;
double a[105][105],b[105][105],w[105];
int flag=0,m[105],f[105][105],n,Num,k,x;
int solve(){
for(int i=1;i<=n;i++){
int maxn=i;
for(int j=i+1;j<=n;j++){
if(fabs(a[j][i])>fabs(a[maxn][i]))
maxn=j;
}
for(int j=1;j<=n+1;j++){
swap(a[i][j],a[maxn][j]);
}
if(!a[i][i]) return 0;
for(int j=1;j<=n;j++){
if(j==i) continue;
double tmp=a[j][i]/a[i][i];
for(int k=i+1;k<=n+1;k++){
a[j][k]-=a[i][k]*tmp;
}
}
}
int maxn=-1,num=0;
for(int i=1;i<=n;i++){
double ans=a[i][n+1]/a[i][i];
if(ans<=0) return 0;
if(ans!=(int)ans) return 0;
maxn=max(maxn,(int)ans);
}
for(int i=1;i<=n;i++){
int ans=a[i][n+1]/a[i][i];
if(maxn==ans){
if(num) return 0;
num=i;
}
}
return num;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n+1;i++){
scanf("%d",&k);
for(int j=1;j<=k;j++){
scanf("%d",&x);
b[i][x]=1;
}
scanf("%lf",&b[i][n+1]);
}
for(int i=1;i<=n+1;i++){
swap(b[i],b[n+1]);
for(int j=1;j<=n;j++){
for(int k=1;k<=n+1;k++){
a[j][k]=b[j][k];
}
}
swap(b[i],b[n+1]);
Num=solve();
if(Num){
if(flag){
cout<<"illegal\n";
return 0;
}
flag=Num;
}
}
if(!flag){
cout<<"illegal\n";
return 0;
}
else cout<<flag<<endl;
}
枚举是否被标记过及标记的数量
典例:道路
题目大意:给定一棵树,有 n n n个叶子结点,每个结点到其父亲结点都有两条路,分别为 A A A型和 B B B型。要求对每个结点到其父亲结点的两条路中选择一条进行翻修。每个结点有一个参数 ( a i , b i , c i ) (a_i,b_i,c_i) (ai,bi,ci),设每个叶子结点到根结点的路上有 x x x条未翻修的 A A A路径和 y y y条未翻修的 B B B路径,求 ∑ i = 1 n c i ( a i + x ) ( b i + y ) \sum_{i=1}^nc_i(a_i+x)(b_i+y) ∑i=1nci(ai+x)(bi+y)的最小值。
解析:设 f [ u ] [ i ] [ j ] f[u][i][j] f[u][i][j]表示从 u u u到根有 i i i条未翻修的 A A A边与 j j j条未翻修的 B B B边
如果
u
u
u是叶结点,那么我们枚举
i
,
j
i,j
i,j,有
f
[
u
]
[
i
]
[
j
]
=
c
u
(
a
u
+
i
)
(
b
u
+
j
)
f[u][i][j]=c_u(a_u+i)(b_u+j)
f[u][i][j]=cu(au+i)(bu+j)
如果
u
u
u不是叶结点,我们枚举删哪条边,有
f
[
u
]
[
i
]
[
j
]
=
m
i
n
(
f
[
l
s
o
n
]
[
i
+
1
]
[
j
]
+
f
[
r
s
o
n
]
[
i
]
[
j
]
,
f
[
l
s
o
n
]
[
i
]
[
j
]
+
f
[
r
s
o
n
]
[
i
]
[
j
+
1
]
)
f[u][i][j]=min(f[lson][i+1][j]+f[rson][i][j],f[lson][i][j]+f[rson][i][j+1])
f[u][i][j]=min(f[lson][i+1][j]+f[rson][i][j],f[lson][i][j]+f[rson][i][j+1])
稍微注意细节即可。
#include<bits/stdc++.h>
using namespace std;
int Read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,a[40005],b[40005],c[40005];
int first[80005],nxt[80005],to[80005],tot=0;
void Add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
vector<int> g[40005];
int cnt,size[40005],dep[40005],dfn[40005];
long long f[105][45][45];
void dfs(int u,int k){
dfn[u]=k;
size[u]=1;
for(int e=first[u];e;e=nxt[e]){
int v=to[e];
dep[v]=dep[u]+1;
g[u].push_back(v);
if(g[u][1]) dfs(v,k+2);
else dfs(v,k+1);
size[u]+=size[v];
}
if(size[u]==1){
for(int i=0;i<=dep[u];i++){
for(int j=0;j<=dep[u];j++){
f[dfn[u]][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
}
}
}
else{
for(int i=0;i<=dep[u];i++){
for(int j=0;j<=dep[u];j++){
f[dfn[u]][i][j]=min(f[dfn[g[u][0]]][i][j]+f[dfn[g[u][1]]][i][j+1],f[dfn[g[u][0]]][i+1][j]+f[dfn[g[u][1]]][i][j]);
}
}
}
}
int main(){
memset(f,63,sizeof(f));
n=Read();
for(int i=1;i<n;i++){
int x=Read(),y=Read();
if(y<0) Add(i,-y+n-1);
else Add(i,y);
if(x<0) Add(i,-x+n-1);
else Add(i,x);
}
for(int i=n;i<=2*n-1;i++){
a[i]=Read(),b[i]=Read(),c[i]=Read();
}
dep[1]=0;
dfs(1,0);
printf("%lld",f[dfn[1]][0][0]);
}
枚举初始情况
典例:翻转棋
题目大意:给一个 n ∗ n n*n n∗n的矩阵 ( n ≤ 16 ) (n\le16) (n≤16),里面有 0 , 1 0,1 0,1两种元素,你可以翻转一个格子及其上下左右的格子,问一个字典序最小的解。
解析:直接处理会很困难,我们考虑如果答案矩阵第一行的状态已知,我们可以 O ( n 2 ) O(n^2) O(n2)算出整个答案矩阵的状态,故我们枚举第一行的状态,然后判断是否可行。
#include<bits/stdc++.h>
using namespace std;
int Read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[25][25],b[25][25],c[25][25],num[25],ans[25][25],flag=0,Ans=2147483647;
void Copy(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans[i][j]=c[i][j];
}
}
}
void check(){
memset(c,0,sizeof(c));
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]=a[i][j];
}
}
for(int i=1;i<=m;i++){
if(num[i]){
b[1][i]^=1;
b[2][i]^=1;
if(i>1) b[1][i-1]^=1;
if(i<m) b[1][i+1]^=1;
cnt++;
}
c[1][i]=num[i];
}
for(int i=2;i<=n;i++){
for(int j=1;j<=m;j++){
if(b[i-1][j]){
cnt++;
c[i][j]=1;
b[i-1][j]^=1;
b[i][j]^=1;
if(j>1) b[i][j-1]^=1;
if(j<m) b[i][j+1]^=1;
if(i<n) b[i+1][j]^=1;
}
}
}
for(int i=1;i<=m;i++){
if(b[n][i]!=0) return ;
}
if(cnt>Ans) return;
if(cnt<Ans){
Ans=cnt;
Copy();
flag=1;
return ;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(c[i][j]>ans[i][j]) return ;
if(c[i][j]<ans[i][j]){
Copy();
flag=1;
return ;
}
}
}
}
void dfs(int x){
if(x==m+1){
check();
return ;
}
num[x]=0;
dfs(x+1);
num[x]=1;
dfs(x+1);
}
signed main(){
//freopen("fliptile.in","r",stdin);
//freopen("fliptile.out","w",stdout);
n=Read(),m=Read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=Read();
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans[i][j]=2;
}
}
dfs(1);
if(!flag){
cout<<"IMPOSSIBLE\n";
fclose(stdin);
fclose(stdout);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cout<<ans[i][j]<<" ";
}
cout<<endl;
}
//fclose(stdin);
//fclose(stdout);
return 0;
}