• 8/14 思维+经典dp基础+tarjan算法


    D. Empty Graph

    题意可转化为给定一个数组,选定一个区间,两点之间的连线为[l,r]中的最小值,问在这些边中最大值是多少
    思路:这次D题想的方向是对的,但有些情况没有考虑到。别人用二分写的,这我就有点迷惑了,属实没往这个方向想。
    1.正常情况下m==0 不修改,则max(ans,min(a[i],a[i+1]))
    2.a数组中前k小的数都会被修改,则max(ans,max(a[i],a[i+1]))
    3.一种情况要特判。数组:2 4 4 9 ,若k=2,则a[2]=a[3]=b[2],这种情况则比较b[m]*2和9

    #include
    #define endl '\n'
    #define re register
    #define int long long
    #define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
    #define maxn 1000000000LL
    using namespace std;
    const int N=2e5+10;
    const int inf=0x3f3f3f3f;
    const int mod=1e7+7;
    int n,m,a[N],b[N],ans;
    
    void solve()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            cin>>a[i],b[i]=a[i];
        ans=0;
        if(m==n)
        {
            ans=maxn;
            cout<<ans<<endl;return;
        }
        sort(b+1,b+n+1);
        for(int i=1;i<n;i++)
        {
            //正常情况下m==0 不修改
            ans=max(ans,min(a[i],a[i+1]));
            //a数组中前k小的数都会被修改
            if(min(a[i],a[i+1])<=b[m])
                ans=max(ans,max(a[i],a[i+1]));
    
            //若两个数都小于b[m]
            if(a[i]<=b[m]&&a[i+1]<=b[m])
            {
                if(a[i]==b[m]&&a[i+1]==b[m]&&b[m]!=b[m-1])
                    continue;
                ans=max(ans,maxn);
            }
        }
        ans=max(min(ans,b[m+1]*2),min(b[m]*2,m>1?maxn:b[n]));
        cout<<ans<<endl;
    }
    signed main()
    {
        int t;cin>>t;
        while(t--)
            solve();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    经典DP复习

    无后效性:由过去推现在;要求满足最优子结构

    最长上升子序列(LIS)

    O(n^2)做法:

    void solve()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i],dp[i]=1;
        //dp[i]记录以i结尾的最长上升子序列
        int ans=0;
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<i;j++)
                if(a[j]<a[i])
                    dp[i]=max(dp[i],dp[j]+1);
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    采用二分查找求LIS:复杂度O(n*logn)
    b数组存储的并非是最长上升子序列,但是长度等于最长上升子序列;
    通过不断用小值替换,使得子序列更具有潜力。
    二分查找第一个大于等于x的位置,即为lower_bound

    const int N=2e5+10;
    const int inf=0x3f3f3f3f;
    const int mod=1e7+7;
    int n,a[N],b[N],len;
    int r_find(int x)
    {
        int l=1,r=len,mid,ans;
        while(l<=r)
        {
            mid=l+r>>1;
            if(x<=b[mid])
                r=mid-1,ans=mid;
            else
                l=mid+1;
        }
        return ans;
    }
    void solve()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i];
        len=1,b[1]=a[1];
        for(int i=2;i<=n;i++)
        {
            if(a[i]>b[len])
                b[++len]=a[i];
            else
                b[r_find(a[i])]=a[i];
        }
        cout<<len<<endl;
    }
    signed main()
    {
        solve();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    最长不下降子序列

    加一个等号:

    for(int i=2;i<=n;i++)
        {
            for(int j=1;j<i;j++)
                if(a[j]<=a[i])
                    dp[i]=max(dp[i],dp[j]+1);
            ans=max(ans,dp[i]);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二分查找第一个大于x的位置,即为upper_bound;
    需要改动两处:

    int r_find(int x)
    {
        int l=1,r=len,mid,ans;
        while(l<=r)
        {
            mid=l+r>>1;
            if(x<b[mid])
                r=mid-1,ans=mid;
            else
                l=mid+1;
        }
        return ans;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    打印最长上升子序列:

    int n,a[N],dp[N],p[N];
    void path(int i)
    {
        if(p[i]) 
            path(p[i]);
        cout<<a[i]<<" ";
    }
    void solve()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i],dp[i]=1;
        //dp[i]记录以i结尾的最长上升子序列
        int ans=0,g=0;
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<i;j++)
                if(a[j]<=a[i]&&dp[i]<dp[j]+1)
                {
                    dp[i]=dp[j]+1;p[i]=j;
                }
            if(dp[i]>ans)
            {
                ans=dp[i];
                g=i;        //记录最长位置
            }
        }
        path(g);
        cout<<ans<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    Tarjan(塔扬)算法

    强连通分量:极大的联通子图
    https://www.bilibili.com/video/BV1SY411M7Tv?spm_id_from=333.999.0.0&vd_source=91973ada1213cf6ba2cbb43a2bebd2e8
    dfn[x]:结点x第一次被访问的顺序
    low[x]:结点x所能访问的最早时间戳
    记录SCC,极大连通子图。具体注解在代码中:

    vector<int>e[N];
    int dfn[N],low[N],tot;
    int stk[N],instk[N],top;
    int scc[N],siz[N],cnt;
    void tarjan(int x)
    {
        //盖戳、入栈
        dfn[x]=low[x]=++tot;
        stk[++top]=x,instk[x]=1;
        for(int y:e[x])
        {
            if(!dfn[y])
            {
                tarjan(y);
                //回x时及时更新low
                low[x]=min(low[x],low[y]);
            }
            else if(instk[y]) //若x已访问且在栈中
                low[x]=min(low[x],dfn[y]);
        }
        if(dfn[x]==low[x]) //若x时SCC的根
        {
            int y;++cnt;
            do{
                y=stk[top--];instk[y]=0; //出栈
                scc[y]=cnt; //SCC编号
                ++siz[cnt];
            }while(y!=x)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    P2812 校园网络【[USACO]Network of Schools加强版】

    SCC 缩点
    思路:
    利用模板进行缩点操作
    问题一:至少选几所学校作为共享软件的母机,能使每所学校都可以用上
    解:统计缩点后入度为0点的数量
    问题二:至少要添加几条线路能使任意一所学校作为母机都可以使别的学校使用上软件
    解:统计缩点后所有点的入度和出度。使得所有点组成一个环,添加的边数为入读点和出度点中的较大值。

    #include
    #define endl '\n'
    #define re register
    #define int long long
    #define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
    #define maxn 1000000000LL
    using namespace std;
    const int N=2e5+10;
    const int inf=0x3f3f3f3f;
    const int mod=1e7+7;
    vector<int>e[N];
    int dfn[N],low[N],tot;
    int stk[N],instk[N],top;
    int scc[N],siz[N],cnt;
    int n,din[N],dout[N];
    void tarjan(int x)
    {
        //盖戳、入栈
        dfn[x]=low[x]=++tot;
        stk[++top]=x,instk[x]=1;
        for(int y:e[x])
        {
            if(!dfn[y])
            {
                tarjan(y);
                //回x时及时更新low
                low[x]=min(low[x],low[y]);
            }
            else if(instk[y]) //若x已访问且在栈中
                low[x]=min(low[x],dfn[y]);
        }
        if(dfn[x]==low[x]) //若x时SCC的根
        {
            int y;++cnt;
            do{
                y=stk[top--];instk[y]=0; //出栈
                scc[y]=cnt; //SCC编号
                ++siz[cnt];
            }while(y!=x);
        }
    }
    void solve()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            int x;
            while(cin>>x&&x)
                e[i].push_back(x);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i])
                tarjan(i);
        for(int i=1;i<=n;i++)
        {
            for(int j:e[i])
            {
                if(scc[i]!=scc[j])
                    dout[scc[i]]++,din[scc[j]]++;
            }
        }
        int ans1=0,ans2=0;
        for(int i=1;i<=cnt;i++)
        {
            if(!din[i]) ans1++;
            if(!dout[i]) ans2++;
        }
        cout<<ans1<<endl;
        if(cnt==1)
            cout<<0<<endl;
        else
            cout<<max(ans1,ans2)<<endl;
    }
    signed main()
    {
        solve();
        return 0;
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
  • 相关阅读:
    No.6软件工程之需求分析
    吴恩达-机器学习-k-means聚类算法
    深入了解Redis的TYPE命令
    Python数据分析与机器学习31-SVM案例:人脸识别
    OpenCV基本操(IO操作,读取、显示、保存)
    110道Java初级面试题及答案(最新Java初级面试题大汇总)
    IDEA创建动态web工程并配置Tomcat服务器
    网络变压器工厂:了解POE POE+ 网络变压器(网络隔离滤波器)
    来自2年前端的面经
    【鸿蒙OS】【ArkUI】鸿蒙OS UI布局适配终极攻略
  • 原文地址:https://blog.csdn.net/weixin_51934288/article/details/126328016