首页 > 其他分享 >Living-Dream 系列笔记 第80期(国庆集训合集)

Living-Dream 系列笔记 第80期(国庆集训合集)

时间:2024-10-05 18:43:59浏览次数:1  
标签:Living cur 剪枝 int dfs dep ans 80 Dream

IDDFS

使用场景:

  • 搜索树非常大而答案的深度较浅,一般在 \(20\) 以内,且 dfs 会 TLE,bfs 会 MLE。

算法原理:

  • 以 dfs 的形式搜索;

  • 设定搜索的深度限制 \(dep\);

  • dfs 深度不能超过 \(dep\),且要恰好遍历所有 \(dep\) 的状态;

  • 若在 \(dep\) 层没有找到答案,\(dep+1 \to dep\),重新 DFS。

剪枝:

  • 最优性剪枝:考虑将选择序列排序,优先选最大 / 最小。

  • 可行性剪枝:按照最优策略选择时如果还不能在剩下的层数之内达到目标就剪枝。

UVA529

观察到此题的答案序列形如 Fib 数列,

而众所周知这玩意增长极快,

因此答案绝对不会过大,考虑 IDDFS。

朴素搜索是简单的,但 \(n\) 达到 \(10^4\) 规模,因此考虑剪枝。

最优性剪枝:从大到小选数,一遍更快地凑近 \(n\)。

可行性剪枝:

最快的正常方式显然是不断选择两个当前的数然后求和。

(得益于题目的限制,当前的数即为最大的)

设当前层数为 \(cur\),深度限制为 \(dep\),当前的数为 \(x\)(定义下文同),

则若 \(2^{dep-cur} \times x<n\) 则直接剪枝即可。

code
//
//  UVA529.cpp
//  
//
//  Created by _XOFqwq on 2024/10/5.
//

#include<bits/stdc++.h>
using namespace std;

const int N=1e4+5;
int n,ans[N],dep;

