20220614
- rk 3/9, 100+30+0=130
- max: 100,60,100, 100+50+10=160
区间第 k 小
求第 k 大考虑整体二分,需要统计 \(\le mid\) 的个数
出现 \(>w\) 次视为 \(n\) 相当于删掉,对于数值 \(x\),记 \(r\) 前 \(w\) 个权值为 \(1\),第 \(w+1\) 个权值为 \(-w\),把统计个数改为求权值和,容易发现是等价的
求出每个位置对哪些 \([l,r]\) 贡献 \(0/1/w\),问题变为矩形加单点求值,扫描线即可
强制在线就直接存下来整体二分的结构,把用于扫描线的线段树可持久化了,时空复杂度 \(O(n\log^{2}n)\)(其实就是树套树)
赛时做法是用在线化莫队套值域分块,复杂度 \(O(n\sqrt{n})\)
考场代码
const int N = 1e5+5, B = 333, BN = N/B+3;
int n,m,q,ty,ans,a[N],c[N],d[BN],cnt[N],
in[N],le[BN],ri[BN],bcnt[BN][N],b[BN][BN][BN],bb[BN][BN];
Vi aa;
void add(int *a,int *b,int x) {
if( a[x]++ == m ) a[n] += m, b[in[n]] += m, b[in[x]] -= m;
if( a[x] > m ) ++a[n], ++b[in[n]];
else ++b[in[x]];
}
signed main() { freopen("kth.in","r",stdin); freopen("kth.out","w",stdout);
io>>n>>m>>q>>ty; For(i,1,n) io>>a[i];
For(i,0,n) in[i] = i/B; For(i,0,in[n]) le[i] = i*B, ri[i] = le[i]+B-1;
For(i,0,in[n]) {
For(j,i,in[n]) {
if( i < j ) copy(b[i][j-1],b[i][j-1]+in[n]+1,b[i][j]);
For(k,le[j],ri[j]) add(c,b[i][j],a[k]);
bb[i][j] = c[n];
}
mem(c,0,n);
if( i ) copy(bcnt[i-1],bcnt[i-1]+n,bcnt[i]);
For(j,le[i],ri[i]) ++bcnt[i][a[j]];
}
while( q-- ) {
int l,r,ll,rr,k; io>>l>>r>>k; if( ty ) l^=ans, r^=ans, k^=ans;
ll = in[l]+1, rr = in[r]-1;
if( ll > rr ) For(i,l,r) aa.pb(a[i]);
else {
c[n] = bb[ll][rr], copy(b[ll][rr],b[ll][rr]+in[n]+1,d);
For(i,l,ri[in[l]]) aa.pb(a[i]);
For(i,le[in[r]],r) aa.pb(a[i]);
for(int i : aa) c[i] = bcnt[rr][i]-bcnt[ll-1][i];
}
for(int i : aa) add(c,d,i), ++cnt[i];
// cerr<<"c: "; Rep(i,0,n) cerr<<c[i]<<' '; cerr<<' '<<c[n]<<endl;
For(i,0,in[n])
if( k > d[i] ) k -= d[i];
else
for(ans = le[i]; ans < n; ++ans) {
int x = cnt[ans]+(ll<=rr?bcnt[rr][ans]-bcnt[ll-1][ans]:0);
if( x > m ) continue;
if( k <= x ) goto togo;
k -= x;
}
togo: io<<ans<<endl;
for(int i : aa) c[i] = cnt[i] = 0; c[n] = 0;
mem(d,0,in[n]), aa.clear();
}
return 0;
}
求和
推下式子:
\[\sum_{i=1}^{n}\sum_{j=1}^{n}f_{k}(\gcd(i,j))=\sum_{d=1}^{n}f_{k}(d)\left((2\sum_{i=1}^{n/d}\varphi(i))-1)\right)
\]
整除分块后杜教筛 \(\varphi\) 前缀和,剩下的问题是求 \(f_k\) 前缀和
PN 筛。构造 \(g=\mu,h(p^{k})=f_{k}(p^{k})+h(p^{k-1})\)
实现细节:预处理每个 PN 的 \(\sum_{i=1}^{k}f_{i}\),线筛前 \(n^{\frac{2}{3}}\)
时间复杂度 \(O(k\sqrt{n}+n^{\frac{2}{3}})\)
code
const int N = 1e7;
LL n;
int m,ans,mnp[N],mx[N],mu1[N],phi1[N],f[N],sf[N];
vector<LL> p;
struct Vec {
int a[42];
Vec() { memset(a,0,sizeof a); }
int& operator [] (int i) { return a[i]; }
int operator [] (int i) const { return a[i]; }
void operator += (const Vec &b) { For(i,1,m) a[i] += b[i]; }
Vec operator * (const Vec &b) const {
Vec res = *this; For(i,1,m) res[i] *= b[i];
return res;
}
int sum() { return accumulate(a+1,a+m+1,0u); }
} I;
void sieve() {
bitset<N> vis;
mx[1] = phi1[1] = mu1[1] = f[1] = 1;
Rep(i,2,N) {
if( !vis[i] ) p.pb(i), mnp[i] = mx[i] = 1, phi1[i] = i-1, mu1[i] = f[i] = -1;
for(int j = 0; j < sz(p) && i*p[j] < N; ++j) {
vis[i*p[j]] = 1, f[i*p[j]] = -f[i];
if( !(i % p[j]) ) {
mnp[i*p[j]] = mnp[i]+1, mx[i*p[j]] = max(mx[i],mnp[i*p[j]]),
phi1[i*p[j]] = phi1[i] * p[j];
break;
}
mnp[i*p[j]] = 1, mx[i*p[j]] = mx[i],
phi1[i*p[j]] = phi1[i] * (p[j]-1), mu1[i*p[j]] = -mu1[i];
}
}
Rep(i,1,N) {
phi1[i] += phi1[i-1], mu1[i] += mu1[i-1], sf[i] = sf[i-1];
if( mx[i] <= m ) sf[i] += (m-mx[i]+1) * f[i];
}
}
int mu(LL m) {
static gp_hash_table<LL,int> mu2;
if( m < N ) return mu1[m];
if( mu2.find(m) != mu2.end() ) return mu2[m];
int res = 1;
for(LL l = 2, x,r; l <= m; l = r+1)
x = m/l, r = m/x, res = res - mu(x) * int(r-l+1);
return mu2[m] = res;
}
int phi(LL m) {
static gp_hash_table<LL,int> phi2;
if( m < N ) return phi1[m];
if( phi2.find(m) != phi2.end() ) return phi2[m];
int res = int(m) * int(m+1) / 2;
for(LL l = 2, x,r; l <= m; l = r+1)
x = m/l, r = m/x, res = res - phi(x) * int(r-l+1);
return phi2[m] = res;
}
struct {
Vec hp[34];
vector<pair<LL,int>> h;
void dfs(LL n,int i,LL x,Vec hx) {
LL y = n/x; h.pb(x,hx.sum());
for(; p[i]*p[i] <= y; ++i)
for(LL pp = p[i]*p[i], e = 2; pp <= y; pp *= p[i], ++e)
dfs(n,i+1,x*pp,hx*hp[e]);
}
void init() {
hp[0] = I;
For(i,1,min(33u,m))
fill(hp[i].a+i,hp[i].a+m+1,i&1?-1:1), hp[i] += hp[i-1];
dfs(n,0,1,I), sort(all(h));
}
int operator [] (LL n) {
if( n < N ) return sf[n];
int res = 0;
for(auto &i : h) {
if( i.fi > n ) break;
res += i.se * mu(n/i.fi);
}
return res;
}
} pn;
signed main() { freopen("sum.in","r",stdin); freopen("sum.out","w",stdout);
io>>n>>m, sieve();
For(i,1,m) I[i] = 1;
pn.init();
int lst = 0, now;
for(LL l = 1, x,r; l <= n; l = r+1)
x = n/l, r = n/x,
now = pn[r], ans += (now-lst) * (2*phi(x)-1), lst = now;
io<<(ans&((1<<30)-1));
return 0;
}
树
诈骗题。结论:
- 答案中每个点不会同时作为起/终点,否则合并起来会少一条路径
- 把两条路径 \((u,v),(x,y)\) 改为 \((u,y),(x,v)\) 不影响权值(相交部分一正一反抵消了)
由结论 1 可以初始令每条边为一条路径(从叶子递推到根即可),随意合并,得到每个点作为起/终点的次数。由结论 2 可以贪心地令起/终点各自的字典序最小
code
const int N = 1e6+5;
int n,a[N],f[N];
Vi s,t,e[N];
void dfs(int u,int fa) {
for(int v : e[u]) if( v != fa ) {
dfs(v,u), a[u] -= a[v];
if( u < v ) f[u] += a[v], f[v] -= a[v];
else f[u] -= a[v], f[v] += a[v];
}
}
signed main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
io>>n; For(i,1,n) io>>a[i]; Rep(i,1,n, x,y) io>>x>>y, e[x].pb(y), e[y].pb(x);
dfs(1,0);
For(i,1,n)
if( f[i] > 0 ) For(j,1,f[i]) s.pb(i);
else For(j,1,-f[i]) t.pb(i);
io<<sz(s)<<endl;
Rep(i,0,sz(s)) io<<s[i]<<' '<<t[i]<<endl;
return 0;
}