C
令\(x=i\times j\),\(f(x)=p=(i\times j)^2=x^2\)(\(i,j\geq 1\)),\(f(x)\)的所有取值構成可重集和\(S\),同時\(S\)為單調不減的,現在求\(S\)中前\(k\)小的元素之和。
因為\(k\)比較大,顯然我們不能直接暴力枚舉。我們嘗試模擬一下會發現:
x=1,(i,j)組合1種
x=2,(i,j)組合2種
x=3,(i,j)組合2種
......
x=v,(i,j)組合等於cnt[fac[v]]
也就是x=v的(i,j)組合數等於x的因數個數
令\(g(x)=\sum_{i=1}^x{cnt[i]}\),其中\(cnt[i]\)表示\(i\)的因數個數。我們需要找到最大的那個\(x\),滿足\(g(x)\leq k\),這個可以通過二分答案,然後每次\(check\)使用分塊解決,時間複雜度為\(O(\sqrt{V}\log {V})\)。
找到上界後,我們需要解決\(\sum_{i=1}^{x}{i^2\times cnt[i]}\),也就是\(\sum_{i=1}^{x}\sum_{d=1}^{i}{i^2}\times [d\mid i]\),如果這樣寫表示每次枚舉數\(num\),然後枚舉其因數,時間複雜度為\(O(V\times \sqrt{V})\),顯然會爆掉。
我們交換一下枚舉順序:\(\sum_{d=1}^{x}\sum_{k=1}^{\lfloor\frac{x}{d}\rfloor}{(kd)^2}=\sum_{d=1}^{x}d^2\sum_{k=1}^{\lfloor\frac{x}{d}\rfloor}{k^2}\),令\(m=\lfloor\frac{x}{d}\rfloor\),那麼\(\sum_{k=1}^{m}{k^2}=\frac{n\times (n+1)\times(2\times n+1)}{6}\)。
我們可以根據\(d\)對\(\lfloor\frac{x}{d}\rfloor\)進行分塊,也就是\(\lfloor\frac{x}{l}\rfloor=\lfloor\frac{x}{r}\rfloor\),然後對於相同的\(m\),令\(h(x)=\sum_{i=1}^x{i^2}\),式子變為\(\sum{(h(r)-h(l-1)\times h(m))}\)。
注意要及時取模就好,最後我們再加上\((k-g(x))\times x^2\)就好。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 998244353;
//求[1,n]的約數個數之和
int inv_6;
int f(int n){
int sum=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
sum+=n/l*(r-l+1);
}
return sum;
}
int ksm(int b,int p){
int res=1;
while(p){
if(p&1) res=res*b%mod;
b=b*b%mod;
p>>=1;
}
return res;
}
//g(n)為[1,n]所有數平方和
int g(int n){
return n%mod*(n%mod+1)%mod*(n%mod*2%mod+1)%mod*inv_6%mod;
}
void solve() {
int k;
cin >> k;
inv_6=ksm(6,mod-2);
int lo=1,hi=1e12;
int lim=1;
//求
auto check=[&](int mid)->bool{
return f(mid)<=k;
};
while(lo<=hi){
int mid=(lo+hi)/2;
if(check(mid)){
lim=mid;
lo=mid+1;
}else hi=mid-1;
}
// cout << lim << endl;
//找到了上限,對前面求和
int ans=0;
for(int l=1,r;l<=lim;l=r+1){
r=lim/(lim/l);
int m=lim/l;
ans=(ans+g(m)*((g(r)-g(l-1)+mod)%mod)%mod)%mod;
}
int r=k-f(lim);
ans=(ans+r*(lim%mod+1)%mod*(lim%mod+1)%mod)%mod;
cout << ans << endl;
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
H
最大化\(mex\oplus gcd\),很經典的討論就是枚舉\(mex\),我們只需要討論\(mex=0,1\)和\(mex>1\)的三種情況即可。
這道題有一個前置知識:
給定一個序列\(a\),現在有不可重集合\(S\)以及\(a\)的子集\(A\),那麼\(s=(\sum_{v\in A}{v})\in S\),也就是説任選一個\(a\)的子集,其子集元素和\(sum\)是\(S\)中的元素,求\(a\)的子集合最大不能表示的數\(mex\)。
這個可以通過排序+貪心來解決,我們先對\(a\)進行排序,之後假設現在能夠表示\([0,x]\),那麼可以討論現在枚舉的數\(a_i\):
- 如果\(a_i>x+1\),那麼\(mex=x+1\);
- 否則可表示範圍為\([0,x+a_i]\);
這個時間複雜度是\(O(n\log{n})\)的。
sol:
當\(mex=0\)時,\(ans=sum[r]-sum[l-1]\)。
當\(mex=1\)時,\(ans=(sum[r]-sum[l-1])\oplus 1\)。
當\(mex\geq 2\)時,\(gcd=1\),我們只需要最大化\(mex\)就行,\(ans=mex\oplus 1\)。
最後答案肯定在三種情況中的一種,前面只需要維護前綴和以及0的個數即可。
對於第三個,因為有\(q\)個詢問,如果每次排序然後他貪心選擇的話,時間複雜度為\(O(qn\log{n})\),顯然不行。那麼根據我們的前置知識,我們可以考慮使用數據結構來優化這個。
這裏有一個很神奇的結論,就是這裏\(mex\)的變化次數最多\(log{V}\)次,説是和斐波拉契數列的變化同階。所以使用可持久化線段樹的總複雜度為\(n\log^2{V}\)。
法一:可持久化線段樹
我們枚舉最大覆蓋值\(x\),那麼每次查詢在\([l,r]\)中,值小於等於\(x+1\)的元素之和\(res\),如果\(res=x\),那麼最大覆蓋值無法再增大,否則\(x=res\)。肯定有的是\(res\geq x\),所以當\(res=x\)時,我們退出。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 1e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
int root[N],tot;
int lc[N*32],rc[N*32],sum[N*32];
int a[N];
vector<int> v;
int n,m;
int build(int l,int r){
int id=++tot;
if(l==r) return id;
int m=l+r>>1;
lc[id]=build(l,m);
rc[id]=build(m+1,r);
return id;
}
int update(int prev,int l,int r,int pos,int add){
int id=++tot;
lc[id]=lc[prev];
rc[id]=rc[prev];
sum[id]=sum[prev]+add;
if(l==r) return id;
int m=l+r>>1;
if(pos<=m) lc[id]=update(lc[prev],l,m,pos,add);
else rc[id]=update(rc[prev],m+1,r,pos,add);
return id;
}
int query(int x,int y,int l,int r,int ql,int qr){
if(ql>qr) return 0;
if(ql<=l&&r<=qr) return sum[y]-sum[x];
int m=l+r>>1;
int res=0;
if(ql<=m) res+=query(lc[x],lc[y],l,m,ql,qr);
if(qr>m) res+=query(rc[x],rc[y],m+1,r,ql,qr);
return res;
}
void solve() {
cin >> n;
vector<int> sm(n+1),zero(n+1);
for(int i=1;i<=n;i++){
cin >> a[i];
v.push_back(a[i]);
sm[i]=sm[i-1]+a[i];
zero[i]=zero[i-1]+(a[i]==0?1:0);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
auto get_id=[&](int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
};
vector<int> id(n+1);
for(int i=1;i<=n;i++){
id[i]=get_id(a[i]);
}
int rsize=v.size();
root[0]=build(1,rsize);
for(int i=1;i<=n;i++){
root[i]=update(root[i-1],1,rsize,id[i],a[i]);
}
cin >> m;
while(m--){
int l,r;
cin >> l >> r;
if(zero[r]-zero[l-1]==0){
cout << sm[r]-sm[l-1] << endl;
continue;
}
int s=0;
while(1){
//查詢區間[l,r]小於等於need的值的和
int pos=upper_bound(v.begin(),v.end(),s+1)-v.begin();
int res=query(root[l-1],root[r],1,rsize,1,pos);
if(res==s) break;
s=res;
}
int mex=s+1;
int ans=sm[r]-sm[l-1];
if(mex>=1) ans=max(ans,1^(sm[r]-sm[l-1]));
if(mex>1) ans=max(ans,1^mex);
cout << ans << endl;
}
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
法二:離線莫隊+樹狀數組
我們對原問題進行離線,之後用樹狀數組維護前綴和,單次修改為\(\log{n}\),單次查詢為\(\log{n}\),那麼總時間複雜度應該為\(O(q\log{n}+n\sqrt{n}\log{n})\),按道理後面那個完全會超呀。但是這道題就是可以過,這就是\(mex\)的神奇性質嘛。
但是還是非常極限了時間879ms,所以還是用可持久化線段樹好一點。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
struct Fenwick{
vector<int> sum;
int n;
Fenwick(int n){this->n=n;sum.resize(n+1);}
int lowbit(int x){return x&-x;}
void update(int pos,int k){for(int i=pos;i<=n;i+=lowbit(i)) sum[i]+=k;}
int query(int pos){int res=0;for(int i=pos;i;i-=lowbit(i)) res+=sum[i];return res;}
};
void solve() {
int n,m;
cin >> n;
vector<int> a(n+1),zero(n+1),sum(n+1);
for(int i=1;i<=n;i++){
cin >> a[i];
sum[i]=sum[i-1]+a[i];
zero[i]=zero[i-1]+(a[i]==0);
}
vector<int> b=a;
sort(b.begin(),b.end());
b.erase(unique(b.begin(),b.end()),b.end());
int rsize=b.size();
vector<int> id(n+1);
auto get_id=[&](int x){
return lower_bound(b.begin(),b.end(),x)-b.begin()+1;
};
for(int i=1;i<=n;i++){
id[i]=get_id(a[i]);
}
cin >> m;
vector<array<int,3>> qu(m+1);
for(int i=1;i<=m;i++){
cin >> qu[i][0] >> qu[i][1];
qu[i][2]=i;
}
int B=sqrt(n)+1;
sort(qu.begin()+1,qu.end(),[&](array<int,3> x,array<int,3> y){
auto [l1,r1,id1]=x;
auto [l2,r2,id2]=y;
if((l1-1)/B!=(l2-1)/B) return (l1-1)/B<(l2-1)/B;
else return r1<r2;
});
Fenwick tr(rsize);
auto del=[&](int pos){
tr.update(id[pos],-a[pos]);
};
auto add=[&](int pos){
tr.update(id[pos],a[pos]);
};
auto get=[&](int l,int r){
int res=sum[r]-sum[l-1];
if(zero[r]-zero[l-1]==0){
return res;
}
int s=0;
while(1){
int pos=upper_bound(b.begin(),b.end(),s+1)-b.begin();
int sm=tr.query(pos);
if(sm==s) break;
s=sm;
}
int mex=s+1;
res=max(res,res^1);
if(mex>1) res=max(res,mex^1);
return res;
};
int cl=1,cr=0;
vector<int> ans(m+1);
for(int i=1;i<=m;i++){
auto[l,r,id]=qu[i];
while(l<cl) add(--cl);
while(cr<r) add(++cr);
while(l>cl) del(cl++);
while(r<cr) del(cr--);
ans[id]=get(l,r);
}
for(int i=1;i<=m;i++) cout << ans[i] << endl;
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
最後討論一下為什麼這裏求\(mex\)的次數最多\(\log{V}\)次。
看一個例子:
初始:[0,-1]
加入0:[0,0]
加入1:[0,1]
加入2:[0,3]
加入4:[0,7]
加入8:[0,15]
很明顯了,如果當前覆蓋區間是\([0,x]\),那麼下一次我們找的是\(x+1\),那麼下一次覆蓋範圍為\([0,2\times x+1]\),這個就能夠看出來了。
A
給定\(n\)個數排成一行,每5個數一組,將前面4個丟棄,保留第5個數,直到剩下的數小於4個,問最後剩下幾個數,分別是多少。
模擬一下可以知道,當前是\(n\),那麼剩下的數為\(n/5+n\%5\),我們記下每一輪的\(Q_j,r_j\),這樣我們可以倒推:
如果當前剩下\(m\)個數,那麼對於\(idx\in[1,m]\),可以分為下面兩種情況:
- 如果\(idx\le Q_j\),那麼\(idx\)是整數分組中的,那麼前一輪的位置為\(5\times idx\);
- 如果\(idx>Q_j\),那麼\(idx\)是最後餘下的幾個數中的,那麼前一輪的位置為\(5\times Q_j+(idx-Q_j)\);
所以我們直接按照這個模擬就行,時間複雜度為\(O(\log{n})\)。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
void solve() {
int n;
cin >> n;
vector<int> num,Q;
num.push_back(n);
while(num.back()>=4){
int v=num.back();
int q=v/5;
Q.push_back(q);
int r=v%5;
num.push_back(q+(r==4?0:r));
}
vector<int> ans;
for(int i=1;i<=num.back();i++){
int idx=i;
for(int j=Q.size()-1;j>=0;j--){
if(idx<=Q[j]) idx=idx*5;
else idx=Q[j]*5+(idx-Q[j]);
}
ans.push_back(idx);
}
cout << ans.size() << endl;
for(int i:ans) cout << i << " ";
cout << endl;
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}