图论数据结构DP题目汇总2

图论数据结构dp

codeforces 1900上下,简单的1600

A - DZY Loves Sequences

题意:
给你一个序列,让你找一个子序列,使这个子序列中的数最多修改一次,这个子序列为最长连续上升子序列。这里的子序列要求是连续的
思路:
一眼dp问题是,阶段是从1到n有两个状态到i位置为结尾,修改一次的最长上升子序列是的长度,和i为结尾,修改0次最长上升子序列的长度。明显,如果a[i]>a[i-1],有转移方程 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + 1 dp[i][j] = dp[i-1][j]+1 dp[i][j]=dp[i1][j]+1,如果不成立,我们很容易想到, d p [ i ] [ 1 ] = d p [ i ] [ 0 ] + 1 dp[i][1] = dp[i][0] + 1 dp[i][1]=dp[i][0]+1 ,并且 d p [ i ] [ 0 ] = 1 dp[i][0] =1 dp[i][0]=1但是我们忽略了转移的条件,如果序列为3,2,3其实我们是转移不过来的,我们需要对比 a [ i ] a[i] a[i] a [ i − 2 ] a[i-2] a[i2]位置的大小

AC代码:
代码写的很烂,不轻易看这一版

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int dp[maxn][2];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1 ;i <= n;i++){
        scanf("%d",&a[i]);
        // dp[i][0] = dp[i][1] = 1;
    }
    int ans = 1;
    int mx = 0,mx2 = 0;
    for(int i = 1 ;i <= n;i++){
        
        if(a[i]>mx){
            dp[i][0] = dp[i-1][0] + 1;
            mx = a[i];
        }
        else {
            dp[i][0] = 1;
            mx = a[i];
            dp[i][1] = dp[i-1][0] + 1;
            if(i<2)mx2 = a[i-1] + 1;
            if(a[i]>a[i-2]+1)mx2 = a[i];
            else mx2 = a[i-1] + 1;
        }
        if(a[i]>mx2){
            dp[i][1] = dp[i-1][1] + 1;
            mx2 = a[i];
        }
        if(dp[i][1] == 0){
            mx2 = 0;
        }
        // else {
        //     dp[i][1] = 1;
        //     mx2 = a[i];
        // }
        ans = max(ans,dp[i][0]+1);
        ans = max(ans,dp[i][1]);
    }
    if(ans>n)ans = n;
    cout<<ans<<endl;
}

B - Modulo Sum

题意:
给你一个序列,和一个m,问是否存在从这个序列中选出一些数,随意选,但是不能重复,使这些数的和可以被m整除

思路:
一看序列a 的数据范围很大,然后这个m的范围1000,先让a序列取模m,取模后的结果均小于m,并且对于实际结果没有影响。然后就是在开始我的想法是这个样的,结果均小于m,那么如果我就可以等价于从这些数中找到一些数,让这些数之和等于m了吗,这是一个经典的背包问题,数不重复使用就是0/1背包,然后我们外层循环i表示使用到的数都在i位置之前,内层循环表示和为j然后dp数组压缩到一维,倒序循环表示0/1背包。但是,我又发现,就算我可以频繁取模,但是我的和可以超过m加入8,8两个数,m=11,我使用8,8可以组合成16,16和 16 m o d 11 = 5 16 mod 11=5 16mod11=5等价所以我的dp[5]应该置1,所以我很容易就想到了 d p [ j m o d m ] ∣ = d p [ j − a [ i ] ] dp[j mod m ] |= dp[j-a[i]] dp[jmodm]=dp[ja[i]]把j的范围扩大到2m,但是wa了,于是我又发现,因为我们是从后往前转移,如果取模就很破坏这个从后往前转移的条件,因为我转移16,就会转移到5上,我这次就会多算一个5,于是我就先不去模,在转移完成之后,在手动将m-2m,范围能转移到的数据在拷一份到0-m即可

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+6;
ll a[maxn];
ll dp[maxn];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i++){
        scanf("%lld",&a[i]);
        a[i] %= m; 
        if(a[i]==0){
            printf("YES\n");
            return 0;
        }
    }
    dp[0] = 1;
    for(int i = 1;i <= n;i++){
        for(int j = m*2;j >= a[i];j--){
            // dp[j%(m+1)] |= dp[(j-a[i])];
            if(j>m){
                dp[j] |= dp[(j-a[i])];
                // cout<<j%m<<" "<<dp[j%m]<<" "<<j-a[i]<<endl;
            }
            else dp[j] |= dp[(j-a[i])];
            // cout<<j<<" "<<j%m<<" "<<dp[j]<<" "<<dp[j%m]<<" "<<a[i]<<endl;
        }
        for(int j = 1;j <= m; j++)dp[j] |= dp[j+m];
        // for(int i = 1;i<=m;i++){
        //     cout<<dp[i]<<" ";
        // }
        // cout<<endl;
    }
    if(dp[m]==1){
        printf("YES\n");
    }
    else printf("NO\n");
}

