對dp還不是特別熟練
只做到了C(還是太菜了),開始前剛好各種事情來了,vp晚了10多分鐘開才始做題,喜提排名(不是)3000+,後面有時間就儘量把dp補掉
A. Rectangle Arrangement
你需要在一個無限的方格網格上塗色,所有格子最初都是白色的。為了完成這項任務,你有 \(n\) 個印章。每個印章是一個寬度為 \(w_i\)、高度為 \(h_i\) 的矩形。
你將使用 每個 印章 恰好一次 在網格上塗一個與印章同樣大小的矩形區域為黑色。你不能旋轉印章,對於每個格子,印章必須完全覆蓋它或者完全不覆蓋它。你可以在網格上的任何位置使用印章,即使印章覆蓋的一些或全部格子已經是黑色的。
在使用完所有印章之後,你能得到的黑色方格區域的 周長之和 的最小值是多少?
\(Input\)
每個測試包含多個測試案例。第一行包含測試案例的數量 t(1≤t≤500)。每個測試案例的描述緊隨其後。
每個測試案例的第一行包含一個整數 n(1≤n≤100)。
接下來的 n 行中,第 i 行包含兩個整數 wi 和 hi(1≤wi,hi≤100),分別代表第 i 個印章的寬度和高度。
\(Output\)
對於每個測試案例,輸出一個整數——在使用完所有印章後,你能得到的黑色方格區域周長之和的最小值。
\(Sample\)
5
5
1 5
2 4
3 3
4 2
5 1
3
2 2
1 1
1 2
1
3 2
3
100 100
100 100
100 100
4
1 4
2 3
1 5
3 2
20
8
10
400
16
思路:這題我是老老實實進行了區域覆蓋,然後dfs了一遍區域,看周長
沒想到是小學數學,\(2*max(h)*max(w)\),也確實是啊,vp時真是腦子抽了
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
//#define endl "\n"// 互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
struct s
{
ll x,y;
}p[2500];
ll a[400][400];
ll d[4]={0,1,-1,0};
ll e[4]={1,0,0,-1};
int main()
{
fio();
ll t;
cin>>t;
while(t--)
{
for(ll i=1;i<=100;i++)
{
for(ll j=1;j<=100;j++)
a[i][j]=0;
}
ll n;
cin>>n;
for(ll i=1;i<=n;i++)
{
ll w,h;
cin>>w>>h;
for(ll k=1;k<=w;k++)
{
for(ll u=1;u<=h;u++)
{
a[k][u]=1;
}
}
}
ll ans=0;
for(ll i=0;i<=101;i++)
{
for(ll j=0;j<=101;j++)
{
for(ll u=0;u<=3;u++)
{
ll nx=i+d[u];
ll ny=j+e[u];
if(nx>=1&&nx<=100&&ny>=1&&ny<=100&&a[i][j]==0)
{
if(a[nx][ny])ans++;
}
}
}
}
cout<<ans<<endl;
}
}
B. Stalin Sort
斯大林排序(Stalin Sort)是一種幽默的排序演算法,它不是為了正確地對元素進行排序,而是為了消除那些不在適當位置的元素,從而實現 \(\mathcal{O}(n)\) 的時間複雜度。
斯大林排序的步驟如下:從陣列的第二個元素開始,如果它嚴格小於前一個元素(忽略那些已經被刪除的元素),那麼就刪除它。繼續遍歷陣列,直到陣列按照非遞減順序排序。例如,陣列 \([1, 4, 2, 3, 6, 5, 5, 7, 7]\) 在經過斯大林排序後變為 \([1, 4, 6, 7, 7]\)。
我們定義一個陣列為“易受攻擊”的,如果你可以透過反覆對它的任意子陣列應用斯大林排序,無論需要多少次,來將其排序成非遞增順序。
給定一個包含 \(n\) 個整數的陣列 \(a\),確定必須從陣列中移除的最小整數數量,以使其變得“易受攻擊”。
\(^{\text{∗}}\) 如果陣列 \(a\) 可以透過從陣列 \(b\) 刪除若干(可能為零或全部)開頭和結尾的元素來獲得,那麼陣列 \(a\) 就是陣列 \(b\) 的一個子陣列。
\(Input\)
每個測試包含多個測試案例。第一行包含一個整數 \(t\)(\(1 \le t \le 500\))——測試案例的數量。之後是測試案例的描述。
每個測試案例的第一行包含一個整數 \(n\)(\(1 \le n \le 2000\))——陣列的大小。
每個測試案例的第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\)(\(1 \le a_i \le 10^9\))。
保證所有測試案例中 \(n\) 的總和不超過 \(2000\)。
\(Output\)
對於每個測試案例,輸出一個整數——必須從陣列中移除的最小整數數量,以使其變得“易受攻擊”。
\(Sample\)
6
7
3 6 4 9 2 5 2
5
5 4 4 2 2
8
2 2 4 4 6 6 10 10
1
1000
9
6 8 9 10 12 9 7 5 4
7
300000000 600000000 400000000 900000000 200000000 400000000 200000000
2
0
6
0
4
2
樣例解析:
在第一個測試案例中,最優解是移除數字 \(3\) 和 \(9\)。這樣我們剩下的陣列為 \(a = [6, 4, 2, 5, 2]\)。為了證明這個陣列是“易受攻擊”的,我們可以先對子陣列 \([4, 2, 5]\) 應用斯大林排序,得到 \(a = [6, 4, 5, 2]\),然後對子陣列 \([6, 4, 5]\) 應用斯大林排序,得到 \(a = [6, 2]\),這是一個非遞增序列。
在第二個測試案例中,陣列已經是非遞增的,所以我們不需要移除任何整數。
思路:說了這麼多,其實題意等價於問第一個是不是最大的數,列舉每一個數作為第一個,前面的要全刪除,然後後面比這個數大的也要刪除,這樣子就可以保證排序到最後可以獲得不遞增的排列
不妨象想想,第一個數非最大數,且後面有更大數時是無解的
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
//#define endl "\n"// 互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[4500];
int main()
{
fio();
ll t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
}
ll ans=9999999999;
for(ll i=1;i<=n;i++)
{
ll cnt=i-1;
for(ll j=i+1;j<=n;j++)
{
if(a[j]==a[i])continue;
else if(a[j]>a[i])cnt++;
}
ans=min(ans,cnt);
}
cout<<ans<<endl;
}
}
C. Add Zeros
您將獲得一個最初包含 \(n\) 個整數的陣列 \(a\)。在一個操作中,您必須執行以下操作:
選擇一個位置 \(i\),使得 \(1 < i \leq |a|\) 且 \(a_i = |a| + 1 - i\),其中 \(|a|\) 是陣列的當前大小。
在 \(a\) 的末尾追加 \(i - 1\) 個零。
在您想要多次執行此操作後,陣列 \(a\) 的最大可能長度是多少?
\(Input\)
每個測試包含多個測試用例。第一行包含測試用例的數量 \(t\)(\(1 \le t \le 1000\))。測試用例的描述緊隨其後。
每個測試用例的第一行包含 \(n\)(\(1 \le n \le 3 \cdot 10^5\))——陣列 \(a\) 的長度。
每個測試用例的第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\)(\(1 \le a_i \le 10^{12}\))。
保證所有測試用例的 \(n\) 之和不超過 \(3 \cdot 10^5\)。
\(Output\)
對於每個測試用例,輸出一個整數 —— 在執行一系列操作後,陣列 \(a\) 的最大可能長度。
\(Sample\)
4
5
2 4 6 2 5
5
5 4 4 5 1
4
6 8 2 3
1
1
10
11
10
1
樣例解析:
在第一個測試用例中,我們可以先選擇 \(i = 4\),因為 \(a_4 = 5 + 1 - 4 = 2\)。在此之後,陣列變為 \([2, 4, 6, 2, 5, 0, 0, 0]\)。然後我們可以選擇 \(i = 3\),因為 \(a_3 = 8 + 1 - 3 = 6\)。在此之後,陣列變為 \([2, 4, 6, 2, 5, 0, 0, 0, 0, 0]\),其長度為 10。可以證明,沒有任何操作序列會使最終陣列更長。
在第二個測試用例中,我們可以選擇 \(i=2\),然後 \(i=3\),然後 \(ii=4\)。最終陣列將是 \([5, 4, 4, 5, 1, 0, 0, 0, 0, 0, 0]\),長度為 11。
思路:其實由這個式子\(a_i = |a| + 1 - i\)可得到\(a_i - i = |a| + 1\),當一個數可以進行操作時,其必然符合這個式子,不妨先求出\(a_i - i -|a| - 1\)作為特徵值然後反過來用特徵值去記錄符合的下標,然後每次增加i-1時,可以去看看是否有符合的特徵值存在,如果存在,則進行搜尋,不在就返回。因為答案顯然是沒有一個固定策略可選,所以選擇記憶化遞迴(dp的一種形式)然後從以0作為特徵值去搜即可算出答案,對於每個階段的增值可能最好用map記住,然後從其他地方搜過來時,時間複雜度就大大減小了
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
//#define endl "\n"// 互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll n;
map<ll,set<ll>>q;
map<ll,ll>op;
ll dfs(ll cnt,ll z)//記憶化?
{
if(op[cnt])
{
return op[cnt]+z;
}
ll ans=z;
for(auto j:q[cnt])
{
ans=max(ans,dfs(cnt+j-1,z+j-1));
}
op[cnt]=ans-z;
return ans;
}
ll a[450000];
int main()
{
fio();
ll t;
cin>>t;
while(t--)
{
op.clear();
cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]>=(n-i+1)&&i!=1)
{
q[a[i]-(n-i+1)].insert(i);
}
}
cout<<dfs(0,n)<<endl;
q.clear();
}
}
D1.The Endspeaker (Easy Version)
思路:先給個個人想法,用一維滾動數陣列進行dp,dp[i]指dp到這位的最小費用,然後再每次數進行dp時進行雙方更新,這種想法喜提WA3了,後面有時間再重新想想