4/7 题目们 二分、前缀和+DP+深搜+链表

本文深入探讨四道经典算法题目:最大正方形、最大值、数字游戏与家庭作业优化,通过详细解析高效算法实现,如动态规划、贪心策略及链表优化,提供解决复杂问题的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 最大正方形

给一个N*N的01矩阵, 求一个面积最大的全为1的正方形子矩阵. 输出它的面积.

80%的数据中 N<=250;
100%的数据中 N <= 1000。

记录矩阵的前缀和,循环时只用 n 2 n^2 n2,再加 l o g n log n logn的二分找到前缀和最大的子矩阵,记录矩阵边长即为对应答案

#include <cstdio> 
#include <algorithm>

using namespace std; 
int n,ans;
int a[1005][1005]; 

int main(){   
 scanf("%d",&n);
     for (int i=1;i<=n;i++){ 
            char ch;
                     for (int j=1;j<=n;j++){
                                 ch=getchar(); 
                                 while (ch<'0'||ch>'9') ch=getchar(); 
                                 a[i][j]=ch-'0'; 
                       }   
       }
    for (int i=1;i<=n;i++)     
       for (int j=1;j<=n;j++)        
           a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];  
     for (int i=n;i>ans;i--){      
       for (int j=n;j>ans;j--){        
           int l=ans+1,r=min(i,j);            
           while (l<=r){             
              int mid=(l+r)/2;               
               if (a[i][j]-a[i-mid][j]-a[i][j-mid]+a[i-mid][j-mid]==mid*mid) l=mid+1,ans=mid;             
                     else r=mid-1;        
                         }     
            }   
      }    
     printf("%d",ans*ans);}

2 最大值

找到一个数组的最大值的一种方法是从数组开头从前到后对数组进行扫描,令max=a0,如果a[i]>max,就更新max,这样就可以在O(N)的时间里找到一个数组的最大值。

这个问题是相当简单的,但是想到了另一个问题,如果一个包含N个元素的数组a里面的元素的值是在1…K之间的整数,存在多少个不同的数组a,进行了如上扫描之后,max恰好进行了p次更新?

下面是N = 4,K = 3,P = 2时所有情况

  1. {1,1,2,3}

  2. {1,2,1,3}

  3. {1,2,2,3}

  4. {1,2,3,1}

  5. {1,2,3,2}

  6. {1,2,3,3}

共有6种情况

由于答案可能很大,所以你仅仅需要把答案mod 10^9+7输出。

1 <= T <= 10000 1 <= n <= 100 1 <= K <= 300 0 <= P < n

疲惫。。。
一个动态规划
是一个动态规划。
它是一个动态规划。。

f[i][j][k]表示第i位,到第i位为止出现的最大值为j,最大值更新了k次,的方案数
f[i][j][k]=f[i-1][q] (1<=q<j)[k-1]+f[i-1][j][k]*j
*j因为上一位为j时,当前位可以填1-j的任何数,所以方案数 *j
然后f[i-1][q] (1<=q<j)[k-1] 这一步可以用前缀和优化

#include <cstdio> 
#include <cstring>
#include <algorithm> 

using namespace std;

const int N=1e9+7;
int t,n,k,p;
long long f[105][305][105];
long long s[105][305][105];

int main(){
	for (int i=1;i<=300;i++){
		f[1][i][0]=1;	
		s[1][i][0]=i%N;	
	}	
	for (int i=2;i<=100;i++){	
		for (int j=1;j<=300;j++){			
			f[i][j][0]=(f[i][j][0]%N+(f[i-1][j][0]%N)*(j%N))%N;			
			s[i][j][0]=(s[i][j][0]%N+f[i][j][0]%N+s[i][j-1][0]%N)%N;		
			for (int k=1;k<100;k++){				
					f[i][j][k]=(f[i][j][k]%N+(f[i-1][j][k]%N)*(j%N)+s[i-1][j-1][k-1]%N)%N;				
					s[i][j][k]=(s[i][j][k]%N+f[i][j][k]%N+s[i][j-1][k]%N)%N;			
			}		
		}	
	}	
	scanf("%d",&t);	
	for (;t;t--){	
		scanf("%d%d%d",&n,&k,&p);			
		printf("%lld\n",s[n][k][p]%N);
	}
}