Rescue Nibel!

题意:有一个数组,给你一堆 l , r l,r l,r,然后让你在l到r时间内这个灯会亮,要求选取k个灯,要求这k个灯有可以同时量,问能选出多少种k个灯的组合

思路:
思维我哭了,思维题有做不出来,崩溃中,就是我一直在纠结,当某一个时刻的亮灯数大于k就可以在这个时刻选取k个灯,就是一个组合数的问题,但是!会有重复,比如1时刻有四个灯亮,但是下一个时刻还是有四个灯亮,我们不知道这两个时刻的灯是否是同一些灯。
实际上,我们我们记录某一位置开始的时刻的亮灯情况,然后就是我们选的集合必定包括该时刻开始亮的灯,这样就不会重复,即某位置 C ( s u m , k ) − C ( s u m − s t a r t , k ) C(sum,k) - C(sum-start,k) C(sum,k)C(sumstart,k),然后差分维护区间修改,加离散化,组合数学的板子

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+5;
const int Maxn = maxn;
const int mod = 998244353;
int a[maxn<<1];
int c[maxn<<1];
struct Ques{
    int u,v;
}qu[maxn];
int st[maxn<<1];
//预处理阶乘表 fac[n]=(fac[n-1]*n)%p=(n!)%p
ll fac[Maxn+10];
void setFac(int n){
	fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=1LL*fac[i-1]*i%mod;
	}
}
ll binaryPow(ll a,ll b,ll m){
	ll ans = 1;
    while(b){
        if(b & 1){
            ans = ans * a % m;
        }
        a = a * a % m;
        b >>= 1; 
    } 
    return ans;
}
//计算组合数 $C_{n}^{m}$
ll C(int n,int m){
	if(n<m) return 0;
	if(n<0||m<0) return 0;
	ll t=fac[n-m]*fac[m]%mod;
    ll inv=binaryPow(t,mod-2,mod);
	return fac[n]*inv%mod;
}

int main(){
    int n,k;
    cin>>n>>k;
    setFac(n);
    int cnt = 0;
    for(int i = 1;i<=n;i++){
        scanf("%d%d",&qu[i].u,&qu[i].v);
        c[++cnt] = qu[i].u;
        c[++cnt] = qu[i].v;
    }
    sort(c+1,c+cnt+1);
    int nn = unique(c+1,c+cnt+1) - (c+1);
    for(int i = 1;i<=n;i++){
        qu[i].u = lower_bound(c+1,c+nn,qu[i].u) - c;
        qu[i].v = lower_bound(c+1,c+nn,qu[i].v) - c;
    }
    for(int i = 1;i <= n;i++){
        int u = qu[i].u;
        int v = qu[i].v;
        a[u]++;
        a[v+1]--;
        st[u]++;
    }
    int sum = 0;
    ll ans = 0;
    for(int i = 1;i <= 6e5+1;i++){
        sum += a[i];
        if(sum>=k){
            ans = ((ans%mod + C(sum,k)%mod)%mod - C(sum-st[i],k)%mod+mod)%mod;
        }
        // cout<<ans<<endl;
    }
    cout<<ans<<endl;

}

