zoukankan      html  css  js  c++  java
  • 线规集合

    明天就考(NOIp)了啊啊啊啊!!!我好慌呀

    线性规划

    动态规划一直是菜逼向巨神过渡的重要门槛,跃过了这道坎,即代表着您有了很强的逻辑思维能力,在中高端题面前能够获得充分的部分分

    线规是动态规划中最简单的一种

    常被用来求最值问题与计数问题

    经典题

    (LIS)

    (f[i])来记录长度为i的最长上升子序列的最后一位元素大小,
    每加入一个一个新的值就在f序列中进行二分找到第一个大于等于该值的第一个元素

    (LCS)

    (LCS)则是把每个(B)序列元素对应为(A)序列元素,将(AB)之间的元素形成一一映射,这样就把一个(LCS)问题转化为了(LIS)问题

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    using namespace std;
    
    const int p=1e5+5;
    
    int a[p],b[p],map[p];
    int f[p];
    
    template<typename _T>
    void read(_T &x)
    {
    	x =0;char s=getchar();int f=1;
    	while(s<'0'||s>'9'){f=1;if(f=='-')f=-1;s=getchar();}
    	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    	x*=f;
    }
    
    signed main()
    {
    	int n;
    	
    	ios_base::sync_with_stdio(false);
    	cout.tie(NULL);
    	cin.tie(NULL);
    	
    	read(n);
    	
    	for(int i=1;i<=n;i++)
    	{
    		read(a[i]);
    		map[a[i]] = i;
    	}
    	for(int i=1;i<=n;i++)
    	read(b[i]);
    	
    	memset(f,0x3f,sizeof(f));
    	
    	f[0] = 0;
    	int len = 0;
    	for(int i=1;i<=n;i++)
    	{
    		if(map[b[i]] > f[len]){f[++len] = map[b[i]];}
    		else
    		{
    			int k = lower_bound(f+1,f+1+len,map[b[i]]) - f;
    			f[k] = map[b[i]];
    		}
    	}
    	
    	cout<<len;
    }
    

    例 : 子串

    考虑两个字符串划分为k段后完全匹配
    仿照(LCS)的暴力式子列出了状态
    (f[i][j][k][0/1])
    代表A序列的前i个字符与B序列的前j个字符划分为k个字串且A的第i
    个字符在或不在第k串中

    [f[i][j][k][1] = f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1]+f[i-1][j-1][k-1][0] ]

    [f[i][j][k][0] = f[i-1][j][k][1] + f[i-1][j][k][0] ]

    显然第一维我们可以滚动数组滚掉

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    using namespace std;
    
    const int base=29;
    
    #define mod 1000000007 
    #define int long long 
    
    template<typename _T>
    inline void read(_T &x)
    {
    	x=0;char s=getchar();int f=1;
    	while(s<'0'||'9'<s){f=1;if(s=='-')f=-1;s=getchar();}
    	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    	x*=f;
    }
    
    int f[2][205][205][3];
    
    
    signed main()
    {
    	char a[1005];
    	char b[1005];
    	
    	int n,m,s;
    	
    	read(n);
    	read(m);
    	read(s);
    
    	scanf("%s%s",a+1,b+1);
    	
    
    	if(a[1] == b[1])
    	f[1][1][1][1] =1;
    	
    	for(int i=2;i<=n;i++)
    	{
    		memset(f[i&1],0,sizeof(f[i&1]));
    		
    		if(a[i] == b[1])
    		f[i&1][1][1][1] = 1;
    		
    		for(int j=1;j<i;j++)
    		if(a[j] == b[1]) f[i&1][1][1][0]++;		
    		
    		for(int j=1;j<=m;j++)
    			for(int k=1;k<=s;k++)
    			{
    				if(f[i&1][j][k][0] == 0)
    				f[i&1][j][k][0] = f[i-1&1][j][k][0] + f[i-1&1][j][k][1],f[i&1][j][k][0]%=mod;
    				if(a[i] == b[j] && f[i&1][j][k][1] == 0)
    				f[i&1][j][k][1] = f[i-1&1][j-1][k-1][0] + f[i-1&1][j-1][k-1][1] + f[i-1&1][j-1][k][1],f[i&1][j][k][1]%=mod;
    				
    			}
    	}
    	
    	int tot = f[n&1][m][s][1] + f[n&1][m][s][0];
    	tot%=mod;
    	
    	cout<<((tot%mod)+mod)%mod;
    }
    

    题目集锦

    (P4933)

    本题也是一个线规计数类(dp)
    因为和等差数列有关所以我们考虑把公差作为阶段来表示状态
    (f[i][j][1/0])表示以(i)为结尾以(j)为公差,(1/0)表示是升序还是降序
    由此得出状态转移方程

    [f[i][j][1] = sum_{1≤k<i}f[k][j][1](h_i = h_k+j) ]

    [f[i][j][0] = sum_{1≤k<i}f[k][j][0](h_i=h_k-j) ]

    最后将两者相加再加上原本的电线杆的个数减去(sum_{1≤i≤n}f[i][0][0])就是总方案数

    可以发现转移的时候需要枚举(k)来进行求和,这非常浪费时间,并且我们可以发现当(h-k=hi-d)才可以从(f(k))转移到(f(i))来,所以我们考虑维护一个数组(g[h])来表示高度为(h)的状态之和
    这样就可以表示为
    (f[i]= g[h_i - d])
    (g[h_i] +=f[i])

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    
    using namespace std;
    
    #define int long long 
    
    #define INF 1<<30
    #define mod 998244353
    
    const int p=2e4+5;
    
    template<typename _T>
    inline void read(_T &x)
    {
    	x=0;char s=getchar();int f=1;
    	while(s<'0'||'9'<s){f=1;if(s=='-')f=-1;s=getchar();}
    	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    	x*=f;
    }
    
    int h[p+5];
    int g[p*2];
    int f[p+5];
    
    signed main()
    {
    	int n,v=0;
    	read(n);
    	for(int i=1;i<=n;i++)
    	{
    		read(h[i]);
    		v=max(v,h[i]);
    	}
    	
    	int tot = 0;
    	
    	for(int d=-v;d<=v;d++)
    	{
    		memset(g,0,sizeof(g));
    		fill(f+1,f+1+n,1);
    		for(int i=1;i<=n;i++)
    		{
    			if(h[i]>=d)
    			f[i]+=g[h[i]-d];//f[k][d][1];
    			f[i]%=mod;
    			g[h[i]] += f[i];
    			g[h[i]]%=mod;
    		}
    		for(int i=1;i<=n;i++)
    		{
    			tot+=f[i];//f[k][d][1];
    			tot%=mod; 
    		}
    	}
    	
    	cout<<((tot-n*2*v)%mod+mod)%mod;
    }
    

    (P4767)

    (qb)学堂的老师讲过这个题目,要求当作一种套路dp来记住
    考虑(f[i][j])为前i所村庄修建j所邮局
    其转移式为

    [f[i][j] = f[k][j-1] + calc(k+1,i) ]

    其中(calc(k+1,i))(s[k+1])(s[i])(s[frac{k+1+i}{2}])的距离

    复杂度为(O(PV^2)),显然题解中有更高端的做法,用四边形不等式优化,那我必然不会

    如果我有一天也向他们一样向着更高的理想追求去了,可能就会把这里的坑给填了吧

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    
    int a[23333];
    int f[233][2333];
    int sum[233333];
    
    inline int calc(int l,int r)
    {
    	int op=l+r>>1;
    	return a[op]*(op-l+1)-sum[op]+sum[l-1]+abs(a[op]*(r-op+1)-sum[r]+sum[op-1]);
    	
    }
    
    int main()
    {
    	int n,m;
    	
    	memset(f,0x3f,sizeof(f));
    	
    	ios_base::sync_with_stdio(false);
    	cout.tie(NULL);
    	cin.tie(NULL);
    	
    	cin>>n>>m;
    	
    	int maxn=0;
    	
    	for(int i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		maxn=max(maxn,a[i]);
    	}
    	
    	for(int i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+a[i];
    	}
    
    	for(int i=1;i<=n;i++)
    	{
    		f[1][i]=calc(1,i);
    	}
    	
    	for(int i=2;i<=m;i++)
    		for(int j=1;j<=n;j++)
    			for(int k=1;k<j;k++)
    	{
    		f[i][j]=min(f[i][j],f[i-1][k] + calc(k+1,j));
    	}
    	
    	cout<<f[m][n];
    	
    }
    

    (P5858)

    既然线规都说这么多了,再加一道曾经发过的又何妨

    (f[i][j])代表此时已放入前i种原料,并且锅里有j种原料时最大值

    [f[i][j] = max_{k=j-1​}^{k≤min(w,j+s−1)}f[i-1][k] + a[i]*j ]

    复杂度(O(nm^2))
    可以发现随着j的上升k的上下边界也都在随之变化,那么就引导着我们进行单调队列优化

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    using namespace std;
    
    #define minn 5505
    
    long long   a[minn];
    long long   f[minn][minn]; 
    long long   con[minn*2];
    long long   que[minn*2];
    
    int  main()
    {
    	long long   n,w,s;
    	long long   maxn;
    	
    	ios_base::sync_with_stdio(false);
    	cout.tie(NULL);
    	cin.tie(NULL);
    	
    	cin>>n>>w>>s;
    	
    	for(long long   i=1;i<=n;i++)
    	cin>>a[i];
    	
    	memset(f,0xcf,sizeof(f));
    	
    	maxn=f[0][0];
    	
    	f[0][0]=0;
    	
    	for(long long   i=1;i<=n;i++)
    	{
    		int lef=1,rig=0;
    		
    		que[++rig] = f[i-1][w];
    		con[rig] = w;
     		
    		for(int k=w ;k ;k--)
    		{
    		
    		while(lef <= rig && con[lef] > k + s - 1 ) lef++;
    		
    		while(lef <= rig && que[rig] < f[i-1][k-1]) rig--;
    		
    		con[++rig] = k-1;  	  
    		que[rig] = f[i-1][k-1];
    		
    		f[i][k]= que[lef] + a[i] * k;
    		
    		}
    	}
    		
    	for(long long   j=1;j<=w;j++)
    	maxn=max(f[n][j],maxn);
    	
    	cout<<maxn;
    }
    

    (The End)

    至此,考前习题集合到此就完结归档了,从数学专题到背包规划集合再至
    线规集合,是我(NOIp)前集中停课一周所作

    (NOIp rp++)

  • 相关阅读:
    Arduino mega 2560驱动安装失败(没有建立对验证码(TM)签名的目录的发布者信任)的解决方法
    Submile text3 安装使用技巧
    window.onload
    JS简单示例
    python类和对象的底层实现
    python类中方法加单下划线、双下划线、前后双下滑线的区别
    linux下json工具jq
    Django使用自定义的authentication登录认证
    django admin
    linux网卡桥接问题与docker网卡桥接问题
  • 原文地址:https://www.cnblogs.com/-Iris-/p/14087698.html
Copyright © 2011-2022 走看看