1、P1018 [NOIP2000 提高组] 乘积最大
题意:给n个数,k个乘号,问怎样分配乘号才能使所得的乘式最大。
虽然这道题出自dp专题里,但是不管在哪都得礼让大法师三分。之前有一道0/1背包的模板题,我用大法师解决了,这又有一道比较难的dp题,大法师依旧能胜任!!!!!!
dfs(已用的乘号数,当前最大乘积,上一个乘号的位置)
如果不考虑高精度,这样大法师直接就能AC代码如下:
#include <bits/stdc++.h>
#define ll long long
//dfs 大法师
using namespace std;
const int mod=20100403;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char x[101];
ll y[101];
int n,k;
ll s_mul;
void dfs(int x,ll mul,int xx)//x:第几个乘号;mul:当前成绩;y:上一个称号插入的位置
{
if(x==k)
{
ll t=0;
for(int a=xx+1;a<n;a++)
{
t+=y[a];
t*=10;
}
t+=y[n];
s_mul=max(s_mul,mul*t);
return;
}
else
{
for(int a=xx+1;a<=n-k+x;a++)
{
ll ans=0;
for(int b=xx+1;b<a;b++)
{
ans+=y[b];
ans*=10;
}
ans+=y[a];
dfs(x+1,mul*ans,a);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int a=1;a<=n;a++)
{
cin>>x[a];
y[a]=x[a]-'0';
}
dfs(0,1,0);
cout<<s_mul<<endl;
return 0;
}
这里只有一点,为什么上式中a要小于等于n-k+x,这里在草稿纸上模拟一下就可以,我们要给剩下的乘号留够位置。
然后这道题的难点恰恰是高精度的表达所以这样直接大法师只能过6组。之前发过一次高精整数与普通整型的乘式,那种高精度乘法可以解决大部分高精乘问题,但是对于这道题,由于n是40以内的,所以当n大于37时,也就是高精数和普通整形都大于19位时,就不能用高精数乘整形的方法了,这里就需要高精乘高精,具体算法这里不再赘述。有了高精只需要将mul替换一下就可以了。
2、P1019 [NOIP2000 提高组] 单词接龙
题意:像成语接龙一样,只不过这里是单词,给出n个单词,每个单词最多用两次,问能形成的龙的最大长度。
对于这道题就是大法师管辖之内的题目了,这道题看似有点像贪心,但是仔细一想又不能直接找最长“回文”的单词。这是大法师的能力就显现出来了,不管怎样最大,我全都遍历,总能找出最长的出来。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=20100403;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n;//单词数
string tr[30];//存储字符串
int yc[30][30];//两个字母的最小重叠部分
int vis[30];//判断单词使用频率.
int mt(int x, int y){//mt函数,返回x单词后连接一个y单词的最小重叠部分
bool pp=true;
int ky=0;
for(int k=tr[x].size()-1;k>=0;k--){//从x单词尾部向前看看最小重叠部分是从哪里开始的,以为因为是倒着来,所以保证是最小的
for(int kx=k;kx<tr[x].size();kx++){
if(tr[x][kx]!=tr[y][ky++]){
pp=false;
break;
}
}
if(pp==true){//如果说当前以k为开头的前一个单词后缀 ,是后面单词的前缀,就马上返回重叠部分。(tr[x].size()-k是找出来的规律)
return tr[x].size()-k; }
ky=0;
pp=true;//不行就继续
}
return 0;
}//可能这里有点难理解。可以手动模拟一下
char ch;//开头字母
int ans=-1;//答案
int an=0;//每次搜到的当前最长串
void dfs(int p){//p为尾部单词编号(p的后缀就是“龙”的后缀,因为p已经连接到”龙“后面了)
bool jx=false;
for(int j=1;j<=n;j++){
if(vis[j]>=2) continue;//使用了两次就跳过
if(yc[p][j]==0) continue;//两单词之间没有重合部分就跳过
if(yc[p][j]==tr[p].size() || yc[p][j]==tr[j].size()) continue;//两者存在包含关系就跳过
an+=tr[j].size()-yc[p][j];//两单词合并再减去最小重合部分
vis[j]++;//使用了一次
jx=true;//标记一下当前已经成功匹配到一个可以连接的部分
dfs(j); //接上去
an-=tr[j].size()-yc[p][j];//回溯,就要再减回去那一部分长度
vis[j]--;//回溯,使用--
}
if(jx==false){//jx==false说明不能再找到任何一个单词可以相连了
ans=max(ans,an);//更新ans
}
return;
}
int main()
{
//ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i=1;i<=n;i++)
cin>>tr[i];
cin>>ch;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
yc[i][j]=mt(i,j);
}
}
for(int i=1;i<=n;i++){
if(tr[i][0]==ch){
vis[i]++;
an=tr[i].size();
dfs(i);
vis[i]=0;
}
}
printf("%d",ans);
return 0;
}
3、search for mafuyu
这是2021区域赛济南赛区的一道签到题。
欧拉遍历一颗树,每个节点都遍历到,计算总共的步数,求期望。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int cnt = 0, ans = 0;
int vis[128] = {0};
void dfs(vector<int>g[], int cur)
{
cnt++;//步数加1
vis[cur] = 1;//标记为已访问
ans += cnt;
for(int i = 0; i < g[cur].size(); i++)
{
if(!vis[g[cur][i]])
{
dfs(g, g[cur][i]);
cnt++;//这里相当于回溯一步
}
}
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while(T--)
{
cin >> n;
memset(vis, 0, sizeof(vis));
vector<int>g[128];
cnt = -1;
ans = 0;
for(int i = 0; i < n - 1; i++)
{
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(g, 1);
double x = (double)ans / (n - 1);
printf("%.10f\n", x);
}
return 0;
}
总结:关于dfs的题目,不能去钻牛角尖,他很难被模拟出来。其实dfs就像我们平常用STL一样,不管原理是啥,我就知道dfs()…能得到我想要的结果,把dfs()当成一个步骤或者说就是处理问题的一个方法,我们就会发现dfs的功能十分强大,不过唯一要注意,不能把dfs写成O(n^n)的方式,不然基本上就超时了。