Coffee Break

题意:大概就是给定一天的休息时间,然后一天的总时间,问最少多少天才能把这些休息时间都使用完,使得两次休息时间间隔大于d

思路:
有点想贪心,就是排个序,如果我在第i个休息时间,发现i-d之前还是有没有没有休息的时间,我就可以让i和i-d在一天内完成休息,加入到并查集中去即可。在处理一下细节

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
struct A{
    int id,v;
}a[maxn];
bool cmp(A x,A y){
    return x.v<y.v;
}
bool cmp2(A x,A y){
    return x.id<y.id;
}
int fa[maxn];
int get(int x){
    if(fa[x]==x)return x;
    return fa[x] = get(fa[x]);
}
void merge(int u,int v){
    int p = get(u);
    int q = get(v);
    if(p!=q){
        fa[p] = q;
    }
}
int num[maxn];
int main(){
    int n,m,d;
    scanf("%d%d%d",&n,&m,&d);
    for(int i  = 1 ;i<=n;i++){
        fa[i] = i;
        scanf("%d",&a[i].v);
        a[i].id=i;
    }
    sort(a+1,a+n+1,cmp);
    int p=1;
    for(int i = 1;i <= n;i++){
        if(a[i].v-d>a[p].v){
            
            merge(a[i].id,a[p].id);
            p++;
        }
    }
    int cnt = 0;
    for(int i = 1;i<=n;i++){
        if(fa[a[i].id]==a[i].id){
            cnt++;
            num[a[i].id] = cnt;
        }
    }
    sort(a+1,a+n+1,cmp2);
    cout<<cnt<<endl;
    for(int i = 1;i<=n;i++){
        printf("%d ",num[get(i)]);
    }

}

F - TediousLee

题意:给你一棵数生成的规律,给你一个染色的策略,问最多染多少个节点

思路:经典简单题不会做,其实就是一个递推解法,发现一颗高度为h的数是由四部分构成,一个根节点,一个高度为h-1的树,两个高度为h-2的树,转移方程 a n s [ i ] − a n s [ i − 1 ] + a n s [ i − 2 ] ∗ 2 ans[i] - ans[i-1] + ans[i-2]*2 ans[i]ans[i1]+ans[i2]2,另外就是判断该树的根节点是否被染色,如果子树的根节点全部没有被染色,该树方案数在加1,并且标记该树根节点被染色。

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 2e6+6;
ll a[maxn];
int rt[maxn];
int main(){
    int t;
    scanf("%d",&t);
    a[1] = 0;
    a[2] = 0;
    a[3] = 1;
    rt[3] = 1;
    a[4] = 1;
    rt[4] = 0;
    for(int i = 5;i<=2e6;i++){
        a[i] = (a[i-1] + a[i-2]*2)%mod;
        if(rt[i-1]==0&&rt[i-2]==0){
            rt[i] = 1;
            a[i]++;
        }
    }
    while(t--){
        int n;
        scanf("%d",&n);
        printf("%lld\n",a[n]*4%mod);
    }
}

Carrying Conundrum

题意:给一个错误的加法模式,给出一个和,问有多少种加数可以按错误的模式相加,得到这个和

思路,看他的错误模式,将原数拆分奇数位置,偶数位置,然后拆成两个数,然后就都是正常的模式了,因为加数不能有0,所以需要在减去0 的情况

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        scanf("%d",&n);
        ll odd = 0;
        ll even = 0;
        int len = 1,len2 = 1;
        int l = 0;
        while(n){
            if(l%2==0){
                odd += (n%10)*len;
                len*=10;
            }
            else {
                even += (n%10)*len2;
                len2*=10;
            }
            l++;
            n/=10;
        }
        if(odd==0||even==0){
            printf("%lld\n",max(odd,even)-1);
        }
        else {
            printf("%lld\n",(even+1)*(odd+1)-2);
        }
    }

}

