2021-11-21训练周结(dfs)

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)的方式,不然基本上就超时了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值