【Problem A】 Square
【题意】
给一个n * n的01矩阵,要求一个最大的全1正方形子矩阵,输出它的面积
N <= 1000
【题解】
朴素的做法是先求二维前缀和,然后暴力找最大的正方形子矩阵,时间复杂度 : O(n^3) 期望得分 : 80
考虑优化,我们发现如果有边长为n的正方形,就一定有边长为(n - 1)的正方形,因此,可以先二分边长d,然后
判断边长为d的正方形是否存在. 时间复杂度 : O(n^2log(n)) 期望得分 : 100
【代码】
#include<bits/stdc++.h>
using namespace std;
#define MAXN 2050
int i,j,l,r,mid,n,ans;
int s[MAXN][MAXN];
char ch;
bool check(int d)
{
int i,j,x,y;
for (i = 1; i <= n - d + 1; i++)
{
for (j = 1; j <= n - d + 1; j++)
{
x = i + d - 1;
y = j + d - 1;
if (s[x][y] - s[i-1][y] - s[x][j-1] + s[i-1][j-1] == d * d)
return true;
}
}
return false;
}
int main() {
freopen("square.in","r",stdin);
freopen("square.out","w",stdout);
scanf("%d",&n);
getchar();
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
ch = getchar();
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + ch - '0';
}
getchar();
}
l = 1; r = n;
while (l <= r)
{
mid = (l + r) >> 1;
if (check(mid))
{
l = mid + 1;
ans = mid;
} else r = mid - 1;
}
printf("%d\n",ans*ans);
return 0;
}
【Problem B】 findmax
【题意】
一个包含n个元素的数组,元素的值在1-k之间,存在多少个不同的数组,使得扫描这个数组,最大值更新了p次
【题解】
暴力搜索,期望得分 : 30
由于T最大10^5,所以,我们不可能每次询问都计算一次答案,因此,我们应该进行一些预处理
考虑动态规划预处理
f[i][j][k]表示选了i个元素,元素的最大值为j,进行了k次更新的合法方案
那么,有f[i][j][k] = f[i-1][j][k] * j + f[i-1][t][k-1] (1 <= t <= j - 1)
这个状态转移方程的意思是 :
如果选到第i个元素时最大值未发生变化,那么,就相当于前i-1个元素最大值为 j,进行了k次更新,而第i个元素就可 以选1..j中的任何一个数,方案数为f[i-1][j][k] * j
如果选到第i个元素时最大值发生了变化,那么,前i-1个元素,最大值可以是1..j-1中的任何一个数,进行了k-1次更 新, 第i个元素为j,方案数为f[i-1][t][k-1] (1 <= t <= j - 1)
然而,这样算法的时间复杂度是O(nk^2p),不能通过此题
考虑优化,我们用sum[i][j][k]表示选了i-1个元素,进行j次更新,元素的最大值为1..k的方案数(前缀和),
式子就被简化为了 : f[i][j][k] = f[i-1][j][k] * j + sum[i-1][k-1][j-1]
那么,这样时间复杂度就被优化为了O(nkp)
对于每次询问,答案就是sigma(f[n][i][p])(1 <= i <= k)
总时间复杂度 : O(nkp + tk) 期望得分 : 100
【代码】
#include<bits/stdc++.h>
using namespace std;
#define MAXN 101
#define MAXK 301
#define MAXP 101
const long long MOD = 1e9 + 7;
long long i,j,k,ans,n,p,T;
long long f[MAXN+10][MAXK+10][MAXP+10],sum[MAXN+10][MAXP+10][MAXK+10];
int main() {
freopen("findmax.in","r",stdin);
freopen("findmax.out","w",stdout);
cin >> T;
for (i = 1; i <= MAXK; i++)
{
f[1][i][0] = 1;
sum[1][0][i] = i;
}
for (i = 2; i <= MAXN; i++)
{
for (j = 1; j <= MAXK; j++)
{
for (k = 0; k <= MAXP; k++)
{
if (!k) f[i][j][k] = (f[i-1][j][k] * j) % MOD;
else f[i][j][k] = ((f[i-1][j][k] * j) % MOD + sum[i-1][k-1][j-1]) % MOD;
sum[i][k][j] = (sum[i][k][j] + f[i][j][k]) % MOD;
}
}
for (j = 0; j <= MAXP; j++)
{
for (k = 1; k <= MAXK; k++)
{
sum[i][j][k] = (sum[i][j][k] + sum[i][j][k-1]) % MOD;
}
}
}
while (T--)
{
ans = 0;
cin>> n >> k >> p;
for (i = 1; i <= k; i++) ans = (ans + f[n][i][p]) % MOD;
cout<< ans << endl;
}
return 0;
}
【Problem C】bds
【题意】
写出1..n的排列a,每次相邻两个数相加,构成新的序列,再对新序列再次进行这样的操作,知道剩下一个数N
给定N,要求字典序排列最小的a
【题解】
调用algorithm库里的next_permutation函数,判断是否可以得到N 期望得分 : 20
我们发现,N = C(n,1)a1 + C(n,2)a2 + C(n,3)a3 + C(n,4)a4 + ... + C(n,n)an
因此,可以通过杨辉三角计算C(n,i),然后深度优先搜索DFS
时间复杂度 O (能过) 期望得分 : 100
【代码】
#include<bits/stdc++.h>
using namespace std;
#define MAXN 25
int i,j,n,sum,l;
int c[MAXN][MAXN],visited[MAXN],ans[MAXN];
bool solved = false;
inline void dfs(int dep,int s)
{
int i;
if (dep > n)
{
if (s == sum)
solved = true;
return;
}
for (i = 1; i <= n; i++)
{
if (s + i * c[n][dep] > sum) return;
if (!visited[i])
{
visited[i] = true;
ans[++l] = i;
dfs(dep+1,s+i*c[n][dep]);
if (solved) return;
l--;
visited[i] = false;
}
}
}
int main(){
freopen("bds.in","r",stdin);
freopen("bds.out","w",stdout);
scanf("%d%d",&n,&sum);
c[0][0] = 1;
for (i = 1; i <= n; i++)
{
for (j = 1; j <= i; j++)
{
c[i][j] = c[i-1][j] + c[i-1][j-1];
}
}
dfs(1,0);
for (i = 1; i < n; i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
return 0;
}
【总结】
总分20 + 100 + 100 = 220,第一题失分原因是因为将正方形看成了长方形,这样的错误很不应该,以后
一定要避免!
这次考试,基础算法占了很大比重,虽然最近一直在研究比较复杂的算法,但是,也不能忽视了基础算法的
重要性!