• dp专题


    逆序dp

    P1280 尼克的任务

    链接:https://www.luogu.com.cn/problem/P1280

    本题的关键在于顺推不好做 想着要倒推 想着要统计开始的时间点 排序是为了消除后效性!!!!是个非常好的模型

    #include<iostream>  
    #include<algorithm>  
    using namespace std;  
    long int n,k,sum[10001],num=1,f[10001];  
    struct ren//结构体,一起排序 ,从大到小   
    {  
        long int ks,js;  
    };  
    ren z[10001];  
    int cmp(ren a,ren b)  
    {  
        return a.ks>b.ks;  
    }  
    int main()  
    {  
        long int i,j;   
        cin>>n>>k;  
        for(i=1;i<=k;i++)  
        {  
        cin>>z[i].ks>>z[i].js;    
        sum[z[i].ks]++;  
        }  
        sort(z+1,z+k+1,cmp);  
        for(i=n;i>=1;i--)//倒着搜   
        {  
            if(sum[i]==0)  
            f[i]=f[i+1]+1;  
            else for(j=1;j<=sum[i];j++)  
            {  
                if(f[i+z[num].js]>f[i])  
                f[i]=f[i+z[num].js];  
                num++;//当前已扫过的任务数   
            }  
        }  
        cout<<f[1]<<endl;  
    }  
    

    逆序dp

    这个题目和费用提前算不太一样 两者本质上都是消除后效性

    这个题不同的就是不知道后面到底哪些要选 而费用提前算是后面的一定都是会选的

    所以这个题巧妙地从后往前转移 这样转移前面的一定是后面最优策略 转移方程非常像算期望值

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define maxn 100005
    double f[maxn];
    double n,k,c,w;
    int a[maxn],b[maxn];
    int main()
    {
        int i;
        scanf("%lf%lf%lf%lf",&n,&k,&c,&w);
        for(i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
        for(i=n;i>=1;i--)
        {
            if(a[i]==1) f[i]=max(f[i+1],f[i+1]*(1-k/100)+b[i]);
            if(a[i]==2) f[i]=max(f[i+1],f[i+1]*(1+c/100)-b[i]);
        }
        printf("%.2lf",f[1]*w);
    }
    
    

    逆序dp

    分析:

    发现我们可以很随意的写出dp[i][j]表示初始点走到<i,j>需要的最小值

    但是很快我们就会发现问题 跟新后面必须还得要当前路径权值和 对后面的转移会造成影响 也就是具有后效性 这样是没法维护的

    我们考虑逆着来设计 dp[i][i]表示从<i,j>走到终点需要的最小值 转移的时候我们只需要知道从初始点走到<i,j>的最大路径和即可

    class Solution {
    public:
        int calculateMinimumHP(vector<vector<int>>& dungeon) {
            int n = dungeon.size(), m = dungeon[0].size();
            vector<vector<int>> dp(n + 1, vector<int>(m + 1, INT_MAX));
            dp[n][m - 1] = dp[n - 1][m] = 1;
            for (int i = n - 1; i >= 0; --i) {
                for (int j = m - 1; j >= 0; --j) {
                    int minn = min(dp[i + 1][j], dp[i][j + 1]);
                    dp[i][j] = max(minn - dungeon[i][j], 1);
                }
            }
            return dp[0][0];
        }
    };
    
    

    反向dp(结果推反推数据)

    先考虑一个简单的问题:

    代码很好写:

    int f[5005], a[5005], n, m;
    void slove() {
    	cin >> n >> m;
    	for (int i = 1; i <= n; i++)cin >> a[i];
    	f[0] = 1;
    	for (int i = 1; i <= n; i++) {
    		for (int j = m; j >= 0; j--) {
    			(f[a[i] + j] += f[j]) %= mod;
    			(f[a[i] / 2 + j] += f[j]) %= mod;
    		}
    	}
    	for (int i = 1; i <= m; i++)cout << f[i] << " ";
    	cout << endl;
    }
    

    重点是下一个问

    分析:

    由于这个dp相互都有关联,我们必须找到一个突破口。可以发现,当前所有f数组中,最小f[i]非0的i,只能是由一个西瓜的一半贡献而来的。

    那我们只需要模仿这上面的dp,依次反向的减去这个i的贡献即可。

    int f[5005];
    int m;
    vector<int>a;
    void slove() {
    	cin >> m;
    	for (int i = 1; i <= m; i++)cin >> f[i];
    	f[0] = 1;
    	for (int i = 1; i <= m; i++) {
    		while (f[i]) {
    			a.push_back(2 * i);
    			for (int j = 0; j <= m; j++) {
    				f[i + j] -= f[j]; f[i + j] = (f[i + j] + mod) % mod;
    				f[2 * i + j] -= f[j]; f[2 * i + j] = (f[2 * i + j] + mod) % mod;
    			}
    		}
    	}
    	cout << a.size() << endl;
    	for (int x : a)cout << x << " ";
    	cout << endl;
    }
    

    反向dp(结果反推数据)

    http://acm.hdu.edu.cn/showproblem.php?pid=6092

    分析:

    和上面那个题目是一样的

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define mem(s,t) memset(s,t,sizeof(s))
    #define D(v) cout<<#v<<" "<<v<<endl
    #define inf 0x3f3f3f3f
    //#define LOCAL
    inline void read(ll &x){
        x=0;char p=getchar();
        while(!(p<='9'&&p>='0'))p=getchar();
        while(p<='9'&&p>='0')x*=10,x+=p-48,p=getchar();
    }
    const ll N =1e4+10;
    ll B[N];
    int main() {
    #ifdef LOCAL
        freopen("1006.in","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
        ll t;
        read(t);
        while(t--){
            ll n,m;
            read(n);read(m);
            for(int i=0;i<=m;i++){
                read(B[i]);
            }
            ll st=0,f=1;
            for(ll x=0;x<n;x++){
                for(ll i=1;i<=m;i++){
                    if(B[i]){
                        st=i;
                        break;
                    }
                }
                if(f) printf("%lld",st),f=0;//末尾空格处理
                else printf(" %lld",st);
                for(ll j=st;j<=m;j++){
                    B[j]-=B[j-st];
                }
            }
            puts("");
        }
        return 0;
    }
    
    

    这个dp以前没见过 确实不会 怎么才能保证两两互不相交呢 在我印象里面没有这样操作过的dp

    考虑换个想法 设dp[i,j,k]表示 前i个 两者差值为j 用了k次加倍 因为下标不能为负 所以整体向右偏移1300

    初始化 dp[0,1300,0]=0 答案 max{dp[n,1300,i]} i属于[0,k]

    转移方程:dp[i,j,k]=max(dp[i-1,j+v[i],k]+w[i],dp[i-1,j-v[i],k]+w[i],dp[i-1,j+2v[i],k-1]+w[i],dp[i-1,j-2v[i],k-1]+w[i])

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #include<queue>
    using namespace std;
    const int N=103;
    typedef long long ll;
    ll f[N][3003][N],w[N],v[N];
    int main()
    {
    	memset(f,-0x3f,sizeof f);
    	f[0][1300][0]=0;
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    		scanf("%lld%lld",&w[i],&v[i]);
    	for(int i=1;i<=n;i++)
    	for(int j=0;j<=m;j++)
    	for(int k=0;k<=2600;k++)
    	{
    		f[i][k][j]=f[i-1][k][j];
    		if(k>=2*v[i]&&j>=1)
    			f[i][k][j]=max(f[i][k][j],f[i-1][k-2*v[i]][j-1]+w[i]);
    		if(k>=v[i])
    			f[i][k][j]=max(f[i][k][j],f[i-1][k-v[i]][j]+w[i]);
    		if(k+2*v[i]<=2600&&j>=1)
    			f[i][k][j]=max(f[i][k][j],f[i-1][k+2*v[i]][j-1]+w[i]);
    		if(k+v[i]<=2600)
    			f[i][k][j]=max(f[i][k][j],f[i-1][k+v[i]][j]+w[i]);
    	}
    	ll ans=-0x3f3f3f3f;
    	for(int i=0;i<=m;i++)
    		ans=max(ans,f[n][1300][i]);
    	cout<<ans;
    	return 0;
    }
    
    

    https://www.luogu.org/problem/P2577

    分析:

    首先本题不是贪心排序就是dp

    再数据范围<=200,就只有可能是dp

    考虑本题,容易想到应该尽量让那些打饭快却吃饭慢的排在前面

    又因为所有人打饭的总时间是一定的,

    即无论怎么安排所有人打完饭都会耗费这么多时间

    所以我们只用考虑吃饭慢的排在前面则一定是最优的

    但此时有两个窗口,怎样安排就要dp了

    • f[i,j]记录前i个人排队,第一队用时为j的情况下最大用时。

    • 不记录第2队状态的原因是可以由第一队状态推出来。

    • 为了便于计算,建议使用前缀和维护一下,可以较为简易的计算出第2队的情况。

    • 那么我们就得到了这样个方程:

      加入第一队的情况下: f[i,j]=min(f[i,j],max(f[i-1,j-a[i].x],j+a[i].y));

      当前最小时间为上一个人用的时间和这一个人用的时间的最大值

      加入第二队同理

      f[i,j]=max(f[i-1,j],a[i].y+b[i]-j)其中b[i]为前i个人排队所用的总时间

      然而200*40000好像有点大,降维呗!

      类似背包一样降维就行

      code by std:

    #include<bits/stdc++.h>
    using namespace std;
    struct lsg{int x,y;}a[1000];
    int n,f[400001],sum,ans,b[1000];
    bool pd(lsg x,lsg y){return x.y>y.y;}
    int main(){
        ios::sync_with_stdio(false);
        cin>>n;
        for (int i=1;i<=n;i++)cin>>a[i].x>>a[i].y;
        sort(a+1,a+1+n,pd);memset(f,10,sizeof(f));f[0]=0;
        for (int i=1;i<=n;i++)b[i]=a[i].x+b[i-1];
        for (int i=1;i<=n;i++){
                for (int j=sum;j>=0;j--){
                    f[j+a[i].x]=min(f[j+a[i].x],max(f[j],a[i].y+j+a[i].x));//将i加入第一个队列
                        f[j]=max(f[j],a[i].y+b[i]-j);//将i加入第二个队列
                    }
                sum+=a[i].x;
            }
        ans=1e9;
        for (int i=1;i<=sum;i++)ans=min(ans,f[i]);
        cout<<ans<<endl;
    }
    

    https://www.luogu.org/problem/P2467

    这是一道好题

    题目描述

    求1-n排列组成的波动数列的个数

    因为要比较大小关系 所以dp里面最后数是多少是要记录的 又因为是每个数都不重复的排列

    所以定义dp[i,j]表示用前 i 个数 最后一个数为 j 的方案数

    dp[i,j]相当于dp[i-1,k]中原排列大于等于j的数都加1,再把j插到末尾后的新合法排列的方案数

    答案有“M"型与"W"型,显然方案数是一样的,这里只考虑"W"型的,最后把答案*2就行了


    用一个前缀和维护一下就好

    这时你可能会有疑问,为什么偶数是枚举[1,j-1],而奇数是枚举[j,i-1],

    因为只考虑“W”形态的,所以奇数一定是山峰的,而偶数一定山谷

    偶数就不用再每个加一了 直接加进去就好 奇数就要每个大于等于 j 的加一

    所以奇数枚举的一定要比前一个位置上的数大,偶数枚举的一定要比前一个位置上的数小

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define N 4211
    #define M(a) ((a)<=mod?(a):(a-mod))
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        while(c<'0'||c>'9'){
            if(c=='-')f=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9'){
            x=(x<<3)+(x<<1)+c-'0';
            c=getchar();
        }
        return x*f;
    }
    int n,mod;
    int dp[N][N],ans=0;
    int b[N];
    int lowbit(int x){
        return x&(-x);
    }
    void Add(int x,int d){
        while(x<=n){
            b[x]=M(b[x]+d);
            x+=lowbit(x);
        }
    }
    int Ask(int x){
        int ans=0;
        while(x){
            ans=M(ans+b[x]);
            x-=lowbit(x);
        }
        return ans;
    }
    int main(){
        n=read(),mod=read();
        for(int i=1;i<=n;i++){
            dp[1][i]=1;
            Add(i,1);
        }
        for(int i=2;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i&1){
                    if(i>j){
                        dp[i][j]=M(Ask(i-1)-Ask(j-1)+mod);
                    }
                }
                else{
                    dp[i][j]=Ask(j-1);
                }
            }
            memset(b,0,sizeof(b));
            for(int j=1;j<=n;j++){
                Add(j,dp[i][j]);
            }
        }
        for(int i=1;i<=n;i++){
            ans=M(ans+dp[n][i]);
        } 
        cout<<2*ans%mod<<endl;
        return 0;
    }
    

    https://www.luogu.org/problem/P1005

    分析:

    发现啊,每一行怎么取数是互不干扰的,,只用分别处理每一行就好

    数据范围也在算法复杂度以内

    很好联想到区间dp

    dp[i,j]表示处理到该行,区间[i,j]的最优解

    考虑怎么转移

    只有从[i-1,j]和[i,j+1]转移过来(因为转移逐渐将区间缩小

    因为题目中说要取完,但是空区间是DP不出来的,然后就得手动模拟每个长度为1的区间

    具体高精啊,区间dp啊,见代码了

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    
    using namespace std;
    
    const int MAXN = 85, Mod = 10000; //高精四位压缩大法好 
    int n, m;
    int ar[MAXN];
    
    struct HP {
        int p[505], len;
        HP() {
            memset(p, 0, sizeof p);
            len = 0;
        } //这是构造函数,用于直接创建一个高精度变量 
        void print() {
            printf("%d", p[len]);  
            for (int i = len - 1; i > 0; i--) {  
                if (p[i] == 0) {
                    printf("0000"); 
                    continue;
                }
                for (int k = 10; k * p[i] < Mod; k *= 10) 
                    printf("0");
                printf("%d", p[i]);
            }
        } //四位压缩的输出 
    } f[MAXN][MAXN], base[MAXN], ans;
    
    HP operator + (const HP &a, const HP &b) {
        HP c; c.len = max(a.len, b.len); int x = 0;
        for (int i = 1; i <= c.len; i++) {
            c.p[i] = a.p[i] + b.p[i] + x;
            x = c.p[i] / Mod;
            c.p[i] %= Mod;
        }
        if (x > 0)
            c.p[++c.len] = x;
        return c;
    } //高精+高精 
    
    HP operator * (const HP &a, const int &b) {
        HP c; c.len = a.len; int x = 0;
        for (int i = 1; i <= c.len; i++) {
            c.p[i] = a.p[i] * b + x;
            x = c.p[i] / Mod;
            c.p[i] %= Mod;
        }
        while (x > 0)
            c.p[++c.len] = x % Mod, x /= Mod;
        return c;
    } //高精*单精 
    
    HP max(const HP &a, const HP &b) {
        if (a.len > b.len)
            return a;
        else if (a.len < b.len)
            return b;
        for (int i = a.len; i > 0; i--)
            if (a.p[i] > b.p[i])
                return a;
            else if (a.p[i] < b.p[i])
                return b;
        return a;
    } //比较取最大值 
    
    void BaseTwo() {
        base[0].p[1] = 1, base[0].len = 1;
        for (int i = 1; i <= m + 2; i++){
            base[i] = base[i - 1] * 2;
        }
    } //预处理出2的幂 
    
    int main(void) {
        scanf("%d%d", &n, &m);
        BaseTwo();
        while (n--) {
            memset(f, 0, sizeof f);
            for (int i = 1; i <= m; i++)
                scanf("%d", &ar[i]);
            for (int i = 1; i <= m; i++)
                for (int j = m; j >= i; j--) { //因为终值是小区间,DP自然就从大区间开始 
                    f[i][j] = max(f[i][j], f[i - 1][j] + base[m - j + i - 1] * ar[i - 1]); 
                    f[i][j] = max(f[i][j], f[i][j + 1] + base[m - j + i - 1] * ar[j + 1]);
                } //用结构体重载运算符写起来比较自然 
            HP Max;
            for (int i = 1; i <= m; i++)
                Max = max(Max, f[i][i] + base[m] * ar[i]);
            ans = ans + Max; //记录到总答案中 
        }
        ans.print(); //输出 
        return 0;
    }
    

    https://www.luogu.org/problem/P2511

    分析

    第一问,求最长的最短,很显然一个二分就行

    那第二问计数

    f[i,j]代表前i个数分成j块的方案数,

    f[i,j]=Σ f[k,j-1] (k>=left[i]&&k<i) ,而因为空间问题,是不可以开1000*50000个数组的,

    考虑用到前缀和 因为[ , j ] 都是由 [ , j-1]转移过来的 直接降维 每次更新完f数组 最后更新前缀数组s

    这是一道非常好的动规优化

    时间复杂度(N*M)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<cmath>
    using namespace std;
    
    int read()
    {
        int x=0,f=1;
        char ss=getchar();
        while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
        while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
        return x*f;
    }
    
    const int mod=10007;
    const int maxn=50010;
    int n,m,mx,ans;
    int a[maxn],sum[maxn];
    int dp[maxn],S[maxn];
    int rem[maxn];
    
    int check(int x)
    {
        int tot=0,len=0;
        for(int i=1;i<=n;++i)
        {
            if(len+a[i]>x) tot++,len=a[i];
            else len+=a[i];
            if(tot>m) return 0;
        }
        return tot<=m;
    }
    
    int DP(int x)
    {
        int k=0;
        for(int i=1;i<=n;++i)
        for(;k<i;++k)
        if(sum[i]-sum[k]<=x){ rem[i]=k; break;}
        
        int res=(sum[n]<=x);//因为后面是从2开始枚举的 特判是否有只能为1段的情况
    
        for(int i=1;i<=n;++i)
        {
        	if(sum[i]<=x) dp[i]=1;//初始化很重要 此时的dp[i] 表示前i个数分为1段的方案 如果没法分为1段的dp值就为0
        	S[i]=(S[i-1]+dp[i])%mod;
        }
        
        for(int i=2;i<=m+1;++i)
        {
            for(int j=1;j<=n;++j)
            {
                dp[j]=S[j-1];
                //非常常见的一种方法 用于转移的时候数组下标可能为负 取0
                if(rem[j]-1>=0) dp[j]=((dp[j]-S[rem[j]-1])%mod+mod)%mod;//注意减法出现负数
            }
            for(int j=1;j<=n;++j)
            S[j]=(S[j-1]+dp[j])%mod;
            
            res=(res+dp[n])%mod;
        }
        return res;
    }
    
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=n;++i) 
        a[i]=read(),sum[i]=sum[i-1]+a[i],mx=max(mx,a[i]);
        
        int L=mx,R=sum[n],mid;
        while(L<R)
        {
            mid=L+R>>1;
            if(check(mid)) ans=mid,R=mid;
            else L=mid+1;
        }
        printf("%d %d",ans,DP(ans));
        return 0;
    }
    

    皇宫看守[树的最小点覆盖问题]

    Description:

    太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
    皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
    可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

    Input:

    帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

    f[u][0]表示被父节点覆盖

    f[u][1]表示被子节点覆盖

    f[u][2]表示被自己覆盖

    只有f[u][1]的转移麻烦一点

    首先f[u][1]+=Σmin(f[v][1],f[v][2])

    但是还得保证至少v中有一个点要选它自己的

    所以记录d 表示子节点中min(f[v][2]-min(f[v][1],f[v][2]))

    最后f[u][1]+=d 即可

    #include<bits/stdc++.h>
    using namespace std;
    vector<int> s[1505];
    int w[1505];
    int f[1505][3];
    bool v[1505];
    void dp(int u){
    //    f[u][2]//self
    //    f[u][1]//son
    //    f[u][0]//fa
        int y;
        int d=0x7fffffff/2;
        int sz=s[u].size();
        for(int i=0;i<sz;i++){
            dp(s[u][i]);
            f[u][0]+=min(f[s[u][i]][2],f[s[u][i]][1]);
            f[u][1]+=min(f[s[u][i]][2],f[s[u][i]][1]);
            d=min(d,f[s[u][i]][2]-min(f[s[u][i]][2],f[s[u][i]][1]));
            f[u][2]+=min(f[s[u][i]][2],min(f[s[u][i]][1],f[s[u][i]][0]));
        }
        f[u][1]+=d;
        f[u][2]+=w[u];
    }
    int main(){
        int n;
        cin>>n;
        int num,k,r,m;
        for(int i=1;i<=n;i++){
            cin>>num>>k>>m;
            w[num]=k;
            for(int j=1;j<=m;j++){
                cin>>r;
                v[r]=1;
                s[num].push_back(r);
            }
        }
        int root;
        for(int i=1;i<=n;i++){
            if(v[i]==0){
                root=i;
                break;
            }
        }
        dp(root);
        cout<<min(f[root][1],f[root][2]);
        return 0;
    } 
    
    
    

    https://codeforces.com/problemset/problem/1646/D

    分析:

    其实很好发现 如果两个点相邻的话一定是不会都是good点的(死循环)

    所以变相就是求树的最大独立点集

    但是这个题还要求点权之和最小 所以非good点我们都设为1 相应地 good点即为度数和

    转移呢 就要在满足点集最大的条件下 去转移点权和最小

    还比较特殊 需要输出每个点的点权 所以还需要一个dfs顺序推过去就好

    注意:n=2的情况 相邻的点是能都成为good点的

    #include <iostream>
    #include <cstdio>
    #include <vector>
    using namespace std;
    
    int read(){
        int num=0, flag=1; char c=getchar();
        while(!isdigit(c) && c!='-') c=getchar();
        if(c == '-') c=getchar(), flag=-1;
        while(isdigit(c)) num=num*10+c-'0', c=getchar();
        return num*flag;
    }
    
    const int N = 290005;
    int T, n;
    vector<int> p[N];
    int f[N][2], d[N][2], fa[N];
    
    void dp(int x){
        f[x][1] = 1, d[x][1]= p[x].size(); d[x][0]=1;
        for(auto nex : p[x]){
            if(nex == fa[x]) continue;
            fa[nex] = x;
            dp(nex); 
            
            f[x][1] += f[nex][0];
            d[x][1] += d[nex][0];
    
            if(f[nex][1] == f[nex][0]) f[x][0]+=f[nex][1], d[x][0]+=min(d[nex][1], d[nex][0]);
            else if(f[nex][1] > f[nex][0]) f[x][0]+=f[nex][1], d[x][0]+=d[nex][1];
            else f[x][0]+=f[nex][0], d[x][0]+=d[nex][0];
        }
        
    }
    
    int a[N];
    
    void build(int x, int flag){
        if(flag){
            a[x] = p[x].size();
            for(auto i : p[x]) if(i!=fa[x]) build(i, 0);
        }else{
            a[x] = 1;
            for(auto i : p[x]){
                if(i == fa[x]) continue;
                if(f[i][0] == f[i][1]){
                    if(d[i][0] < d[i][1]){
                        build(i, 0);
                    }else{
                        build(i, 1);
                    }
                }else if(f[i][0] > f[i][1]){
                    build(i, 0);
                }else{
                    build(i, 1);
                }
            }
        }
    }
    
    int main(){ 
        n = read();
        
        for(int i=1; i<=n-1; i++){
            int u=read(), v=read();
            p[u].push_back(v);
            p[v].push_back(u);
        }
        if(n == 2){
            printf("%d %d\n%d %d", 2, 2, 1, 1);
            return 0;
        }
        dp(1);
        if(f[1][0] == f[1][1]){
            if(d[1][0] < d[1][1]){
                printf("%d %d\n", f[1][0], d[1][0]);
                build(1, 0);
            }else{
                printf("%d %d\n", f[1][1], d[1][1]);
                build(1, 1);
            }
        }else if(f[1][0] > f[1][1]) {
            printf("%d %d\n", f[1][0], d[1][0]);
            build(1, 0);
        }else {
            printf("%d %d\n", f[1][1], d[1][1]);
            build(1, 1);
        }
        for(int i=1; i<=n; i++) printf("%d ", a[i]);
        return 0;
    }
    
    

    https://www.luogu.com.cn/problem/P2216

    题目描述

    有一个 a×b 的整数组成的矩阵,现请你从中找出一个 n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

    分析:

    相当于是二维的滑动窗口

    具体怎么实现呢 进行两次的单调队列

    第一次求出X[i][j] 和 x[i][j] 数组 分别表示 点(i,j) 向右n个长度 的最大值 和最小值 转移的时候用原数组转移

    第二次求出Y[i][j] 和 y[i][j] 数组 分别表示 点(i,j) 为左上端点 长度为n 的矩形 的最大最小值 转移的时候用 X和x数组进行转移

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,m,k,front,FRONT,back,BACK,ans;
    int a[1001][1001],q[1001],Q[1001];
    int x[1001][1001],X[1001][1001];
    int y[1001][1001],Y[1001][1001];
    
    int main()
    {
    	scanf("%d%d%d",&n,&m,&k);
    	for (int I=1;I<=n;I++)
    		for (int i=1;i<=m;i++)
    			scanf("%d",&a[I][i]);
    	for (int I=1;I<=n;I++)
    		{
    			FRONT=BACK=front=back=Q[1]=q[1]=1;
    			for (int i=2;i<=m;i++)
    				{
    					while (a[I][i]>=a[I][Q[BACK]]&&FRONT<=BACK) BACK--;
    					while (a[I][i]<=a[I][q[back]]&&front<=back) back--;
    					BACK++;back++;Q[BACK]=i;q[back]=i;
    					while (i-Q[FRONT]>=k) FRONT++;
    					while (i-q[front]>=k) front++;
    					if (i>=k) X[I][i-k+1]=a[I][Q[FRONT]],x[I][i-k+1]=a[I][q[front]];
    				}
    		}
    	for (int I=1;I<=m-k+1;I++)
    		{
    			FRONT=BACK=front=back=Q[1]=q[1]=1;
    			for (int i=2;i<=n;i++)
    				{
    					while (X[i][I]>=X[Q[BACK]][I]&&FRONT<=BACK) BACK--;
    					while (x[i][I]<=x[q[back]][I]&&front<=back) back--;
    					BACK++;back++;Q[BACK]=i;q[back]=i;
    					while (i-Q[FRONT]>=k) FRONT++;
    					while (i-q[front]>=k) front++;
    					if (i>=k) Y[i-k+1][I]=X[Q[FRONT]][I],y[i-k+1][I]=x[q[front]][I];
    				}
    		}
        ans=0x3f3f3f3f;
    	for (int I=1;I<=n-k+1;I++)
    		for (int i=1;i<=m-k+1;i++)
    			ans=min(ans,Y[I][i]-y[I][i]);
    	printf("%d\n",ans);
    	return 0;
    }
    

    https://www.luogu.com.cn/problem/P2034

    题意:

    一个正整数序列 选择若干个数 使得和最大 不能选连续k个数

    分析:

    # include <algorithm>
    # include <iostream>
    # include <cstring>
    # include <cstdio>
    # include <queue>
    # include <cmath>
    # include <ctime>
    # define R register
    # define LL long long
    
    using namespace std;
    
    LL tot,d,n,k;
    LL p[100010],head = 1,tail = 1;
    LL q[100010],f[100010],ans;
    
    inline void in(R LL &a){
        R char c = getchar();R LL x=0,f=1;
        while(!isdigit(c)){if(c == '-') f=-1; c  =getchar();}
        while(isdigit(c)) x = (x<<1)+(x<<3)+c-'0',c = getchar();
        a = x*f;
    }
    
    inline void maxx(R LL &a,const LL b){a>b? 0:a=b;}
    
    inline LL yg(){
        // freopen("bronlily.in","r",stdin);
        // freopen("bronlily.out","w",stdout);
        in(n),in(k);
        for(R int i=1; i<=n; ++i)
        {
            in(d);
            tot += d;
            f[i] = q[head]+1LL*d;
            while(head<=tail&&q[tail]>=f[i]) tail--;
            q[++tail] = f[i],p[tail] = i;
            while(head<=tail&&p[head]<i-k) head++;
        }
        for(R int i=n-k; i<=n; ++i) maxx(ans,1LL*tot-1LL*f[i]);
        printf("%lld",ans);
        return 0;
    }
    
    LL youngsc = yg();
    int main(){;}
    

    分析:如果直接顺序递推肯定是不行的

    考虑记忆化搜索 每个点能向四周拓展的最长的路径是一定的 当我在此访问该点的时候直接返回答案即可

    # include <bits/stdc++.h>
    using namespace std;
    int n, m;
    int g[110][110];
    int dp[110][110];
    int X[5] = {1, -1, 0, 0};
    int Y[5] = {0, 0, 1, -1};
    int find(int x, int y)
    {
        if (dp[x][y])
            return dp[x][y];
        dp[x][y] = 1;
        for (int i=0; i<4; i++)
        {
            int x1 = X[i] + x;
            int y1 = Y[i] + y;
            if (x1<=0||y1<=0||x1>n||y1>m)
                continue;
            if (g[x][y] > g[x1][y1])
                dp[x][y] = max(dp[x][y], find(x1, y1)+1);
        }
        return dp[x][y];
    }
    int main()
    {
        cin>>n>>m;
        for (int i=1; i<=n; i++)
        {
            for (int j=1; j<=m; j++)
                cin>>g[i][j];
        }
        int ans = 0;
        for (int i=1; i<=n; i++)
        {
            for (int j=1; j<=m; j++)
            {
    //             cout<<i<<" "<<j<<" "<<find(i, j)<<endl;
                ans = max(ans, find(i, j));
            }
        }
        cout<<ans<<endl;
        
        
        
        return 0;
    }
    

    https://www.luogu.com.cn/problem/P2507

    如果不存在a[i]!=b[i]的情况 只要对a数组和b数组排序然后依次对应就好 很好证明

    现在考虑存在a[i]=b[i]的情况 最优解一定是相邻两个交换

    很容易忽略另一种情况 i位置的a和前面交换 b和后面交换 !!!

    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) x&(-x)
    #define ll long long
    #define Max 1e14
    const int maxn=1e5+5;
    int n; 
    ll dp[maxn];
    ll a[maxn],b[maxn];
    ll calc(int x,int y){
    	if(a[x]==b[y])return Max;
    	else return abs(a[x]-b[y]);
    }
    
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i],&b[i]),dp[i]=Max;
    	sort(a+1,a+1+n);sort(b+1,b+1+n); 
    	dp[0]=0;
    	for(int i=1;i<=n;i++){
    		if(i>=1)dp[i]=min(dp[i],dp[i-1]+calc(i,i));
    		if(i>=2)dp[i]=min(dp[i],dp[i-2]+calc(i-1,i)+calc(i,i-1));
    		if(i>=3){
    			dp[i]=min(dp[i],dp[i-3]+calc(i-1,i-2)+calc(i-2,i)+calc(i,i-1));
    			dp[i]=min(dp[i],dp[i-3]+calc(i-2,i-1)+calc(i,i-2)+calc(i-1,i));
    		}
    	}
    	if(dp[n]<Max)
    	cout<<dp[n];
    	else cout<<"-1";
         return 0;
    }
    
    
  • 相关阅读:
    update结合查询更新
    查表字段名,注释
    微信access_token
    Oracle中的dual伪表
    Oracle中的null
    UIView九宫格
    UIWebView使用
    sql触发器Tigger
    重写init方法
    OC内存管理示例
  • 原文地址:https://www.cnblogs.com/wzxbeliever/p/16525928.html
Copyright © 2020-2023  润新知