3 数字游戏

有这么一个游戏:

写出一个1~N的排列a[i],然后每次将相邻两个数相加,构成新的序列,再对新序列进行这样的操作,显然每次构成的序列都比上一次的序列长度少1,直到只剩下一个数字位置。下面是一个例子:

3   1   2   4

  4   3   6

    7   9

     16

最后得到16这样一个数字。

现在想要倒着玩这样一个游戏,如果知道N,知道最后得到的数字的大小sum,请你求出最初序列a[i],为1~N的一个排列。若答案有多种可能,则输出字典序最小的那一个。

预处理出杨辉三角的值,如

     1  2  1
        1  1
           1

作为对每一个的权值,然后搜索全排列,剪枝,就,就过了(?)

#include <cstdio>
#include <cstring> 

using namespace std; 

int n,sum;int c[25],a[25];
int f[25][25];int b[25]; 

bool dfs(int d,int s){	
	if (s>sum) return 0;	
	if (d>n){		
		if (s==sum) return 1;		
		return 0;	
	}	
	for (int i=1;i<=n;i++)
		if (!b[i]){	
			b[i]=1;		
			a[d]=i;		
			if (dfs(d+1,s+c[d]*i)) return 1;		
			b[i]=0;
		}	
		return 0;
	}
	
int main(){	
	scanf("%d%d",&n,&sum);	
	for (int i=1;i<=n;i++){	
		memset(f,0,sizeof f);		
		f[1][i]=1;		
		for (int j=2;j<=n;j++)		
			for (int k=1;k<=n-j+1;k++)			
				f[j][k]=f[j-1][k]+f[j-1][k+1];		
				c[i]=f[n][1];	
		}	
	dfs(1,0);	
	for (int i=1;i<=n;i++)		
	printf("%d ",a[i]);
}

4 家庭作业

老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为10,要求在6天内交,那么要想拿到这10学分,就必须在第6天结束前交。

每个作业的完成时间都是只有一天。例如,假设有7次作业的学分和完成时间如下:

作业号 1 2 3 4 5 6 7

期限 1 1 3 3 2 2 6

学分 6 7 2 1 4 5 1

最多可以获得15学分,其中一个完成作业的次序为2,6,3,1,7,5,4,注意可能d还有其他方法。

你的任务就是找到一个完成作业的顺序获得最大学分。

竟然可以用贪心…可以用贪心…用贪心…贪心…贪…
然而是用链表优化的贪心
以学分为关键字排序,对于未选作业中学分最大的,给它安排它的期限前的最晚空闲天,这样可以尽可能得多分
找最晚空闲天可以用链表记录每一天前最晚的空闲天并随时更新

#include <cstdio>
#include <algorithm> 

using namespace std; 

int n;long long ans;
int l[700005],tot;
int b[700005];
struct node{
	int t,w;
}a[1000006]; 

bool comp(node a,node b)
{
	if (a.w>b.w) return 1;   
	 return 0;
}

int dfs(int x){ 
   if (b[x]) return x;	
   l[x]=dfs(l[x]);	
   return l[x];
} 

int main(){
	scanf("%d",&n);	
	for (int i=1;i<=n;i++){	
		scanf("%d%d",&a[i].t,&a[i].w);		
		if (a[i].t>tot) tot=a[i].t;	
	}	
	sort(a+1,a+1+n,comp);	
	for (int i=0;i<=tot;i++){	
		l[i]=i-1;		
		b[i]=1;	
	}	
	for (int i=1;i<=n;i++){	
		int tt=dfs(a[i].t);		
		if (tt!=0) {		
			ans+=a[i].w;			
			b[tt]=0;			
			if (tt!=a[i].t)			
			l[a[i].t]=l[tt];		
		}	
	}	
	printf("%lld",ans);
}     

经年痴心妄想,一朝走火入魔

正经句子,相信我
形容有朝一日夙愿得成,梦想成真

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值