寫在前面:(2020-2-12)
發現之前學習的一些算法忘了很多,準備最近在洛谷上找一些基礎的題目做一做,熟悉一下這些基礎算法,僅僅是Ac(暴力)的,
洛谷P1387最大的正方形
題目描述
在一個n*m的只包含0和1的矩陣里找出一個不包含0的最大正方形,輸出邊長。
輸入格式
輸入文件第一行為兩個整數n,m(1<=n,m<=100),接下來n行,每行m個數字,用空格隔開,0或1.
輸出格式
一個整數,最大正方形的邊長
分析:二維前綴和的裸題,對于每個點,我們依次枚舉邊長從1到min(n,m)的正方形,因為矩陣的元素不是0就是1,所以我們利用二維前綴和計算出正方形中1的個數是否等于該正方形的面積,ans維護答案即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010;
int n,m;
int a[N][N];
int s[N][N];
bool check(int x1,int y1,int x2,int y2)
{
int res = (x2-x1+1) * (y2-y1+1);
int t = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
if(t == res) return true;
return false;
}
int main()
{
cin>>n>>m;
for(int i = 1; i<=n; i++)
for(int j = 1; j<=m; j++)
scanf("%d",&a[i][j]);
for(int i = 1; i<=n; i++)
for(int j = 1; j<=m; j++)
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
int len = min(n,m); //正方形的邊長不可能超過n和m中小的那一個
int ans = 0;
for(int i = 1; i<=n; i++)
for(int j = 1; j<=m; j++)
for(int k = 1; k<=len; k++)
{
if(i+k-1 >n || j+k-1 >m) continue;
if(check(i,j,i+k-1,j+k-1))
{
ans = max(ans,k);
//cout<<ans<<endl;
}
}
cout<<ans<<endl;
return 0;
}
洛谷P2280激光炸彈
很容易看出,此題和上題一樣,是個二維前綴和的題目,但是由于處于正方形的邊上的目標不會被摧毀,我們可以做一個轉化,對二維前綴和來說,我們認為每個元素代表一個格子,所以,這題我們可以認為這些目標處于格子的中心,這樣格子的左上角坐標是(Xi-0.5,Yi-0.5),右下角坐標是(Xi+0.5,Yi+0.5),而右下角處于格子交線上,所以我們依次枚舉邊長為R的正方形,利用二維前綴和維護答案即可。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 5050;
int a[N][N];
int u,r;
int main()
{
cin>>u>>r;
r = min(r,5001);
for(int i = 0; i<u; i++)
{
int x,y,w;
cin>>x>>y>>w;
a[x+1][y+1] = w;
}
//預處理前綴和
int n = 5001,m = 5001;
for(int i = 1; i<=n; i++)
for(int j = 1; j<=m; j++)
a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
int ans = 0;
for(int i = r; i<=n; i++)
for(int j = r; j<=m; j++)
{
ans = max(ans,a[i][j] - a[i-r][j] - a[i][j-r] + a[i-r][j-r]);
//cout<<ans<<" "<<i<<" "<<j<<endl;
}
cout<<ans<<endl;
return 0;
}
洛谷P1114非常男女計劃
題目描述
近來,初一年的XXX小朋友致力于研究班上同學的配對問題(別想太多,僅是舞伴),通過各種推理和實驗,他掌握了大量的實戰經驗。例如,據他觀察,身高相近的人似乎比較合得來。
萬圣節來臨之際,XXX準備在學校策劃一次大型的“非常男女”配對活動。對于這次活動的參與者,XXX有自己獨特的選擇方式。他希望能選擇男女人數相等且身高都很接近的一些人。這種選擇方式實現起來很簡單。他讓學校的所有人按照身高排成一排,然后從中選出連續的若干個人,使得這些人中男女人數相等。為了使活動更熱鬧,XXX當然希望他能選出的人越多越好。請編寫程序告訴他,他最多可以選出多少人來。
輸入格式
第一行有一個正整數n,代表學校的人數。
第二行有n個用空格隔開的數,這些數只能是0或1,其中,0代表一個女生,1代表一個男生
輸出格式
輸出一個非負整數。這個數表示在輸入數據中最長的一段男女人數相等的子序列長度。
如果不存在男女人數相等的子序列,請輸出0。
先想一想暴力的方法,枚舉左右端點,前綴和計算區間的和(把女生的值設為-1),用ans來維護答案,這樣暴力的復雜度是,顯然是通過不了的,其實在第二層循環那里可以優化一下,第二層循環從后向前枚舉,如果當前枚舉區間的長度小于ans,直接break,效率將大大提升。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 101000;
int n;
int a[N];
int s[N];
int main()
{
scanf("%d",&n);
for(int i = 1; i<=n; i++)
{
scanf("%d",&a[i]);
if(!a[i]) a[i] = -1;
s[i] = s[i-1] + a[i];
}
int ans = 0;
for(int i = 1; i<=n; i++)
{
for(int j = n; j>=i+1; j--)
{
if(j-i+1 <ans) break; //如果當前枚舉的長度小于ans,不需要再往后遍歷了
if(s[j] - s[i-1] == 0)
ans = max(ans,j-i+1);
}
}
cout<<ans<<endl;
return 0;
}
(更新-2020-3-25)
Codeforces#Round624C題-Perform the combo
我們可以使用前綴和解決這道題目:先記錄每個字母出現的次數,然后求一遍前綴和,累加到答案中即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5+100;
int t,n,m;
int p[N], a[N][26],ans[26];
char s[N];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
for(int i = 1; i<=m; i++) scanf("%d",&p[i]);
//這里如果使用memset清空數組會超時
for(int i = 1; i<=n; i++)
for(int j = 0; j<26; j++)
a[i][j] = 0;
for(int i = 0; i<26; i++) ans[i] = 0;
//記錄每個字符出現的次數
for(int i = 1; i<=n; i++) a[i][s[i]-'a'] ++;
//求一遍前綴和,ans[i][j]即表示前i個字符中,字母j一共出現了多少次
for(int i = 1; i<=n; i++)
for(int j = 0; j<26; j++)
a[i][j] += a[i-1][j];
for(int i = 1; i<=m; i++)
for(int j = 0; j<26; j++)
ans[j] += a[p[i]][j];
for(int i = 0; i<26; i++) ans[i] += a[n][i];
for(int i = 0; i<26; i++) printf("%d ",ans[i]);
puts("");
}
return 0;
}