[68] (煉石計劃) NOIP 模擬賽 #20

HaneDaniko發表於2024-11-14

學了一個挺帥的 MerMaid

所以用一下

MerMaid 就是你們接下來會看到的好看小標題

但是實際上它是用來畫流程圖的……

flowchart TB A(鄰間的骰子之舞) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

考慮每次複製以後一定會貼上若干次(大於零,否則沒有意義),因此將複製貼上捆綁起來考慮,設複製後連續貼上了 \(m\) 次,則代價為 \(x+my\),貢獻為讓編輯器內文字個數變為 \(m+1\)

假設我們做了 \(k\) 次上述操作,第 \(i\) 次操作的貼上次數為 \(m_i\),則

  • 總貢獻為 \(\prod\limits^k_i(m_i+1)\)
  • 總代價為 \(\sum\limits^k_i(x+m_iy)\)

根據題意,我們需要 \(\prod\limits^k_i(m_i+1)\gt n\),在此基礎上總代價最小

轉化一下總代價的式子

\[\begin{aligned}ans&=\sum^k_i(x+m_iy)\\&=kx+y\sum^k_im_i\end{aligned} \]

\(k\) 不變時,\(kx\) 項不變,等價於求 \(\sum^k_im_i\) 的最小值

引理:最優答案中不存在 \(i,j\),使得 \(m_i-m_j\gt 1\)

證明:設 \(m_i-m_j\gt 1\),因為 \((m_i-1)(m_j+1)=m_im_j+m_i-m_j-1=m_i(m_j+1)\gt m_im_j\),故其一定不是最優解

因此設最終答案中最小的數為 \(t\),則最大的可能值一定為 \(t+1\),如果我們欽定有 \(p\)\(t\),則題目中條件等價於 \(t^p\times (t+1)^{k-p}\gt n\),可以列舉所有可能的 \(p\),透過二分答案找到最小的 \(t\) 的可能值來計算貢獻

在外層列舉 \(k\) 即可,\(k\le log_2n\),因此複雜度為列舉 \(k\) 套列舉 \(p\) 套二分答案套快速冪,\(\log^4n\)

記得開 int128



#include<bits/stdc++.h>
#define int __int128
using namespace std;
long long n,x,y;
int power(int a,int t){
	int base=a,ans=1;
	while(t){
		if(t&1){
            ans=ans*base;
        }
		base=base*base;
		t>>=1;
	}
	return ans;
}
signed main(){
	int res=0x7fffffffffffffff;
	cin>>n>>x>>y;
	for(int k=1;k<=n and clock()<=0.85*CLOCKS_PER_SEC;++k){
		for(int i=0;i<=k;++i){
			int l=2,r=ceil(pow(n,(double)1/k)*2),ans=-1;
			while(l<=r){
				int mid=(l+r)/2;
				if(power(mid,i)*power(mid+1,k-i)>n){
                    ans=mid;
                    r=mid-1;
                }
				else l=mid+1;
			}
			if(ans==-1) continue;
			res=min(res,(__int128)k*x+((__int128)(ans-1)*i+(__int128)ans*(k-i))*(__int128)y);
		}
	}
	cout<<(long long)res;
}
flowchart TB A(星海浮沉錄) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

暴力思路:可以處理出值域上每個值在原數列上的最大不出現長度 \(l_i\)(形式化地,\(l_i\) 為滿足存在 \(j\),使得 \(\forall k\in[j,j+l_i-1],a_k\neq i\) 的最大值),然後對這個最大長度做字首 \(\max\),這樣就可以二分答案了,修改 \(n\) 查詢 \(\log n\)

最佳化思路:對值域建線段樹,每個點 \(i\) 記錄 \(l_i\),這樣查詢字首 \(\max\) 相當於查區間最值,不用重構 \(\max\) 陣列,然而並沒有什麼最佳化,修改 \(n\) 查詢 \(\log^2n\)

再次最佳化思路:由於單點修改隻影響相鄰的兩個區間的長度,因此考慮只改變這兩個區間的值

可以想到對每個值維護一個 set,記錄每個值的位置,方便查詢其前驅,後繼,然後算出距離後進行刪除,那麼我們就還需要一種資料結構來支援快速刪除,插入,查詢最大值

賽時卡這了,沒想到解法竟然是無腦上兩顆優先佇列

