Solved: 4/6
Upsolved: 5/6
Rank: 205
腦洞題做多了導致套路題不會做了。。。
A. Greedy Monocarp
題意:給一個序列,每次可以給其中一個數加1,問最少操作多少次可以滿足當從大到小取數時取到的數之和會恰好等於 \(k\)。
從大到小排序,設恰好小於 \(k\) 的字首和為 \(s\),答案就是 \(k-s\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
void solve(){
int n,k;
cin>>n>>k;
vector<int> a(n);
ll sum=0,ans=-1;
for(int& x:a)cin>>x,sum+=x;
sort(all(a));
for(int i=0;i<n;++i){
if(sum<=k){ans=k-sum;break;}
sum-=a[i];
}
cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
B. Game with Colored Marbles
題意:有一個序列,兩人輪流取數。先手每取到一個值都會得 1 分,如果取到某個值的所有數又會額外得 1 分。先手希望得分最多,後手希望得分最少。問最優策略下的分數。
最優策略下,一定是先取完所有出現次數為 1 的數(先手會取得 \(\lceil \frac{c+1}2\rceil\) 個),再取剩下的(先手每個數都會取到但都不會取完)。答案就是出現的值的數量加(出現次數為 1 的數的數量 mod 2)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
void solve(){
int n,x;
cin>>n;
vector<int> c(n+1);
for(int i=0;i<n;++i)cin>>x,++c[x];
int cnt1=0,cnt2=0;
for(int i=1;i<=n;++i){
if(c[i]==1)++cnt1;
else if(c[i]>1)++cnt2;
}
cout<<cnt1+(cnt1&1)+cnt2<<'\n';
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
C. Competitive Fishing
題意:有一個 \(\pm 1\) 序列,給每個位置賦一個連續且單調不降的自然數權值(即 00111223333 這樣的),使得所有數字與權值的乘積之和至少為 \(k\),求最大權值的最小值。
每個權值增加的位置對答案的貢獻是從這個位置開始的字尾和。因此給所有字尾和排序,從大到小加直到超過 \(k\) 為止即可(注意第一個位置不能加)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
void solve(){
int n,k;
cin>>n>>k;
string a;
cin>>a;
vector<int> s(n);
for(int i=n-1;i;--i)s[i-1]=s[i]+(a[i]=='1'?1:-1);
s.pop_back();
sort(all(s));
int m=1,sum=0;
for(int i=n-2;~i;--i){
if(sum>=k)break;
sum+=s[i],++m;
}
if(sum<k)cout<<"-1\n";
else cout<<m<<'\n';
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
D. Recommendations
題意:有 \(n\) 個區間。對每個區間,求所有覆蓋它的區間的交的長度減去它本身的長度。
對所有區間按左端點從小到大排序,用set維護右端點,列舉到一個區間時其右端點的lower_bound就是覆蓋區間交的右端點。反過來做一遍即可得到區間交的左端點。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define all(x) (x).begin(),(x).end()
struct node{int l,r,id;};
void solve(){
int n;
cin>>n;
vector<node> a(n);
for(int i=0;i<n;++i)cin>>a[i].l>>a[i].r,a[i].id=i;
sort(all(a),[=](node a,node b){return a.l<b.l||a.l==b.l&&a.r>b.r;});
set<int> s;
vector<int> l(n),r(n);
for(int i=0;i<n;++i){
auto it=s.lower_bound(a[i].r);
if(it==s.end())r[a[i].id]=-1;
else r[a[i].id]=*it;
if(i<n-1&&a[i].l==a[i+1].l&&a[i].r==a[i+1].r)r[a[i].id]=-1;
s.insert(a[i].r);
}
s.clear();
sort(all(a),[=](node a,node b){return a.r>b.r||a.r==b.r&&a.l<b.l;});
for(int i=0;i<n;++i){
auto it=s.upper_bound(a[i].l);
if(it==s.begin())l[a[i].id]=-1;
else l[a[i].id]=*(--it);
if(i<n-1&&a[i].l==a[i+1].l&&a[i].r==a[i+1].r)l[a[i].id]=-1;
s.insert(a[i].l);
}
sort(all(a),[=](node a,node b){return a.id<b.id;});
for(int i=0;i<n;++i){
if(l[i]==-1||r[i]==-1)cout<<"0\n";
else cout<<(r[i]-l[i])-(a[i].r-a[i].l)<<'\n';
}
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
E. Vertex Pairs
題意:給一棵 \(2n\) 個節點的樹,每個節點的權值為 \(1\sim n\) 且每個值恰好出現 \(2\) 次。編號為 \(i\) 的節點的費用為 \(2^i\),求費用最小的包含所有權值的連通集合。
首先可以注意到:因為集合的大小至少為 \(n\),超過總點數的一半,所以這個集合一定至少包含一個重心。
以重心為根(如果有兩個重心要分別做一遍)。從大到小列舉節點看是否可以刪除。因為答案集合一定包含重心,所以要刪除一個點只能刪除以它為根的整棵子樹。
考慮如何快速判斷能否刪除。dfs序將子樹轉化為區間,然後用樹狀陣列維護每個位置的值是否能被刪除:
-
每個值的兩個點的lca一定不能被刪除;
-
若一個值的兩個點有一個點被刪了,那麼另一個點不能再被刪。
刪除過程中維護樹狀陣列,判斷時判區間和大於0即可。