https://codeforces.com/contest/1348
A. Phoenix and Balance
想了想,還是不略了
顯然有 \(2^1+2^2+...+2^{n-1}<2^n\),前 \(n-1個\) 數隨你選 \(\dfrac n 2\) 個一定小於 \(2^n\)。為了兩邊硬幣質量更靠近,在前 \(n-1\) 中要選最大的 \(\dfrac n 2\) 個硬幣
求和公式搞一搞
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll;
#define int ll
signed main(){
int T; cin>>T;
while(T--){
int n; cin>>n;
#define p(n) (1ll<<(n))
cout<<(p(n/2+1)-2)<<endl;
}
return 0;
}
B. Phoenix and Beauty
顯然要我們弄出一個周期為 \(k\) 個序列
如果數字種類數大於 \(k\) 那一定無解,除此之外,輸出 \(nk\) 個數,每 \(k\) 個數一周期,同時該序列要包含原序列的所有數就行了
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
#define int ll
set<int> s;
void solve(){
int n,k,x; cin>>n>>k; s.clear();
repeat(i,0,n)cin>>x,s.insert(x);
if((int)s.size()>k){cout<<-1<<endl; return;}
for(int i=1;(int)s.size()<k;i++)s.insert(i);
cout<<n*k<<endl;
repeat(i,0,n)
for(auto j:s)
cout<<j<<' ';
cout<<endl;
}
signed main(){
int T; cin>>T;
while(T--)solve();
return 0;
}
C. Phoenix and Distribution
好難啊
先對讀入的字符串 \(s\) 排個序,然后分情況討論
首先如果最小的字符的個數湊不齊 \(k\) 個,那輸出第 \(k\) 個字符(比如說,s="aaxxzz",k=4,顯然輸出 "x" 就行了,比 "x" 小的字符串一定包含 'a' 但是又不可能)
(更:原來寫的時候刪了一個判斷條件,結果發現假了,這里要特判一下 \(n=k\) 的情況qwq)
然后就是重點,我們有兩種不同的方法,第一種方法是將答案均攤(第一個字符放第一個,第二個字符放第二個,第 \(k+1\) 個字符又放回第一個,依次類推),第二種方法是把前 \(k-1\) 個字符放前 \(k-1\) 個桶里,剩下的塞到最后一個桶
這兩種方法取個max當然再好不過,我用了比較蠢的判定方法,如果先有 \(k\) 個相同的字符,剩下的 \(n-k\) 個字符也都相同(前 \(k\) 個字符 \(=A\),后 \(n-k\) 個字符 \(=B\),當然 \(A=B\) 也沒事),這時候一定要均攤方法,其他情況用第二種方法
公式渲染好像有問題,貼個截圖
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
#define int ll
string s;
void solve(){
int n,k; cin>>n>>k;
cin>>s; sort(s.begin(),s.end());
if(s[k-1]!=s[0] || n==k){
putchar(s[k-1]);
puts("");
return;
}
if(s[n-1]==s[k]){
putchar(s[0]);
repeat(i,0,(n-1)/k)
putchar(s[k]);
puts("");
return;
}
puts(s.c_str()+k-1);
}
signed main(){
int T; cin>>T;
while(T--)solve();
return 0;
}
D. Phoenix and Science
一波操作后,發現其實要我們求一個最短的單調不減序列,這個序列的第一個數是 \(1\),任意相鄰的兩個數,后一個不能超過前一個的兩倍,並且序列所有數之和為 \(n\)(解釋一下,序列的第i個數表示第i天的細胞數)
初步思考可以發現,細胞數要以最快速度增長(指數增長),即 \(1,2,4,8...\),如果發現數字之和即將大於 \(n\) 就要減緩。但是這么寫有點麻煩,一個簡單的方法是生成一個長度為 \(sz\) 的序列 \(a[i]\),前 \(sz-1\) 個數滿足指數增長,最后一個數等於 \(n-\sum\limits_{i=1}^{sz-1}a[i]\),如果 \(a[sz]<a[sz-1]\)(不符合要求)就要平均一下這兩個數,這一步的代碼表示為
if(a[sz-1]<a[sz-2]){
int sum=a[sz-1]+a[sz-2];
a[sz-2]=sum/2;
a[sz-1]=(sum+1)/2;
}
然后輸出 \(sz-1\) 和 \(a[i]\) 的差分數組即可
公式渲染好像有問題,貼個截圖
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
#define int ll
vector<int> a;
void solve(){
int n; cin>>n; a.clear();
for(int i=1;;i<<=1){
if(n<=i){a.push_back(n); break;}
a.push_back(i);
n-=i;
}
int sz=a.size();
if(a[sz-1]<a[sz-2]){
int sum=a[sz-1]+a[sz-2];
a[sz-2]=sum/2;
a[sz-1]=(sum+1)/2;
}
cout<<sz-1<<endl;
repeat(i,0,sz-1)
cout<<a[i+1]-a[i]<<' ';
cout<<endl;
}
signed main(){
int T; cin>>T;
while(T--)solve();
return 0;
}
E. Phoenix and Berries
假設同色的漿果才能放入同一籃子中,那么答案顯然是 \(\lfloor\dfrac{\sum a[i]}k\rfloor+\lfloor\dfrac{\sum b[i]}k\rfloor\)
如果同樹的漿果也能放同一籃子呢?
智熄地思考后發現,由於 \(n,k\) 很小,甚至可以用優化后的 \(O(n^3)\) 算法(不優化其實也行),而題目又和(模 \(k\) 數)很有關系,我們考慮在模 \(k\) 的意義上背包dp(就是如果只考慮同樹的漿果放籃子里,那么我們要dp出成功摘下的第一種顏色的漿果個數模k的數的所有可能)
emmm這段是補充,為什么要算模 \(k\)?如果我知道了一種可行的(只摘同樹的漿果的)摘漿果的方法,即第一種顏色拿了 \(t_1k+s_1\) 個,第二種顏色拿了 \(t_2k+s_2\) 個( \(s_1,s_2<k\) 且 \(s_1+s_2=k\)),那么對於這種方法,我們計算 \(\lfloor\dfrac{\sum a[i]-s_1}k\rfloor+\lfloor\dfrac{\sum b[i]-s_2}k\rfloor+1\) 並更新答案即可。震驚,竟然和 \(t_1,t_2\) 沒關系!這是因為就算考慮了 \(t_1,t_2\) 算出來的答案也是一回事,不信可以試試(doge)
dp每個狀態只有01兩種可能,顯然能用bitset優化,復雜度除以32,美滋滋
公式渲染好像有問題,貼個截圖
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=510; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
#define int ll
int a[N],b[N],k;
bitset<N*2> dp,dp2;
void push(int l,int r){
if(l>r)return;
dp2=0;
repeat(i,l,r+1)
dp2|=dp<<i;
dp|=dp2;
dp|=dp>>k;
}
void solve(){
int n,sa=0,sb=0,ans=0;
cin>>n>>k;
repeat(i,0,n)cin>>a[i]>>b[i],sa+=a[i],sb+=b[i];
dp=1;
repeat(i,0,n){
push(max(1ll,k-b[i]),min(k-1,a[i]));
}
ans=sa/k+sb/k;
repeat(i,0,k+1)
if(dp[i]){
int j=k-i;
if(sa>=i && sb>=j)
ans=max(ans,(sa-i)/k+(sb-j)/k+1);
}
cout<<ans<<endl;
}
signed main(){
int T=1; //cin>>T;
while(T--)solve();
return 0;
}
F. Phoenix and Memory
官方題解說有好多好多解法,我思來想去寢食難安也只理解了一種解法(線段樹解法)我好菜啊
首先可以用貪心+優先隊列跑出第一種排列方法。for i=1..n,讓所有左端點為 i 的入隊,讓右端點最小的一個出隊(一個就夠了)並記錄至答案
第二種排列方法難很多,官方題解過於簡潔所以我自己推了一遍。首先要明確一個概念就是,一定一定存在這樣的合法的排列方法,它和第一種排列方法相比只交換了兩個元素(這是區間的玄學性質,莫得證明)
為了方便討論我們讓每個區間儲存一下答案(\(X.mid\)),還有 \(X.l,X.r\) 分別表示區間左端點和右端點。因此根據上面莫得證明的概念,我們的任務是找到兩個區間 \(X,Y\) 滿足關系一:\(X.l\le Y.mid\le X.r\),關系二:\(Y.l\le X.mid\le Y.r\),然后交換它們即可
首先固定一個區間 \(X\)。我們要找滿足兩個關系的區間,這太復雜了,簡單一點,不如先找到所有滿足第二個關系的區間的集合(即 \(\{Y|Y.l\le X.mid\le Y.r\}\))。如果我們的 \(X.mid\) 遞增,維護這樣的集合還是挺容易的(也是優先隊列就能維護了)。那么對於前一個關系(\(X.l\le Y.mid\le X.r\)),其實就是維護 \(\{Y.mid\}\) 這個集合,然后判斷是否有元素落在區間 \(X\) 中,馬上聯想到線段樹。在維護集合 \(\{Y|Y.l\le X.mid\le Y.r\}\) 的同時也維護一個線段樹 \(tr\),其中 \(tr[i]=\) 多少個 \(Y\) 滿足 \(Y.mid=i\)
最后,因為單點修改區間維護所以我用了zkw線段樹
公式渲染好像有問題,貼個截圖/kk
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;} typedef pair<int,int> pii;
struct seg{
#define U(a,b) (a+b) //查詢操作
ll a0=0; //查詢操作的零元
int n; ll a[1024*1024*4*2]; //內存等於2^k且大於等於兩倍inn
void init(int inn){ //建樹
for(n=1;n<inn;n<<=1);
repeat(i,0,n)a[n+i]=0;
repeat_back(i,1,n)up(i);
}
void up(int x){
a[x]=U(a[x<<1],a[(x<<1)^1]);
}
void update(int x,ll k){ //位置x加上k
a[x+=n]+=k; //也可以賦值等操作
while(x>>=1)up(x);
}
ll query(int l,int r){ //區間查詢
ll ans=a0;
for(l+=n-1,r+=n+1;l^r^1;l>>=1,r>>=1){
if(~l & 1)ans=U(ans,a[l^1]); //l^1其實是l+1
if(r & 1)ans=U(ans,a[r^1]); //r^1其實是r-1
}
return ans;
}
}tr;
struct node{
int l,r,m,rawp,p;
}a[N];
struct op{bool operator()(const node &a,const node &b){return -a.r<-b.r;}};
priority_queue<node,vector<node>,op> q;
int n,ans[N],pos[N],r,rr;
void output(){
repeat(i,0,n)cout<<ans[i]+1<<' ';
cout<<endl;
}
void get_first_ans(){
int ptr=0;
repeat(i,0,n){
while(ptr<n && a[ptr].l==i)
q.push(a[ptr++]);
a[q.top().p].m=i; ans[q.top().rawp]=i; pos[i]=q.top().p; q.pop();
}
}
bool get_second_ans(){
int ptr=0;
repeat(i,0,n){
int x=pos[i];
while(ptr<n && a[ptr].l==i){
tr.update(a[ptr].m,1);
q.push(a[ptr++]);
}
while(!q.empty() && a[q.top().p].r<a[x].m){
tr.update(a[q.top().p].m,-1);
q.pop();
}
if(tr.query(a[x].l,a[x].r)>=2){
r=x;
#define inc(a,b,c) (a<=b && b<=c)
repeat(j,0,n)
if(j!=r)
if(inc(a[j].l,a[r].m,a[j].r) && inc(a[r].l,a[j].m,a[r].r)){
rr=j;
return true;
}
cout<<"wcynb"<<endl; return false; //這樣我可以在評測記錄中知道程序運行到了不該運行的地方
}
}
return false;
}
signed main(){
n=read(); tr.init(n+1);
repeat(i,0,n){
a[i].l=read()-1;
a[i].r=read()-1;
a[i].rawp=i;
}
sort(a,a+n,[](const node &a,const node &b){
return a.l<b.l;
});
repeat(i,0,n)a[i].p=i;
get_first_ans();
if(get_second_ans()){
cout<<"NO"<<endl;
output();
swap(ans[a[r].rawp],ans[a[rr].rawp]);
output();
}
else{
cout<<"YES"<<endl;
output();
}
return 0;
}