對值域上每個值開兩個優先佇列,一個表示現有的值,一個表示刪除的值

  • 加入操作:直接放進現有值的優先佇列
  • 刪除操作:直接放進刪除的值的優先佇列
  • 查詢最大值操作:由於刪除佇列內的值一定是加入操作內的值的子集,因此如果當前現有值佇列的對首已經被刪除,則它也一定是刪除值佇列的對首,因此只需要判斷兩個佇列的對首是否相等即可

修改 \(\log n\) 查詢 \(\log^2n\)



#include<bits/stdc++.h>
using namespace std;
int n,q;
int a[500001];
int lstpos[500001];
namespace stree{
    struct tree{
        int maxn;
    }t[500001*4];
    #define tol (id*2)
    #define tor (id*2+1)
    #define mid(l,r) mid=((l)+(r))/2
    void change(int id,int l,int r,int pos,int val){
        if(l==r){
            t[id].maxn=val;
            return;
        }
        int mid(l,r);
        if(pos<=mid) change(tol,l,mid,pos,val);
        else change(tor,mid+1,r,pos,val);
        t[id].maxn=max(t[tol].maxn,t[tor].maxn);
    }
    int ask(int id,int l,int r,int L,int R){
        if(L<=l and r<=R){
            return t[id].maxn;
        }
        int mid(l,r);
        if(R<=mid) return ask(tol,l,mid,L,R);
        else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
        return max(ask(tol,l,mid,L,mid),ask(tor,mid+1,r,mid+1,R));
    }
}
set<int>pos[500001];
struct getmax_t{
    priority_queue<int> add_t,del_t;
    inline void add(int x){
        add_t.push(x);
    }
    inline void del(int x){
        del_t.push(x);
    }
    inline int max(){
        while(del_t.empty()==false and del_t.top()==add_t.top()){
            del_t.pop();
            add_t.pop();
        }
        return add_t.top();
    }
};
getmax_t maxn[500001];
inline void add(int _pos){
    pos[a[_pos]].insert(_pos);
    auto iter1=pos[a[_pos]].lower_bound(_pos);iter1--;
    auto iter2=pos[a[_pos]].upper_bound(_pos);
    maxn[a[_pos]].add(_pos-*iter1-1);
    maxn[a[_pos]].add(*iter2-_pos-1);
    stree::change(1,1,n+1,a[_pos]+1,maxn[a[_pos]].max());
}
inline void del(int _pos){
    auto iter1=pos[a[_pos]].lower_bound(_pos);iter1--;
    auto iter2=pos[a[_pos]].upper_bound(_pos);
    maxn[a[_pos]].del(_pos-*iter1-1);
    maxn[a[_pos]].del(*iter2-_pos-1);
    pos[a[_pos]].erase(_pos);
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>q;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        maxn[a[i]].add(i-lstpos[a[i]]-1);
        pos[a[i]].insert(i);
        lstpos[a[i]]=i;
    }
    for(int i=0;i<=n;++i){
        pos[i].insert(0);
        pos[i].insert(n+1);
        maxn[i].add(n-lstpos[i]);
        stree::change(1,1,n+1,i+1,maxn[i].max());
    }
    while(q--){
        int opt,x;
        cin>>opt>>x;
        if(opt==1){
            del(x);del(x+1);
            swap(a[x],a[x+1]);
            add(x);add(x+1);
        }
        else{
            int l=0,r=n,ans=n+1;
            while(l<=r){
                int mid=(l+r)/2;
                if(stree::ask(1,1,n+1,1,mid+1)>=x){
                    ans=mid;
                    r=mid-1;
                }
                else l=mid+1;
            }
            cout<<ans<<'\n';
        }
    }
}
flowchart TB A(第八交響曲「千日同升」) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

這道題用一種比較好玩的方式考了並行排序

部分分解法是常數較大的奇偶排序(多執行緒冒泡(?))



#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
    cin>>n;
    cout<<((n+1)/2)*2<<endl;
    for(int i=1;i<=((n+1)/2);i++){
        for(int j=1;j+1<=n;j+=2){
            cout<<"CMPSWP R"<<j<<" R"<<j+1<<' ';
        }
        cout<<'\n';
        for(int j=2;j+1<=n;j+=2){
            cout<<"CMPSWP R"<<j<<" R"<<j+1<<' ';
        }
        cout<<'\n';
    }
}

正解用的是比較優秀的雙調排序

關於雙調排序的學習推薦 這篇文章


這是什麼

相關文章