1.洛谷[HAOI2007]理想的正方形 (单调队列)
题目描述
有一个 a × b a \times b a×b 的整数组成的矩阵,现请你从中找出一个 n × n n \times n n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
输入格式
第一行为 3 3 3 个整数,分别表示 a , b , n a,b,n a,b,n 的值。
第二行至第 a + 1 a+1 a+1 行每行为 b b b 个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
输出格式
仅一个整数,为 a × b a \times b a×b 矩阵中所有“ n × n n \times n n×n 正方形区域中的最大整数和最小整数的差值”的最小值。
样例
样例输入
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
样例输出
1
提示
问题规模。
矩阵中的所有数都不超过 1 , 000 , 000 , 000 1,000,000,000 1,000,000,000。
20 % 20\% 20% 的数据 2 ≤ a , b ≤ 100 , n ≤ a , n ≤ b , n ≤ 10 2 \le a,b \le 100,n \le a,n \le b,n \le 10 2≤a,b≤100,n≤a,n≤b,n≤10。
100 % 100\% 100% 的数据 2 ≤ a , b ≤ 1000 , n ≤ a , n ≤ b , n ≤ 100 2 \le a,b \le 1000,n \le a,n \le b,n \le 100 2≤a,b≤1000,n≤a,n≤b,n≤100。
滑动窗口求最大最小值,对于每一行吧每个长度为n的区间的最值求出,此时可以看作每个长度为n的区间内的最值位于该区间最后一个位置,然后再在列上对这些最值求一次最大最小得到该n*n区间内正方形最值,最后维护ans=min(ans,b[i]-c[i])。
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1010;
int n,m,k;
int row_max[maxn][maxn],row_min[maxn][maxn];//行的最大最小值
int q[maxn],w[maxn][maxn];//单调队列,矩阵
void get_min(int a[],int b[],int len){
int hh=0,tt=-1;
for(int i=1;i<=len;++i){
if(hh<=tt&&i-k>=q[hh]) ++hh;
while(hh<=tt&&a[q[tt]]>=a[i]) --tt;
q[++tt]=i;
b[i]=a[q[hh]];
}
//求最小值维护递增单调递增的单调队列
//可用区间为从i开始往前的k个元素即[i-k+1,i]
}
void get_max(int a[],int b[],int len){
int hh=0,tt=-1;
for(int i=1;i<=len;++i){
if(hh<=tt&&i-k>=q[hh]) ++hh;
while(hh<=tt&&a[q[tt]]<=a[i]) --tt;
q[++tt]=i;
b[i]=a[q[hh]];
}
//求最大值维护单调递减的单调队列
//可用区间为从i开始往前的k个元素即[i-k+1,i]
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf("%d",&w[i][j]);
}
}
for(int i=1;i<=n;++i){
get_min(w[i],row_min[i],m);
get_max(w[i],row_max[i],m);
}
int a[maxn],b[maxn],c[maxn];
int ans=0x3f3f3f3f;
for(int i=k;i<=m;++i){
for(int j=1;j<=n;++j) a[j]=row_max[j][i];
get_max(a,b,n);
for(int j=1;j<=n;++j) a[j]=row_min[j][i];
get_min(a,c,n);
//列元素不连续先吧每列元素放到临时数组a中,b中维护最大值,c中维护最小值
for(int j=k;j<=n;++j){
ans=min(ans,b[j]-c[j]);
}//从[k,k]开始往后枚举所有的最值之差并求最小
}
printf("%d\n",ans);
return 0;
}
2.牛客网.22数(数位dp)
题目描述
现在定义一种22数, 这种数各个数位上的数字之和等于其位数的二倍
求 1 ~ n 中,22数的个数 x。
输入描述:
输入包含一行一个正整数 n ( 1 ≤ n ≤ 1 0 10 ) n(1 \leq n \leq 10^{10}) n(1≤n≤1010)。
输出描述:
输出包含一行一个数表示 1 ∼ n 1 \sim n 1∼n范围内22数的个数 x。
示例
输入 :22
输出: 3
说明
1~22范围内包含3个22数分别是2,13,22。
dp[i]j][k] 表示数位为i,最高位为j,数位和为k的所有方案数。先预处理出所有的dp数组,
1
0
10
10^{10}
1010最多有十个数位,所以k理论上最多为20,保险起见可以多开,同时爆了int,本题所有数组均需要开long long
dp的预处理考虑数位为i的时候最高位为j,总和为k,他会从dp[i-1][t][k-j]转移过来(k>j, 0<=t<=9)
void init(){
for(int i=0;i<=9;++i){
f[1][i][i]++;
}
for(int i=1;i<=10;++i){// n
for(int j=0;j<=9;++j){// nmax
for(int k=0;k<=20;++k){ //sum
for(int t=0;t<=9;++t) //i-1
if(k>=j){
f[i][j][k]+=f[i-1][t][k-j];
}
}
}
}
}
对于之后的方案数,枚举所有数位,先将其各位拆分出来,然后对于每个数位上数字x有两种可能:
第一:该位选取0-x-1范围内的数字,那么ans+=dp[i+1][j][n*2-last](从i到0有i+1位数字,除最高位外0<=j<x,last为之前所有位的和。第二:该位选取
第二:该位选取x,那么last+=x,当i==0时,如果n * 2 == last,++ans
该过程中如果出现了last>=n*2的情况可以直接break
这是所有不含前导0的情况,最后再额外处理除前导0的情况即可,从第1位到第n-1位,每一位枚举1-9, ans+=dp[i][j][i*2]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[15][15][100];//i->n j->n-max k ->sum
ll n;
void init(){
for(int i=0;i<=9;++i){
f[1][i][i]++;
}
for(int i=1;i<=10;++i){// n
for(int j=0;j<=9;++j){// nmax
for(int k=0;k<=20;++k){ //sum
for(int t=0;t<=9;++t) //i-1
if(k>=j){
f[i][j][k]+=f[i-1][t][k-j];
}
}
}
}
}
ll dp(ll x){
vector<ll> tmp;
while(x) tmp.push_back(x%10),x/=10;
ll n=tmp.size();
ll ans=0;
ll last=0;
for(int i=tmp.size()-1;i>=0;--i){
for(int j=i==tmp.size()-1;j<tmp[i];++j){
ans+=f[i+1][j][2*n-last];
}
last+=tmp[i];
if(last>2*n) break;
if(i==0&&last==n*2) ans++;
}
for(int i=1;i<n;++i){
for(int j=1;j<=9;++j){
ans+=f[i][j][i*2];
}
}
return ans;
}
int main()
{
init();
cin>>n;
cout<<dp(n);
return 0;
}