中值定理

模型:如果一个序列 \(a\) 长度为 \(p\)\(a_1 = 0, a_p = 1\),需要找到一个长度为 \(q\) 的子序列 \(a[l, r]\) 使得 \(a_l = 0, a_r = 1\)\(q|p\),那么可以这样考虑:对于 \([a_1,a_q],[a_{q+1},a_{2q}],[a_{2q+1},a_{3q}]...\) 一定存在至少一个序列满足条件。这是个可以二分的结构。二分的时候以 \(q\) 为单位长度,令 \(mid = kq\),若 \(a_{mid}=1\),那么继续在 \(a[l,mid]\) 上二分。否则在 \([mid+1,r]\) 上二分。

image

UER #11 B

【题意】给定一个长度为 \(2n-1\) 的序列,要求拿出其中的 \(n\) 个数,使得它们加起来是 \(n\) 的倍数。
\(n \le 5 \times 10^5\)
【分析】
令命题 \(EGZ(n)\) 表示对于任意数组 \(n\) 都符合题意并且找到了一个构造方法。

\(n\) 为合数,不妨设 \(n=pq\)。假设 \(EGZ(p) \and EGZ(q)\),那么考虑首先对 \(n\) 不断取前 \(2p-1\) 个数,然后找出 \(p\) 个符合条件的数合并成一个 \(p\) 的倍数,再将其他数丢回去,直到剩下的数没有 \(2p-1\) 个为止。注意到 \(2n-1=2pq-1=(2q-2)p+(2p-1)\),因此可以找出 \((2q-1)\)\(p\) 的倍数,剩下可以扔掉。然后对这些数做 \(EGF(q)\) 就得到了一组合法解。因此只需要考虑素数情形。

素数怎么办。考虑 \(EGZ(p)\)。考虑对这 \(2p-1\) 个数采用不断选众数和第二多的数的方式,分成 \(p-1\) 个二元组和一个剩下来的数,其中每个二元组都满足其中两个数不一样。

我们选上这个数。另外选上每个二元组第一个数。得到一个 \(p\) 个数的选择方案,加起来为 \(k\)。我们的问题转化为了,有 \(p\) 个数 \(d_1,...,d_p\)(第二个数与第一个数的差),选出一个子集满足加起来模 \(p\)\(-k\)

先考虑证明一定存在一种选法。我们归纳地证明。

假设前 \(i\) 个数选出的子集和可以表出的数集合为 \(S_i\)。定义集合 \(S_i + k\) 表示 \(\{a+k|a\in S_i\}\)。那么考虑证明:\(k \not \equiv 0(\bmod p)\) 并且 \(S_i\) 不是 \(p\) 的完全剩余系的情况下 \(S_i \neq S_i+k\)

这个怎么证,反证法。假设成立,那么考虑 \(x \in S_i, y \not \in S_i\) 有:\(x+k \in S_i, x+2k \in S_i,...\),但是我们令 \(j = (y-x) \times k^{-1}\),有 \(x+jk=y \in S_i\),矛盾。因此原命题成立。

这个命题代表了什么?\(S_i\) 不是 \(p\) 的完全剩余系的情况下 \(|S_i \cup (S_i+k)| > S_i\)!因此对于 \(S_0 = {0}\)\(S_n\) 一定是 \(p\) 的完全剩余系,因为每次会增长至少 \(1\) 个数。

我们考虑构造答案。首先我们让每次增长恰好 \(1\) 个数,则比较容易记录答案。暴力增长是 \(O(n^2)\) 的。我们考虑采用中值定理优化。

首先我们有个非常巧妙的观察:若 \(xk \in S_i, (x+1)k \not \in S_i\),那么一定可以将 \((x+1)k\) 放进去!并且你可以找到一个 \((u,v)\) 使得 \(uk \in S_i, vk \not \in S_i\)。因此可以直接中值定理二分,\(q=1\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
//调不出来给我对拍!
int a[700010];
bitset<300000> bs;
vector<int> v[300100];
set<pii> s;
pair<int, int> p;
int nsum = 0;
vector<int> ts;
vector<int> pre;
vector<int> add;
int tim[300100];
int ch[300100];
int n; 
int inv[300100];
int qpow(int x, int k) {
    int ans = 1;
    while(k) {
        if(k & 1) ans = ans * x % n;
        x = x * x % n;
        k >>= 1;
    }
    return ans;
}
int bin(int i, int j, int d) {
    int l = i * inv[d]; int r = j * inv[d] - 1;
    while(r < l) r += n;
    while(l < r) {  
        int mid = (l + r + 1) >> 1;
        if(!bs[mid * d % n]){r = mid - 1; }
        else { l = mid; }
    }
    return (r + 1) * d % n;
}
set<int> cp, uc;  //选了,没选  
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin >> n;
    f(i, 1, n - 1) inv[i] = qpow(i, n - 2); 
    f(i, 1, 2 * n - 1) cin >> a[i];
    f(i, 1, 2 * n - 1) a[i] %= n;
    f(i, 1, 2 * n - 1) { v[a[i]].push_back(i); }
    f(i, 0, n - 1) { if(v[i].size() >= n) {f(j, 0, n - 1) {cout << v[i][j] << " ";} cout << endl; return 0;}}
    f(i, 0, n - 1) { s.insert({v[i].size(), i}); }
    ts.push_back(0); pre.push_back(0);
    f(i, 1, n - 1) {
        pii cur = (*(--s.end())); s.erase(--s.end());
        pii urc = (*(--s.end())); s.erase(--s.end());
    //    cout << cur.second << " " << urc.second << endl;
        nsum += cur.second; ch[cur.second] ++; nsum %= n; 
        ts.push_back((urc.second - cur.second + n) % n); pre.push_back(cur.second);
        if(cur.first > 1) {s.insert({cur.first - 1, cur.second});}
        if(urc.first > 1) {s.insert({urc.first - 1, urc.second});} 
    }
    pii nct = (*(--s.end()));// cout << nct.second << endl; //assert(s.size() == 1);
    nsum += nct.second; ch[nct.second] ++; nsum %= n; //cout << nsum << endl;
    int tar = (n - nsum) % n;

 //   cout << tar << endl;
    bs[0] = 1; tim[0] = 0;
    cp.insert(0); f(i, 1, n - 1) uc.insert(i);
    int now = 1;
    while(!bs[tar]) {
  //  f(i, 0, 3) {   
        int t = bin((*(cp.begin())), (*(uc.begin())), ts[now]);
        uc.erase(t); cp.insert(t);
        bs[t] = 1; tim[t] = now;
        
     //   cout <<"add : "<< t << endl;
     //   tim[t] = now; 
        now++;
 //   }
    }

    int ncp = tar; int ccp = tim[ncp];
    while(1) {
        if(ccp <= 0) break;
     //   cout << ncp << " " << ccp << endl;
        ch[pre[ccp]] --; ch[(pre[ccp] + ts[ccp]) % n] ++;
        ncp = (ncp - ts[ccp] + n) % n; ccp = tim[ncp];
    }
    f(i, 0, n - 1) { f(j, 0, ch[i] - 1) cout << v[i][j] << " ";}
    cout << endl;
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

[WC2023] 楼梯

也可以用中值定理优化 \(O(n^2)\) 的找区间子区间的过程,但是这题直接保证了 \(q|p\)

posted @ 2023-01-20 11:01  OIer某罗  阅读(48)  评论(0编辑  收藏  举报