在考场中,时常会遇到题目拿到没想法,就先来打个表或写个暴力来骗分。DFS就是一个骗分神器。如果能加上几道优化,或许会有意想不到的结果。
$ 这题在爆搜的时候,可以给后面留下足够的空间。$
$ 什么意思呢?假设当前的值为 x,选择 x 之后要再选\ k-1 个,那么要保证\ yu \geq k*x,yu表示n剩下的部分,$
$ 因为后面还要选择k-1个大小至少为x的数字。$
#include
using namespace std;
int n,m,ans;
void dfs(int sum,int lst,int k)
{
if (k==1){++ans;return;}
for (int i=lst;i*k<=sum;++i) dfs(sum-i,i,k-1);
}
int main()
{
scanf("%d%d",&n,&m);
dfs(n,1,m);printf("%d\n",ans);
return 0;
}
这 题 看 似 D F S 不 可 做 , 实 际 上 加 入 大 量 预 判 剪 枝 后 , 跑 得 飞 快 。 这题看似DFS不可做,实际上加入大量预判剪枝后,跑得飞快。 这题看似DFS不可做,实际上加入大量预判剪枝后,跑得飞快。
先 预 判 体 积 。 如 果 发 现 接 下 来 按 照 最 小 的 方 式 放 都 不 能 使 得 体 积 小 于 n 或 是 按 照 最 大 的 方 式 放 都 不 能 达 到 n 就 停 先预判体积。如果发现接下来按照最小的方式放都不能使得体积小于 \ n\ 或是按照最大的方式放都不能达到\ n\ 就停 先预判体积。如果发现接下来按照最小的方式放都不能使得体积小于 n 或是按照最大的方式放都不能达到 n 就停
再 判 表 面 积 。 如 果 接 下 来 拿 到 最 小 的 表 面 积 都 没 比 当 前 a n s 更 优 秀 , 那 么 就 停 。 或 者 可 以 用 另 一 种 ( 详 见 代 码 ) 再判表面积。如果接下来拿到最小的表面积都没比当前\ ans\ 更优秀,那么就停。或者可以用另一种(详见代码) 再判表面积。如果接下来拿到最小的表面积都没比当前 ans 更优秀,那么就停。或者可以用另一种(详见代码)
还 有 就 是 枚 举 的 时 候 可 以 预 判 r 和 h 的 取 值 范 围 。 还有就是枚举的时候可以预判\ r\ 和\ h\ 的取值范围。 还有就是枚举的时候可以预判 r 和 h 的取值范围。
上 界 : r m a x = min { y u , 上 次 选 的 r } , h m a x = min { y u / r , 上 次 选 的 h } 上界:r_{max}=\min \{\sqrt {yu},上次选的\ r\},h_{max}=\min \{yu/r,上次选的\ h\} 上界:rmax=min{yu,上次选的 r},hmax=min{yu/r,上次选的 h}
下 界 : r m i n = h m i n = min { 1 , h i } 下界:r_min=h_min=\min\{1,hi\} 下界:rmin=hmin=min{1,hi}
其 中 y u 表 示 剩 余 的 体 积 , h i 表 示 剩 余 的 层 数 。 其中\ yu\ 表示剩余的体积,\ hi\ 表示剩余的层数。 其中 yu 表示剩余的体积, hi 表示剩余的层数。
#include
using namespace std;
typedef long long LL;
int n,m;LL ans=1e18,f1[25],f2[25];
void dfs(LL hi,LL yu,LL R,LL H,LL S)
{
bool pd=hi==m;
if (hi==1)
{
for (LL r=min(R,(LL)sqrt(yu));r>0;--r)
{
LL p=pd?r*r:0;LL h=yu/r/r;if (h>H) break;
if (h*r*r==yu) ans=min(ans,S+p+(h*r<<1));
}
return;
}
if (yu>hi*H*R*R||yu<f1[hi]||S+max(yu/R<<1,f2[hi])>ans) return;
/*如果接下来hi层都按照最大都放不满yu单位空间*/
/*如果接下来按照最小方式放蛋糕体积都超过n*/
/*f2是最少要得到的侧面积。对于一个蛋糕,不考虑上下表面,那么最小的方式肯定要使得r尽可能大
当前情况下,r最大取到R。假设后面层都取到R,那么几层可以看成一层,侧面积为yu/R*2 */
for (LL r=min(R,(LL)sqrt(yu));r>=hi;--r)
{
LL p=pd?r*r:0;
for (LL h=min(H,yu/(r*r));h>=hi;--h)
{
dfs(hi-1,yu-r*r*h,r-1,h-1,S+(h*r<<1)+p);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i) f1[i]=f1[i-1]+i*i*i,f2[i]=f2[i-1]+i*i*2;
dfs(m,n,sqrt(n),n,0);
printf("%lld",ans==1e18?0:ans);
return 0;
}
这 题 乍 一 看 还 以 为 是 贪 心 , 实 际 上 是 有 漏 洞 的 。 没 办 法 , 只 能 写 D F S 。 这题乍一看还以为是贪心,实际上是有漏洞的。没办法,只能写DFS。 这题乍一看还以为是贪心,实际上是有漏洞的。没办法,只能写DFS。
但 是 n = 60 还 是 比 较 大 的 , 想 想 办 法 尽 可 能 优 化 一 下 。 但是n=60还是比较大的,想想办法尽可能优化一下。 但是n=60还是比较大的,想想办法尽可能优化一下。
枚举方式
如 果 懒 得 写 链 表 , 我 们 可 以 枚 举 长 度 , 而 不 是 枚 举 木 棍 ( 长 度 小 于 数 量 ) 如果懒得写链表,我们可以枚举长度,而不是枚举木棍(长度小于数量) 如果懒得写链表,我们可以枚举长度,而不是枚举木棍(长度小于数量)
枚举方式
从 m i n ( 上 次 选 择 长 度 , 最 长 的 长 度 , 这 根 剩 余 的 长 度 ) 开 始 枚 举 。 从min(上次选择长度,最长的长度,这根剩余的长度)开始枚举。 从min(上次选择长度,最长的长度,这根剩余的长度)开始枚举。
预判
如果当前这根剩下的部分小于有最短的木棍可以直接弹出。
枚举方式
枚 举 的 时 候 , 按 照 从 大 到 小 的 顺 序 枚举的时候,按照从大到小的顺序 枚举的时候,按照从大到小的顺序
如 果 所 剩 的 木 棍 可 以 凑 到 长 度 L e n , 那 么 跟 顺 序 肯 定 没 关 系 如果所剩的木棍可以凑到长度Len,那么跟顺序肯定没关系 如果所剩的木棍可以凑到长度Len,那么跟顺序肯定没关系
但 是 按 顺 序 可 以 避 免 重 复 但是按顺序可以避免重复 但是按顺序可以避免重复
预判
假 设 我 们 上 一 次 刚 好 凑 出 一 根 木 棍 , 剩 余 木 棍 的 集 合 S , 找 到 了 最 长 的 木 棍 x , 且 x ∈ S 。 假设我们上一次刚好凑出一根木棍,剩余木棍的集合S,找到了最长的木棍 x,且\ x\in S。 假设我们上一次刚好凑出一根木棍,剩余木棍的集合S,找到了最长的木棍x,且 x∈S。
而 选 择 这 根 木 棍 后 接 下 来 却 凑 不 出 一 根 木 棍 , 那 么 就 直 接 弹 出 。 而选择这根木棍后接下来却凑不出一根木棍,那么就直接弹出。 而选择这根木棍后接下来却凑不出一根木棍,那么就直接弹出。
剩 余 木 棍 的 肯 定 不 能 和 x 凑 成 新 木 棍 。 剩余木棍的肯定不能和\ x\ 凑成新木棍。 剩余木棍的肯定不能和 x 凑成新木棍。
即 使 换 一 根 短 一 点 的 木 棍 可 以 凑 出 来 整 根 , 凑 出 后 x 肯 定 没 被 使 用 过 , 否 则 刚 才 就 用 了 。 即使换一根短一点的木棍可以凑出来整根,凑出后\ x\ 肯定没被使用过,否则刚才就用了。 即使换一根短一点的木棍可以凑出来整根,凑出后 x 肯定没被使用过,否则刚才就用了。
x 和 刚 才 那 个 集 合 S 中 的 就 不 能 凑 成 了 , 现 在 的 集 合 比 S 还 要 小 , 更 不 可 能 凑 出 整 根 了 。 x \ 和刚才那个集合 S 中的就不能凑成了,现在的集合比 S 还要小,更不可能凑出整根了。 x 和刚才那个集合S中的就不能凑成了,现在的集合比S还要小,更不可能凑出整根了。
后 面 两 个 的 效 果 比 较 明 显 。 后面两个的效果比较明显。 后面两个的效果比较明显。
#include
using namespace std;
const int maxn=65;
int n,a[maxn],h[55],p[55],MX,MI,sum,Len,LON;
void dfs(int yu,int stp,int lst)
{
if (stp==n) {printf("%d",Len);exit(0);};
bool pd;if (pd=!yu) lst=min(yu=Len,MX);
for (int i=lst;i>=MI;++h[i--])
if (h[i]--&&(dfs(yu-i,stp+1,min(i,yu-i)),true)&&pd) {++h[i];return;}
}
int main()
{
scanf("%d",&n);MI=1e9;
for (int i=1;i<=n;++i) scanf("%d",&a[i]),++p[a[i]],sum+=a[i],MX=max(MX,a[i]),MI=min(MI,a[i]);LON=sum>>1;
for (Len=MX,memcpy(h,p,sizeof p);Len<=LON;++Len) if (sum%Len==0) {dfs(0,0,0);memcpy(h,p,sizeof p);}
printf("%d\n",sum);return 0;
}
这 题 在 搜 索 过 程 中 , 我 们 可 以 提 前 预 判 , 如 果 当 前 长 度 l e n + g e t ( a [ l e n ] ) ≥ 当 前 最 优 解 这题在搜索过程中,我们可以提前预判,如果\ 当前长度len\ +\ get(a[len]) \geq \ 当前最优解 这题在搜索过程中,我们可以提前预判,如果 当前长度len + get(a[len])≥ 当前最优解
其 中 , g e t ( a [ l e n ] ) 为 一 个 估 价 函 数 , 表 示 当 前 最 大 值 为 a [ l e n ] 的 情 况 下 , 只 是 还 需 几 步 使 得 a [ l e n ] = n 其中,get(a[len]) \ 为一个估价函数,表示当前最大值为\ a[len]\ 的情况下,只是还需几步使得\ a[len] = n 其中,get(a[len]) 为一个估价函数,表示当前最大值为 a[len] 的情况下,只是还需几步使得 a[len]=n
a [ l e n ] 的 增 长 速 度 最 快 是 每 次 扩 大 2 倍 。 假 如 有 一 个 最 小 的 k 满 足 2 k + a [ l e n ] ≥ n , 这 个 k 就 是 g e t ( a [ l e n ] ) a[len]\ 的增长速度最快是每次扩大\ 2\ 倍。假如有一个最小的\ k \ 满足\ 2^k+a[len] \geq n,这个\ k\ 就是\ get(a[len]) a[len] 的增长速度最快是每次扩大 2 倍。假如有一个最小的 k 满足 2k+a[len]≥n,这个 k 就是 get(a[len])
如 果 a [ l e n ] 连 达 到 n 都 做 不 到 , 那 么 就 更 别 提 与 n 相 等 了 。 如果\ a[len] 连达到\ n\ 都做不到,那么就更别提与\ n\ 相等了。 如果 a[len]连达到 n 都做不到,那么就更别提与 n 相等了。
其 次 还 可 以 在 枚 举 的 时 候 加 上 一 些 优 化 , 比 如 : 因 为 我 们 的 a 序 列 是 递 增 的 , 而 且 新 的 元 素 要 大 于 当 前 序 列 最 大 值 其次还可以在枚举的时候加上一些优化,比如:因为我们的\ a\ 序列是递增的,而且新的元素要大于当前序列最大值 其次还可以在枚举的时候加上一些优化,比如:因为我们的 a 序列是递增的,而且新的元素要大于当前序列最大值
所 以 我 们 可 以 倒 着 枚 举 i 和 j , 这 样 一 旦 发 现 a [ i ] + a [ j ] < = a [ l e n ] 那 么 就 可 以 b r e a k 了 , 接 下 来 j 是 递 减 的 , a [ i ] + a [ j ′ ] 一 定 小 于 a [ l e n ] 所以我们可以倒着枚举\ i\ 和\ j\ ,这样一旦发现\ a[i]+a[j]<=a[len]\ 那么就可以break了,接下来\ j\ 是递减的,a[i]+a[j']一定小于a[len] 所以我们可以倒着枚举 i 和 j ,这样一旦发现 a[i]+a[j]<=a[len] 那么就可以break了,接下来 j 是递减的,a[i]+a[j′]一定小于a[len]
#include
using namespace std;
int n,a[105],ans,b[105];
int get(int x){int s=0;while(x<n) ++s,x<<=1;return s;}
void dfs(int stp)
{
if (stp+get(a[stp])>=ans) return;
if (a[stp]==n) {ans=stp;for (int k=1;k<=stp;++k)b[k]=a[k];return;}
for (int i=stp;i>=1;--i)
for (int j=stp;j>=i;--j)
if (a[i]+a[j]>a[stp])
{
if (a[i]+a[j]>n) continue;
a[stp+1]=a[i]+a[j];dfs(stp+1);
}else break;
}
int main()
{
a[1]=1;
while(true)
{
scanf("%d",&n);
if (! n) break;
ans=1e9;
dfs(1);
for (int i=1;i<ans;++i)printf("%d ",b[i]);printf("%d\n",b[ans]);
}
return 0;
}
这 题 首 先 想 到 的 是 枚 举 每 一 个 a i 是 集 合 中 的 第 几 号 元 素 。 这题首先想到的是枚举每一个a_i是集合中的第几号元素。 这题首先想到的是枚举每一个ai是集合中的第几号元素。
这 样 加 上 提 前 判 断 能 否 满 足 前 缀 和 和 后 缀 和 , 也 是 要 超 时 的 。 这样加上提前判断能否满足前缀和和后缀和,也是要超时的。 这样加上提前判断能否满足前缀和和后缀和,也是要超时的。
突 发 奇 想 : 因 为 所 有 元 素 都 是 正 整 数 , 所 以 前 缀 和 肯 定 递 增 , 后 缀 和 肯 定 递 减 。 突发奇想:因为所有元素都是正整数,所以前缀和肯定递增,后缀和肯定递减。 突发奇想:因为所有元素都是正整数,所以前缀和肯定递增,后缀和肯定递减。
对 表 示 前 后 缀 和 的 序 列 S 排 序 , 对 于 最 小 的 一 个 前 缀 和 或 后 缀 和 , 它 不 是 第 一 个 元 素 , 就 是 最 后 一 个 元 素 。 对表示前后缀和的序列S排序,对于最小的一个前缀和或后缀和,它不是第一个元素,就是最后一个元素。 对表示前后缀和的序列S排序,对于最小的一个前缀和或后缀和,它不是第一个元素,就是最后一个元素。
1证明
对 于 第 二 个 元 素 也 是 差 不 多 的 道 理 。 剩 下 的 都 可 以 以 此 类 推 。 对于第二个元素也是差不多的道理。剩下的都可以以此类推。 对于第二个元素也是差不多的道理。剩下的都可以以此类推。
#include
using namespace std;
const int maxn=1005,maxm=505;
int n,m,h[maxm+5],S[maxn<<1],s1[maxn],s2[maxn],Sum,b[maxn];
bool pd(int x){return x>=1&&x<=500&&h[x];}
void dfs(int p,int L,int R)
{
if (L==R)
{
if (pd(Sum-s1[L-1]-s2[R+1]))
{
b[L]=Sum-s1[L-1]-s2[R+1];
for (int i=1;i<n;++i) printf("%d ",b[i]);
printf("%d\n",b[n]);exit(0);
}
return;
}
b[L]=S[p]-s1[L-1];
if (pd(b[L]))
{
s1[L]=S[p];
dfs(p+1,L+1,R);
s1[L]=0;
}
b[L]=0;
b[R]=S[p]-s2[R+1];
if (pd(b[R]))
{
s2[R]=S[p];
dfs(p+1,L,R-1);
s2[R]=0;
}
b[R]=0;
}
int main()
{
scanf("%d",&n);n<<=1;
for (int i=1;i<=n;++i) scanf("%d",&S[i]);sort(S+1,S+n+1);Sum=S[n];
scanf("%d",&m);n>>=1;
for (int x,i=1;i<=m;++i) scanf("%d",&x),h[x]=1;
dfs(1,1,n);
return 0;
}
之前写过了,直接套链接
和题解在一起
本蒟蒻的代码找不到问题,但是就是WA……
这 题 在 0 的 个 数 比 较 多 的 时 候 可 能 会 超 时 。 这题在 0 的个数比较多的时候可能会超时。 这题在0的个数比较多的时候可能会超时。
但 是 经 过 观 察 , 我 们 可 以 先 枚 举 确 定 已 经 的 数 字 个 数 较 多 的 行 ( 列 也 是 同 理 的 ) 但是经过观察,我们可以先枚举确定已经的数字个数较多的行(列也是同理的) 但是经过观察,我们可以先枚举确定已经的数字个数较多的行(列也是同理的)
因 为 这 样 子 我 们 可 以 减 少 肯 定 不 必 要 的 枚 举 。 因为这样子我们可以减少肯定不必要的枚举。 因为这样子我们可以减少肯定不必要的枚举。
就 比 如 说 第 5 行 第 1 个 数 字 是 这 行 唯 一 一 个 空 位 , 必 定 填 3 。 若 完 全 按 照 顺 序 枚 举 , 那 么 第 1 、 2 、 3 行 的 第 1 个 位 置 都 可 能 被 枚 举 到 为 3 ( 很 显 然 第 1 列 不 可 能 存 在 任 何 一 个 已 知 数 字 为 3 , 因 为 这 个 3 已 经 可 以 通 过 推 理 确 定 在 第 5 行 第 1 个 ) 就比如说第 5 行第 1 个数字是这行唯一 一个空位,必定填 3。若完全按照顺序枚举,那么第 1、2、3 行的第 1 个位置都可能被枚举到为 3(很显然第 1 列不可能存在任何一个已知数字为 3,因为这个 3 已经可以通过推理确定在第 5 行第 1 个) 就比如说第5行第1个数字是这行唯一一个空位,必定填3。若完全按照顺序枚举,那么第1、2、3行的第1个位置都可能被枚举到为3(很显然第1列不可能存在任何一个已知数字为3,因为这个3已经可以通过推理确定在第5行第1个)
若 先 把 这 个 3 定 住 , 那 将 减 少 多 少 不 必 要 的 枚 举 啊 若先把这个 3 定住,那将减少多少不必要的枚举啊 若先把这个3定住,那将减少多少不必要的枚举啊
本 思 路 来 自 洛 谷 题 解 本思路来自洛谷题解 本思路来自洛谷题解
https://www.luogu.org/problemnew/solution/P1074
其 中 还 有 很 多 很 好 的 想 法 , 本 蒟 蒻 功 力 不 足 , 暂 且 就 说 这 些 吧 。 其中还有很多很好的想法,本蒟蒻功力不足,暂且就说这些吧。 其中还有很多很好的想法,本蒟蒻功力不足,暂且就说这些吧。
#include
using namespace std;
const int f[6]={0,6,7,8,9,10};
int a[15][15],x[15][15],y[15][15],t[15][15],id[15][15],h[7],hx[15],ans=-1;
int nxt()
{
int id=10;
for (int i=1;i<=9;++i) if (hx[i]<9&&hx[i]>hx[id]) id=i;
return id;
}
int g(int F)
{
int sum=0;
for (int i=1;i<=5;++i) sum+=h[i]*9*f[F];
return sum;
}
void dfs(int xx,int yy,int s)
{
if (yy>9) {xx=nxt();yy=1;}
int F=min(min(10-xx,10-yy),min(xx,yy));
if (ans>=s+g(F)) return;
if (xx>9) {ans=s;return;}
if (a[xx][yy]){dfs(xx,yy+1,s);return;}
for (int i=1;i<=9;++i)
{
if (!x[xx][i]&&!y[yy][i]&&!t[id[xx][yy]][i])
{
x[xx][i]=y[yy][i]=t[id[xx][yy]][i]=1;
a[xx][yy]=i;--h[F];++hx[xx];
dfs(xx,yy+1,s+i*f[F]);
x[xx][i]=y[yy][i]=t[id[xx][yy]][i]=0;
a[xx][yy]=0;++h[F];--hx[xx];
}
}
}
int main()
{
for (int k=0;k<3;++k)
for (int t=0;t<3;++t)
{
int m=k*3+t+1;
for (int i=1;i<=3;++i)
for (int j=1;j<=3;++j) id[i+k*3][j+t*3]=m;
}
int S=0;
h[1]=32;
h[2]=24;
h[3]=16;
h[4]=8;
h[5]=1;
for (int i=1;i<=9;++i)
for (int j=1;j<=9;++j)
{
scanf("%d",&a[i][j]);
t[id[i][j]][a[i][j]]=1;
x[i][a[i][j]]=1;
y[j][a[i][j]]=1;
int F=min(min(10-i,10-j),min(i,j));
if (a[i][j]) S+=a[i][j]*f[F],--h[F],++hx[i];
}
hx[10]=-1;
dfs(nxt()!=10?nxt():1,1,S);
printf("%d",ans);
return 0;
}
既然写证明,就写详细点吧 我 们 先 对 一 开 始 的 S 序 列 排 序 我们先对一开始的S序列排序 我们先对一开始的S序列排序 ↩︎