Pashmak and Parmida’s problem

题意:给你一个f函数然后给要求

思路:预处理处一个位置算i的f函数的值和j的f函数的值,转化为两个数组逆序对的问题,在安树状数组处理即可

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
int a[maxn];
map<int ,int>tol;
map<int,int>bef;
int pre[maxn];
int pos[maxn];
int c[maxn];
int n;
void add(int x,int v){
    while(x<=n){
        c[x] += v;
        x+=lowbit(x);
    }
}
int ask(int x){
    int ans = 0;
    while(x){
        ans += c[x];
        x -= lowbit(x);
    }
    return ans;
}
int main(){ 
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
        tol[a[i]] += 1;
    }
    for(int i = 1;i <= n;i++){
        bef[a[i]] += 1;
        pre[i] = bef[a[i]];
        pos[i] = tol[a[i]]-bef[a[i]]+1;
        // cout<<i<<":"<<pre[i]<<" "<<pos[i]<<endl;
    }
    ll ans = 0;
    for(int i = n;i>0;i--){ 
        
        ans += ask(pre[i]-1);
        add(pos[i],1);
    }    
    cout<<ans<<endl;
}
计算无向图中的最小环

如果出现环套环,这种情况
dfs标记深度,判环是不可取的,并查集维护集合大小也不可续,dfs搜索到自身也不可取

我么给出了一种复杂度比较大的方法
Floyd方法:

众所周知,Floyd算法求最短路的过程是三重循环。
当最外层恰好循环到 k k k 时,代表着目前所求出的最短路所含的点集为 [ 1 , k ] [1,k] [1,k]
在第k次循环时 d p [ i ] [ j ] dp[i][j] dp[i][j]是i到j的最短路,并且不经过k,我们看k这个点,他经过了两个点,然后这两个点的最短路是 d p [ i ] [ j ] dp[i][j] dp[i][j],那说明经过至少有k,i,j三个点的最小环就可以求出来了,

注意初始化dp数组的值
例题:

  • Shortest Cycle
    首先我们看出来了,应该是抽屉原理(n个抽屉,如果我们放n+1个小球,必定会有两个小球在一个抽屉),一共就位,如果一位存在三个数都为1,那么答案就是3,也就是说如果给定的数个数大于64*2,那么必行会有某一位存在三个1,
    那么也就是把问题的规模放到的100左右,然后就是刚好可以利用floyd算法求一个最小环了
    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5+5;
    ll a[maxn];
    ll g[200][200];
    ll dp[200][200];
    ll ans = INF;
    int main(){
        int n;
        int cnt = 0;
        scanf("%d",&n);
        for(int i = 0;i<n;i++){
            ll x;scanf("%lld",&x);
            if(x!=0)a[cnt++]=x;
        
        }
        if(cnt>64*2){
            printf("3\n");
            return 0;
        }
        for(int i =0;i < cnt;i++){
            for(int j = 0 ;j<cnt;j++){
                dp[i][j] = 100;
                g[i][j] = 100;
            }
        }
        for(int i = 0;i<cnt;i++){
            dp[i][i] = 0;
            for(int j = 0;j < i;j++){
                if((a[i]&a[j])!=0){
                    g[i][j] = g[j][i] = 1;
                    dp[i][j] = dp[j][i] = 1;
                }
            }
        }
        for(int k = 0;k < cnt;k++){
            for(int i = 0;i < k; i++){
                for(int j = i+1;j<cnt;j++){
                    ans = min(ans,dp[i][j] + g[i][k] + g[k][j]);
                }
            }

            for(int i=0;i<cnt;i++){
                for(int j = 0;j<cnt;j++){
                    dp[i][j] = min(dp[i][j],dp[i][k] + dp[k][j]);
                }
            }
        }
        
        if(ans>=100){
            cout<<-1<<endl;
        }
        else cout<<ans<<endl;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值