前言:
本場 \(div3\) 屬於是閱讀理解了。
本人於賽後 \(12\) 分 和 \(18\) 分過掉了 \(G\) 和 \(F\),警鐘敲爛。
A
題意
兩個陣列 \(a\) 和 \(b\) 長為 \(n,m\),判斷多少種組合方式滿足 \(a_i+b_j \le k\),其中 \(n,m\le 100\)。
Sol
\(n,m\) 很小直接 \(O(nm)\) 暴力匹配。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n"
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,s;
ll a[N],b[N];
void sol(){
cin>>n>>m>>s;
ll res=0;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
cin>>b[i];
for(int j=1;j<=n;j++){
if(a[j]+b[i]<=s)res++;
}
}
cout<<res<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}
B
題意
有一個序列,你可以執行如下操作任意次:
選擇一個 \(2\le i \le n-1\),然後執行:
是否可能將所有元素變成 \(0\)。
Sol
注意到使 \(a_1\) 變成 \(0\) 對 \(i=2\) 操作,不妨更改操作為:
選擇一個 \(1\le i \le n-2\),然後執行:
這樣每個元素只能改變後面而無法改變前面。於是從前往後掃直接模擬操作讓當前元素變成 \(0\),發現當前元素為負數,說明無解。如果最後 \(a_{n-1}\neq 0\) 或 \(a_{n} \neq 0\) 也說明無解,否則有解。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n"
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,a[N];
void sol(){
cin>>n;
ll res=0;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n-2;i++){
if(a[i]<0){
cout<<"NO\n";
return;
}
a[i+1]-=a[i]*2;
a[i+2]-=a[i];
a[i]=0;
}
if(a[n-1]!=0||a[n]!=0){
cout<<"NO\n";
return ;
}
cout<<"YES\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}
C
題意
一個字串,如果其連續子串含有 pie
或 map
,說明該字串不優美。你可以每次選擇刪除一個字母,最最小操作次數讓該字串變得優美。
Sol
注意到 pie
和 map
都含有 p
,我們只需要刪除一個字母就可以讓其沒有這兩個單詞,所以肯定刪除 p
是最划算的。
從左到右掃是否有這兩個單詞,如果有刪去一個 p
即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n"
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n;
string s;
void sol(){
cin>>n>>s;
s=" "+s;
ll res=0;
for(int i=1;i<=n;i++){
if(s[i]=='p'){
if(i>=3&&s[i-1]=='a'&&s[i-2]=='m')res++;
else if(i+2<=n&&s[i+1]=='i'&&s[i+2]=='e')res++;
}
}
cout<<res<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}
D
題意
一個有 \(n\) 個數字的輪盤,數字順時針依次排列,給定旋轉次數和起始點。
對於每次旋轉,給定旋轉長度,以及三種旋轉方向:
順時針,逆時針,和任意方向。
你需要給出最後所有可能的結束點,按從小到大輸出。
Sol
一個點 \(x\) 順時針走 \(k\) 步位於的點是 \(((x+k-1) \mod n)+1\) 。
一個點 \(x\) 逆時針走 \(k\) 步位於的點是 \(((x+n-k-1)\mod)+1\) 。
開一個佇列,取出上一次的終點,按照題意旋轉後再塞進去即可。
注意到可能有重複的,可以開個桶記錄 i
次操作點 x
是否已經旋轉過。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
queue<pair<ll,ll> >q;
ll n,m,st;
set<ll>res;
map<pair<ll,ll>,ll>mp;
void sol(){
while(!q.empty())q.pop();
res.clear();
cin>>n>>m>>st;
mp.clear();
q.push({st,0});
for(int i=1;i<=m;i++){
char c;
ll x;
cin>>x>>c;
while(!q.empty()){
auto t=q.front();
if(t.se==i)break;;
q.pop();
if(mp[t])continue;
mp[t]=1;\
if(c=='0'){
q.push({(t.fi+x-1)%n+1,i});
continue;
}
if(c=='1'){
q.push({(t.fi+n-x-1)%n+1,i});
continue;
}
q.push({(t.fi+x-1)%n+1,i});
q.push({(t.fi+n-x-1)%n+1,i});
}
}
while(!q.empty()){
res.insert(q.front().fi);
q.pop();
}
cout<<res.size()<<endl;
for(auto y:res)cout<<y<<" ";
cout<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}
E
題意
一個 \(n\times m\) 大小的河,\(a_{i,j}=h\),表示當前位置河的深度是\(h\),兩邊(\(a_{i,1}\) 和 \(a_{i,n}\))是平坦的陸地,也就是深度為 \(0\),你可以選擇一行修一座寬為 \(1\) 的橋,橋需要滿足以下條件:
- 選擇若干位置作為橋墩,特別的,兩岸必須作為橋墩。
- 兩個橋墩中間相隔的位置不超過 \(d\)
- 選擇 \((i,j)\) 位置修橋,需要花費 \(a_{i,j}+1\) 的代價。
現在你需要修一座連續的寬為 \(k\) 的大橋,也就是 \(k\) 座寬為 \(1\) 的橋拼起來,不要求這些橋的橋墩位於同一列,求最小花費。
資料範圍:\(n\times m \le 2\times 10^5\)。
Sol
注意到每一行的修橋最小花費方案獨立,可以分開求解。
是個比較裸的dp,設 \(f[j]\) 表示 在 \((i,j)\) 位置必須修橋的最小花費,不難得到:
每一行的答案就是 \(f[m]\)。
然後這個是 \(O(m^2)\),肯定過不去,容易發現可以單調佇列最佳化,不會的可以去做做滑動視窗。動態維護前 \(d\) 個位置的答案,並且保證隊首到隊尾單調遞增。
具體來說,隊頭與當前位置超過 \(d\) 的直接彈出,然後透過隊首轉移,因為隊首一定是符合條件的最優解,然後當前答案塞入佇列尾部,如果當前答案比尾部答案小,就把尾部彈出,因為尾部一定不會成為答案了。
求出每一行的修橋最小費用後就簡單了,記錄一下當前選擇連續 \(k\) 行的答案,最後取最小值即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll f[N],g[N],a[N];
ll n,m,k,d;
deque<ll>q;
void check(ll x){
while (!q.empty())q.pop_front();
g[1]=0;
q.push_front(1);
for(int i=1;i<=m;i++){
while(!q.empty()&&i-q.front()-1>d)q.pop_front();
g[i]=g[q.front()]+(a[i]+1);
while (!q.empty()&&g[i]<g[q.back()])q.pop_back();
q.push_back(i);
}
f[x]=g[m];
}
void sol(){
cin>>n>>m>>k>>d;
ll res=inf,sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[j];
}
check(i);
sum+=f[i];
if(i<k)continue;
res=min(res,sum);
sum-=f[i-k+1];
}
cout<<res<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}
F
本人由於閒的沒事搞了個去重,所以沒有完全清空陣列,導致賽時虛空對拍 \(1h22min\) 沒有發現異常,痛失 AK。
題意
三個序列 \(a,b,c\),長度分別為 \(n,m,k\),其中 \(a\) 嚴格單調遞增。
你可以選擇最多一組(也就是可以不選)\(b_i\) 和 \(c_j\) ,把 \(b_i+c_j\) 插入到 \(a\) 當中,並且仍然保證單調遞增的順序不變,求插入後如下式子的值:
也就是最小化相鄰兩項之間差的最大值。
Sol
最小化最大值,不難想到二分。
二分這個差值的最大值 \(mid\),然後先 \(O(n)\) 掃一遍 \(a\) 陣列,如果有兩個及以上的相鄰項差大於 \(mid\) 就是不合法,如果沒有差大於 \(mid\) 就是合法,可以先判斷掉,然後剩下只有一項的情況。
設唯一的 \(p\),\(a[p+1]-a[p]>mid\)。
這種情況要在 \(b,c\) 裡面選,為了方便可以先把 \(b,c\) 排序去重,不難得到一個和的範圍:\([a[p+1]-mid,a[p]+mid]\),如果和不在這個範圍內,一定會使得存在差大於 \(mid\),同時這個區間可能是無解的,也可以提前判斷。
知道了這點,我們可以 \(O(m)\) 列舉 \(b\) 的元素,然後二分 \(t_1=a[p+1]-mid-b[i]\) 和 \(t_2=a[p]+mid-b[i]\) 在 \(c\) 中的位置,用 lower_bound
實現。滿足以下任意條件即可說明有解:
具體原因畫個圖就出來了,也很好感性理解,不多介紹。
時間複雜度為 \(O(m \log k \log V )\),隨便透過。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 400005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,k;
ll a[N],b[N],c[N];
bool check(ll x){
ll p=0;
for(int i=1;i<n;i++){
if(a[i+1]-a[i]>x){
if(p)return 0;
p=i;
}
}
if(!p)return 1;
if(a[p+1]-a[p]>2*x)return 0;
ll le=a[p+1]-x;
ll ri=a[p]+x;
for(int i=1;i<=m;i++){
if(b[i]>ri)break;
ll t=b[i];
ll t1=lower_bound(c+1,c+k+1,le-t)-(c);
ll t2=lower_bound(c+1,c+k+1,ri-t)-(c);
if(abs(t2-t1)>0||le-t==c[t1]||ri-t==c[t2])return 1;
}
return 0;
}
void sol(){
cin>>n>>m>>k;
ll pm=m;
ll pk=k;
ll l=0,r=-inf;
for(int i=1;i<=n;i++){
cin>>a[i];
if(i>1)r=max(r,a[i]-a[i-1]);
}
for(int i=1;i<=m;i++)cin>>b[i];
for(int i=1;i<=k;i++)cin>>c[i];
sort(b+1,b+m+1);
m=unique(b+1,b+m+1)-(b+1);
sort(c+1,c+k+1);
k=unique(c+1,c+k+1)-(c+1);
while(l<r){
ll mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l<<endl;
for(int i=1;i<=n;i++)a[i]=0;
for(int i=1;i<=pm;i++)b[i]=0;
for(int i=1;i<=pk;i++)c[i]=0;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ll T;
cin>>T;
while(T--)sol();
return 0;
}