bool dfs(int cur){
    if (cur==dep) {
        return ans[cur]==n;
    }
    if (ans[cur]*(1<<(dep-cur))<n) {
        return 0;
    }
    for (int i=cur; i>=1; i--) {
        for (int j=i; j>=1; j--) {
            if (ans[i]+ans[j]<=n&&ans[i]+ans[j]>ans[cur]) {
                ans[cur+1]=ans[i]+ans[j];
                if (dfs(cur+1)) {
                    return 1;
                }
                ans[cur+1]=0;
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    while (cin>>n&&n) {
        ans[1]=1;
        for (dep=1; !dfs(1); dep++);
        for (int i=1; i<dep; i++) {
            cout<<ans[i]<<' ';
        }
        cout<<ans[dep]<<'\n';
    }
    return 0;
}

总结:

  • 对于看上去像搜索但数据较大的题目时,考虑 IDDFS + 剪枝。可以大胆猜想答案不会很大。

  • 剪枝时考虑最优情况是怎样,然后就能推导出可行性剪枝。

  • 写 dfs 函数时,注意函数末尾的 return 0

UVA1374

根据乘方的性质,\(x \to x^n\) 的过程实质即为指数的加减。

考虑到操作序列仍形如 Fib 数列,考虑 IDDFS。

最优性剪枝:从大到小选即可。

可行性剪枝:设前面选的最大指数为 \(x\),则若 \(2^{dep-cur} \times x<n\) 就剪枝。

细节:

枚举时无需枚举两个数,仅需枚举一个然后和当前数参与运算。

这是因为如果当前数不参与运算,它完全可以现在不被计算出来。

而上一题需要枚举两个数是因为题目有递增的限制,

当前数即使不参与运算也要计算出来以构造下一个数。

code
//
//  UVA1374.cpp
//  
//
//  Created by _XOFqwq on 2024/10/5.
//

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e3+5;
int n,dep,ans[N];

bool dfs(int cur){
    if (cur==dep) {
        return ans[cur]==n;
    }
    int mx=-1e18;
    for (int i=0; i<=cur; i++) {
        mx=max(mx,ans[cur]);
    }
    if (mx*(1<<(dep-cur))<n) {
        return 0;
    }
    for (int i=cur; i>=0; i--) {
        ans[cur+1]=ans[cur]+ans[i];
        if (dfs(cur+1)) {
            return 1;
        }
        ans[cur+1]=0;
        if (ans[cur]-ans[i]>0) {
            ans[cur+1]=ans[cur]-ans[i];
            if (dfs(cur+1)) {
                return 1;
            }
            ans[cur+1]=0;
        }
    }
    return 0;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    while (cin>>n&&n) {
        ans[0]=1;
        for (dep=0; !dfs(0); dep++);
        cout<<dep<<'\n';
    }
    return 0;
}

P10489

个人认为,这题只要把题读懂了就不难想出。

形式化题意:

给定 \(n\) 个数(可重),求最少的等差数列的个数,使得它们覆盖每个数恰好 \(1\) 次,且每个等差数列覆盖至少 \(2\) 个数。

注意到答案 \(\le 17\),考虑 IDDFS。

维护 \(cnt_i\) 表示第 \(i\) 个数的出现次数,先预处理所有合法的等差数列,然后枚举每个等差数列暴力覆盖即可。

最优性剪枝:按长度从大到小选择。

可行性剪枝:当不停地选择当前的(即最长的)等差数列都无法完全覆盖时剪枝。

code
//
//  P10489.cpp
//  
//
//  Created by _XOFqwq on 2024/10/5.
//

#include <bits/stdc++.h>
using namespace std;

const int N=3e3+5;
int n,tot,dep;
int cnt[N];
struct Seq {
    int x,d,len;
}seq[N];

bool cmp(Seq a,Seq b){
    return a.len>b.len;
}
bool check(int x,int d){
    for (int i=x; i<60; i+=d) {
        if (!cnt[i]) {
            return 0;
        }
    }
    return 1;
}
bool dfs(int cur,int pos,int sum){
    if (cur==dep) {
        return sum==n;
    }
    if (sum+seq[pos].len*(dep-cur)<n) {
        return 0;
    }
    for (int i=pos; i<=tot; i++) {
        if (check(seq[i].x,seq[i].d)) {
            for (int j=seq[i].x; j<60; j+=seq[i].d) {
                cnt[j]--;
            }
            if (dfs(cur+1,i,sum+seq[i].len)) {
                return 1;
            }
            for (int j=seq[i].x; j<60; j+=seq[i].d) {
                cnt[j]++;
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for (int i=1,x; i<=n; i++) {
        cin>>x;
        cnt[x]++;
    }
    for (int i=0; i<60; i++) {
        for (int j=i+1; i+j<60; j++) {
            if (check(i,j)) {
                seq[++tot]={i,j,(59-i)/j+1};
            }
        }
    }
    sort(seq+1,seq+tot+1,cmp);
    for (dep=0; !dfs(0,1,0); dep++);
    cout<<dep;
    return 0;
}

总结:

  • 对于晦涩难懂的题目,首先提炼出形式化题意

IDA*

可以看作是带有估价函数的 IDDFS。

而估价函数可以看作是复杂一点的可行性剪枝。

P2324

根据题意,答案一定很小,考虑 IDA*。

首先有一个很重要的转化:将骑士的移动看作空位的移动。这样就只有一个东西在动了。

考虑设计可行性剪枝。容易发现空位每动一次就会复原至多一个位置,最后一步至多复原两个位置。

于是设计估价函数 \(h\) 检查有多少个位置没有被复原,设有 \(x\) 个,则 \(dep-cur+1<x\) 时就可以剪枝了。

code
//
//  P2324-2.cpp
//
//
//  Created by _XOFqwq on 2024/10/5.
//

#include <bits/stdc++.h>
using namespace std;

const int N=5;
const int dx[]={-1,1,-1,1,-2,2,-2,2};
const int dy[]={2,2,-2,-2,1,1,-1,-1};
const char tar[N][N]=
{
    {'1','1','1','1','1'},
    {'0','1','1','1','1'},
    {'0','0','*','1','1'},
    {'0','0','0','0','1'},
    {'0','0','0','0','0'}
};
int t,dep;
char a[N][N];

int h(){
    int res=0;
    for (int i=0; i<5; i++) {
        for (int j=0; j<5; j++) {
            res+=a[i][j]!=tar[i][j];
        }
    }
    return res;
}
bool dfs(int cur,int x,int y){
    int cost=h();
    if (!cost) {
        return 1;
    }
    if (dep-cur+1<cost) {
        return 0;
    }
    for (int i=0; i<8; i++) {
        int xx=x+dx[i],yy=y+dy[i];
        if (xx>=0&&xx<5&&yy>=0&&yy<5) {
            swap(a[x][y],a[xx][yy]);
            if (dfs(cur+1,xx,yy)) {
                return 1;
            }
            swap(a[x][y],a[xx][yy]);
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>t;
    while (t--) {
        int sx=-1,sy=-1;
        for (int i=0; i<5; i++) {
            for (int j=0; j<5; j++) {
                cin>>a[i][j];
                if (a[i][j]=='*') {
                    sx=i,sy=j;
                }
            }
        }
        for (dep=0; dep<=15&&!dfs(0,sx,sy); dep++);
        cout<<(dep>15?-1:dep)<<'\n';
    }
    return 0;
}

总结:

  • 这个转化是棋盘搜索类问题的通用套路,需要牢记。

P10488

根据题意,答案一定很小,考虑 IDA*。

考虑可行性剪枝。发现答案序列一定是一个公差为 \(1\) 的等差数列,因此判断有多少对相邻的数差不为 \(1\) 就说明最少要操作多少次。设最少要操作 \(x\) 次,则 \(dep-cur<x\) 时就可以剪枝了。

(注意,这里不能计算有多少个数不等于其下标,因为即使某个数在相应位置上,当它前面的数和它差不为 \(1\),则也需要操作)

然后注意一下状态更新时不要写复杂了即可。具体见代码。

code
//
//  P10488-3.cpp
//  
//
//  Created by _XOFqwq on 2024/10/5.
//

#include <bits/stdc++.h>
using namespace std;

const int N=31;
int T,n,dep;
int a[N],t[6][N];

int h(){
    int res=0;
    for (int i=1; i<n; i++) {
        res+=a[i]+1!=a[i+1];
    }
    return res;
}
void fuck_that_bitch(int cur,int l,int r,int p){
    int pos=l;
    for (int i=r+1; i<=p; i++,pos++) {
        a[pos]=t[cur][i];
    }
    for (int i=l; i<=r; i++,pos++) {
        a[pos]=t[cur][i];
    }
}
bool dfs(int cur){
    int cost=h();
    if (!cost) {
        return 1;
    }
    if (3*(dep-cur)<cost) {
        return 0;
    }
    for (int i=1; i<=n; i++) {
        for (int j=i; j<=n; j++) {
            for (int k=j+1; k<=n; k++) {
                memcpy(t[cur],a,sizeof a);
                fuck_that_bitch(cur,i,j,k);
                if (dfs(cur+1)) {
                    return 1;
                }
                memcpy(a,t[cur],sizeof a);
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>T;
    while (T--) {
        cin>>n;
        for (int i=1; i<=n; i++) {
            cin>>a[i];
        }
        for (dep=0; dep<5&&!dfs(0); dep++);
        if (dep>=5) {
            cout<<"5 or more\n";
        } else {
            cout<<dep<<'\n';
        }
    }
    return 0;
}

总结:

  • 状态更新与剪枝是 IDDFS / IDA* 最重要的部分。前者力求简洁,而后者则需要仔细思考其正确性。

UVA1505

根据题面 PDF,

image

答案一定很小,考虑 IDA*。

考虑可行性剪枝。每次操作最多消除一种颜色,因此统计除了左上角联通块外还有多少种不同的颜色。设有 \(x\) 种,则 \(dep-cur<x\) 时就可以剪枝了。

考虑状态更新。每次从左上角联通块的边界开始染色即可,需要单独写另一个 dfs,并且需要在一开始预先染色左上角的联通块。

code
//
//  UVA1505-2.cpp
//  
//
//  Created by _XOFqwq on 2024/10/5.
//

#include<bits/stdc++.h>
using namespace std;

const int N=10;
const int dx[]={0,1,0,-1};
const int dy[]={1,0,-1,0};
int n,dep;
int a[N][N];
int cnt[N],vis[N][N];

int h(){
    memset(cnt,0,sizeof cnt);
    for (int i=1; i<=n; i++) {
        for (int j=1; j<=n; j++) {
            if (vis[i][j]!=1) {
                cnt[a[i][j]]++;
            }
        }
    }
    int res=0;
    for (int i=0; i<=5; i++) {
        res+=cnt[i]>0;
    }
    return res;
}
void erect(int x,int y,int c){
    vis[x][y]=1;
    for (int i=0; i<4; i++) {
        int xx=x+dx[i],yy=y+dy[i];
        if (xx>=1&&xx<=n&&yy>=1&&yy<=n&&vis[xx][yy]!=1) {
            vis[xx][yy]=2;
            if (a[xx][yy]==c) {
                erect(xx,yy,c);
            }
        }
    }
}
bool dfs(int cur){
    int cost=h();
    if (!cost) {
        return 1;
    }
    if (dep-cur<cost) {
        return 0;
    }
    int t[N][N];
    memcpy(t,vis,sizeof t);
    for (int i=0; i<=5; i++) {
        bool f=0;
        for (int x=1; x<=n; x++) {
            for (int y=1; y<=n; y++) {
                if (a[x][y]==i&&vis[x][y]==2) {
                    erect(x,y,i);
                    f=1;
                }
            }
        }
        if (f&&dfs(cur+1)) {
            return 1;
        }
        memcpy(vis,t,sizeof vis);
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    while (cin>>n&&n) {
        memset(vis,0,sizeof vis);
        for (int i=1; i<=n; i++) {
            for (int j=1; j<=n; j++) {
                cin>>a[i][j];
            }
        }
        erect(1,1,a[1][1]);
        for (dep=0; !dfs(0); dep++);
        cout<<dep<<'\n';
    }
    return 0;
}

标签:Living,cur,剪枝,int,dfs,dep,ans,80,Dream
From: https://www.cnblogs.com/XOF-0-0/p/18447800

相关文章

  • CF 1805 D. A Wide, Wide Graph (*1800) 思维 + 树的直径
    CF1805D.AWide,WideGraph(*1800)思维+树的直径题目链接题意:思路:若当前点到最远的点的距离\(<k\),说明\(x\)自己成为一个联通块。并且我们知道距离任意一点最远的点一定是树直径的一个端点。反之,则与直径端点在同一个联通块。所以一个点要么独立成为联通块......
  • wsl重装Ubuntu遇到的一些问题( WslRegisterDistribution failed with error: 0x800410
        不知道什么原因,VSCode连接WSLUbuntu总是失败,遂决定重装Ubuntu。    但是卸载原来的Ubuntu后,安装新的Ubuntu报错:WslRegisterDistributionfailedwitherror:0x80041002Error:0x80041002(null),查了比较多的帖子,使用了以下方法最终解决:1.关闭"适用于l......
  • 2023-11-25 Matlab和Python在气象中的常用代码 180401
    目录画图横坐标添加月份PythonMatlab画图横坐标添加月份Pythonimportmatplotlib.pyplotaspltimportpandasaspdimportnumpyasnp#准备时间和温度数据start_date=pd.to_datetime('1996-12-01')#thenextdateend_date=pd.to_datetime('1998-12-01')#the......
  • CF808G Anthem of Berland
    题意给定字符串\(s\),\(t\),求将\(s\)中的通配符赋值,\(t\)在\(s\)中出现的最大次数。\(n\timesm\le10^7\)。Sol考虑朴素\(\texttt{dp}\),设\(f_i\)表示\(i\)之前的匹配出现的最大次数。若当前的\([i-m+1,i]\)可以直接匹配,或是由通配符匹配。则\(f_i=......
  • Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in u
    目录Idea启动SpringBoot程序报错:Vebserverfailedtostart.Port8082wasalreadyinuse一、解决办法1、查找占用端口的进程2、结束进程①在任务管理器中终结指定pid的进程②在命令提示符中结束进程 3、重新启动项目4、对于macOS和Linux系统二、博主亲历三、为......
  • CF280C Game on Tree题解
    题目描述给定一棵有根树,结点编号从1到n。根结点为1号结点。对于每一次操作,等概率的选择一个尚未被删去的结点并将它及其子树全部删去。当所有结点被删除之后游戏结束。也就是说,删除1号结点后游戏即结束。要求求出删除所有结点的期望操作次数。不是哥们,我好不容易国庆......
  • 电影《749局》迅雷百度云下载资源4K分享[1.16GB/2.72GBMKV]高清加长版【1280P已完结】
    电影《749局》的深度剖析与全面解读电影《749局》是一部集科幻、冒险、动作与奇幻元素于一体的力作,由陆川编剧并执导,王俊凯、苗苗、郑恺、任敏、辛柏青领衔主演,李晨特邀主演,张钧甯、李梦、杨皓宇特别主演。影片于2024年国庆档在中国大陆上映,以其独特的科幻设定、宏大的视觉效......
  • PMP--三模--解题--71-80
    文章目录7.成本管理--S曲线--S曲线对累计值进行监督和报告--S曲线可以同时报告成本与进度情况。适用于预测和敏捷项目。14.敏捷--信息发射源--是一种可见的实物展示其向组织内其他成员提供信息在不干扰团队的情况下即时实现知识共享。71、[单选]项目经理正在为刚刚进入......
  • P8808 [蓝桥杯 2022 国 C] 斐波那契数组
    Hello大家好,我是小亦,今天一大早就来更东西了嘿嘿,不知道现在大家有没有回老家的或去玩的,那有没有扎在家的,呜呜呜我就是宅在家的QWQ,唉没办法啊,好那么好不了这些了qwq,今天我来讲的题目是来自蓝桥杯2022年国C题目也就是第三道题,名叫:斐波那契数组,其实这道题呢,嗯比较的难所以呢我也......
  • P10280 Cowreography G
    P10280CowreographyG贪心本题的证明中涵盖了多种证明方法:分类讨论,交换两个,整体策略,堪称贪心证明之典范符号约定令\(s_x\)表示最初字符串的不同\(1\)位置,\(t_x\)表示最终字符串的不同\(1\)位置Theorem1:交换\(a_i,a_j(i>j,a_i\nea_j)\)的最优步数为\(\lceil\fr......