2019牛客暑期多校训练营(第九场)

2019牛客暑期多校训练营(第九场)

题目链接

A.The power of Fibonacci

注意到模数为合数,并且可以拆为\(2^9,5^9\),这样就相当于将原问题拆解成了规模比较小的情况。
通过\(2^9,5^9\)分别求出循环节,找到问题的解,之后\(CRT\)合并即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct CRT{
    void exgcd(ll a, ll b, ll &g, ll &x, ll &y) {
        if(b == 0) {
            x = 1, y = 0, g = a;
            return ;
        }
        exgcd(b, a % b, g, x, y);
        int t = x;
        x = y;
        y = t - (a / b) * y;
    }
    ll china(ll m[], ll a[], int n) {
        ll M, Mi, d, X, Y, ans;
        M = 1; ans = 0;
        for(int i = 1; i <= n; i++) M *= m[i];
        for(int i = 1; i <= n; i++) {
            Mi = M / m[i];
            exgcd(Mi, m[i], d, X, Y);
            ans = (ans + Mi * X * a[i]) % M;
        }
        if(ans < 0) ans += M;
        return ans;
    }
}crt;
const int MOD = 1000000000, N = 8000005;
ll md[3] = {0, 512, 1953125};
ll f[N];
ll sum1[N], sum2[N];
ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
ll n, m;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    f[0] = 0; f[1] = f[2] = 1;
    sum1[1] = sum2[1] = 1; sum1[2] = sum2[2] = 2;
    int p1, p2;
    for(int i = 3;; i++) {
        f[i] = (f[i - 2] + f[i - 1]) % md[1];
        sum1[i] = (sum1[i - 1] + qp(f[i], m)) % md[1];
        if(f[i - 1] == 1 && f[i] == 0) {
            p1 = i; break;
        }
    }
    for(int i = 3;; i++) {
        f[i] = (f[i - 2] + f[i - 1]) % md[2];
        sum2[i] = (sum2[i - 1] + qp(f[i], m)) % md[2];
        if(f[i - 1] == 1 && f[i] == 0) {
            p2 = i; break;
        }
    }
    ll a[3];
    a[1] = (n / p1) * sum1[p1 - 1] % MOD; a[1] += sum1[n % p1];
    a[2] = (n / p2) * sum2[p2 - 1] % MOD; a[2] += sum2[n % p2];
    ll ans = crt.china(md, a, 2);
    cout << ans;
    return 0;
}

B.Quadratic equation

首先将问题转化为在模意义下求解二次方程。
方程为:\(x^2-bx+c\equiv 0(\% p)\),配方法将式子化为:\((x-\frac{b}{2})^2\equiv \frac{b^2}{4}-c(\% p)\)
之后就是二次剩余板子了。
这里有个结论比较方便,就是当\(p\% 4=3\)时,方程\(x^2\equiv d(\% p)\)的解为\(x=\pm d^{\frac{p+1}{4}}\)

Code
#include <bits/stdc++.h>
#define rg register
#define il inline
#define co const
using namespace std;
typedef long long ll;
const int P = 1000000007, inv = 500000004;
ll x, y, b, c;
int T;
template<class T>il T read(){
    rg T data=0,w=1;
    rg char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=data*10+ch-'0',ch=getchar();
    return data*w;
}
template<class T>il T read(rg T&x){
    return x=read<T>();
}
ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % P;
        a = a * a % P;
        b >>= 1;
    }
    return ans;
}
ll n;
ll add(ll x,ll y){
    return (x+y)%P;
}
ll mul(ll x,ll y){
    return x*y%P;
}
ll Pow(ll x,ll k){
    x%=P,k%=(P-1);
    ll re=1;
    for(;k;k>>=1,x=mul(x,x))
        if(k&1) re=mul(re,x);
    return re;
}
ll omega;
struct Complex{ll a,b;};
Complex operator*(co Complex&x,co Complex&y){
    return (Complex){add(mul(x.a,y.a),mul(x.b,mul(y.b,omega))),add(mul(x.a,y.b),mul(x.b,y.a))};
}
Complex operator^(Complex x,ll k){
    Complex re=(Complex){1,0};
    for(;k;k>>=1,x=x*x)
        if(k&1) re=re*x;
    return re;
}
ll Sqrt(ll n){
    if(P==2) return 1;
    n%=P;
    if(!n) return 0;
    ll a=rand()%P;
    while(Pow(add(mul(a,a),P-n),(P-1)/2)!=P-1)
        a=rand()%P;
    omega=add(mul(a,a),P-n);
    return ((Complex){a,1}^(P+1)/2).a;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T;
    srand(time(NULL));
    while(T--) {
        cin >> b >> c;
        ll d = ((b * b - 4 * c) % P + P) % P * qp(4, P - 2) % P;
        //v^2 = d (P P)
        if(d && qp(d, (P - 1) / 2) != 1) {
            cout << -1 << ' ' << -1 << '\n';
        } else {
            ll ans1=Sqrt(d),ans2=P-ans1;
            if(ans1>ans2) std::swap(ans1,ans2);
            x = ans1, y = ans2;
            x = (x + b * inv % P) % P;
            y = (y + b * inv % P) % P;
            if(x > y) swap(x, y);
            cout << x << ' ' << y << '\n';
        }
    }

    return 0;
}

C.Inversions of all permutations

前置技能:高斯二次项系数
这个题是wiki里面的一个推广,求的是任意多重集的情况下的解。
解决方法是从小到大一个一个求,并且每次将答案乘起来。
可以类比一下普通生成函数,意义比较类似。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005, MOD = 1000000007;
int n;
ll b;
set <int> s;
int cnt[N];
ll sum[N], pre[N];
ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
ll C(int n, int m) {
    if(m > n) return 0;
    return pre[n] * qp(pre[m], MOD - 2) % MOD * qp(pre[n - m], MOD - 2) % MOD;
}
void init(ll q) {
    int x = 1; pre[0] = 1;
    for(int i = 1; i < N; i++) {
        sum[i] = (sum[i - 1] + x) % MOD;
        x = x * q % MOD;
    }
    for(int i = 1; i < N; i++) {
        pre[i] = pre[i - 1] * sum[i] % MOD;
    }
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> b;
    for(int i = 1; i <= n; i++) {
        int a; cin >> a;
        cnt[a]++;
        s.insert(a);
    }
    init(b);
    ll ans = 1;
    int tot = 0;
    for(auto it : s) {
        ans *= C(tot + cnt[it], cnt[it]); ans %= MOD;
        tot += cnt[it];
    }
    cout << ans;
    return 0;
}

D.Knapsack Cryptosystem

分两半处理,首先预处理一半的答案,然后枚举另外一半。

Code
#include <bits/stdc++.h>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 5, INF = 0x3f3f3f3f, MOD = 1e9 + 7;
const ll inf = 1000000000000000010LL, INFL = 0x3f3f3f3f3f3f3f3f;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
 
int n;
ll m, a[MAXN], sum;
unordered_map<ll, int> mp;
void print(int x) {
    while (x) {
        cout << (x & 1);
        x >>= 1;
    }
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)cin >> a[i];
    int sz = n / 2, sz2 = n - sz;
    for (int i = 0; i < (1 << sz); i++) {
        sum = 0;
        for (int j = 0; j < sz; j++) {
            if (i >> j & 1)sum += a[j + 1];
        }
        mp[sum] = i;
    }
    for (int i = 0; i < (1 << sz2); i++) {
        sum = 0;
        for (int j = 0; j < sz2; j++) {
            if (i >> j & 1)sum += a[sz + j + 1];
        }
        if (sum < m && mp.count(m - sum)) {
            int x = mp[m - sum];
            for (int j = 0; j < sz; j++) {
                cout << ((x >> j) & 1);
            }
            x = i;
            for (int j = 0; j < sz; j++) {
                cout << ((x >> j) & 1);
            }
            break;
        }
    }
    return 0;
}

E.All men are brothers

开始局面的答案易求。
对于之后每次的连边操作,考虑答案减少的个数。
假设现在已有\(k\)个连通块,我们现在将\(a_i\)\(a_j\)两个连通块相连接,那么答案减少的个数就是\(sz[a_i]*sz[a_j]*C(others,2)\),发现有些连通块会重复选择集合内部的数,所以还要减去那些重复的,我们这里用一个变量维护一下即可。
所以最终答案即为\(sz[a_i]*sz[a_j]*(C(others,2)-sum)\)

Code
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N = 100005, M = 200005;
struct Istream {
	template <class T>
	Istream &operator >>(T &x) {
		static char ch;static bool neg;
		for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
		for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
		x=neg?-x:x;
		return *this;
	}
}fin;

struct Ostream {
	template <class T>
	Ostream &operator <<(T x) {
		x<0 && (putchar('-'),x=-x);
		static char stack[233];static int top;
		for(top=0;x;stack[++top]=x%10+'0',x/=10);
		for(top==0 && (stack[top=1]='0');top;putchar(stack[top--]));
		return *this;
	}

	Ostream &operator <<(char ch) {
		putchar(ch);
		return *this;
	}
}fout;
ll C2(int n) {
    return (ll)n * (n - 1) / 2;
}
ll C3(int n) {
    return (ll)n * (n - 1) * (n - 2) / 6;
}
ll C4(int n) {
    return (ll)n * (n - 1) * (n - 2) * (n - 3) / 24;
}
int sz[N], f[N];
int n, m;
int find(int x) {
    return f[x] == x ? f[x] : f[x] = find(f[x]);
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) sz[i] = 1, f[i] = i;
    ll ans = C4(n), sum = 0;
    fout << ans << '\n';
    for(int i = 1; i <= m; i++) {
        int x, y; cin >> x >> y;
        int fx = find(x), fy = find(y);
        if(fx == fy) {}
        else {
            ll tmp = sum;
            tmp -= C2(sz[fx]) + C2(sz[fy]);
            ans -= (ll)sz[fx] * sz[fy] * (C2(n - sz[fx] - sz[fy]) - tmp);
            sum -= C2(sz[fy]) + C2(sz[fx]);
            sz[fy] += sz[fx];
            f[fx] = fy;
            sum += C2(sz[fy]);
        }
        fout << ans << '\n';
    }
    return 0;
}

H.Cutting Bamboos

因为总切割次数确定,并且每次切割相同的数值,所以在切割\(x\)之后,剩下树枝高度总和是已知的。
最直接的想法就是二分一个高度,然后找多少个树枝高度小于二分值,算上它们的贡献,其余数枝的贡献就为该高度。
问题的关键就是查询区间小于\(mid\)的个数已经其权值和,这可以用主席树解决。
进一步思考,发现二分都可以不用,直接上主席树即可,在主席树上面二分。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int n, q;
int h[N];
ll sum[N];
int rt[N], lc[N * 22], rc[N * 22], tot;
ll sumv[N * 22], sz[N * 22];
void build(int &o, int l, int r) {
    o = ++tot;
    if(l == r) return;
    int mid = (l + r) >> 1;
    build(lc[o], l, mid); build(rc[o], mid + 1, r);
}
int L, R;
void insert(int &o, int last, int l, int r, int v) {
    o = ++tot;
    lc[o] = lc[last]; rc[o] = rc[last];
    sumv[o] = sumv[last] + v; sz[o] = sz[last] + 1;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    if(v <= mid) insert(lc[o], lc[last], l, mid, v);
    else insert(rc[o], rc[last], mid + 1, r, v);
}
double query(int o, int last, int l, int r, double need, int cnt, ll Sum) {
    if(l == r) {
        cnt += sz[lc[o]] - sz[lc[last]];
        return (need - Sum) / (R - L + 1 - cnt);
    }
    int mid = (l + r) >> 1;
    double now = Sum + sumv[lc[o]] - sumv[lc[last]] + 1ll * mid * (R - L + 1 - (cnt + sz[lc[o]] - sz[lc[last]]));
    if(now >= need) return query(lc[o], lc[last], l, mid, need, cnt, Sum);
    else return query(rc[o], rc[last], mid + 1, r, need, cnt + sz[lc[o]] - sz[lc[last]], Sum + sumv[lc[o]] - sumv[lc[last]]);
}
int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> h[i], sum[i] = sum[i - 1] + h[i];
    build(rt[0], 1, 100000);
    for(int i = 1; i <= n; i++) insert(rt[i], rt[i - 1], 1, 100000, h[i]);
    while(q--) {
        double x, y;
        cin >> L >> R >> x >> y;
        double now = (1 - x / y) * (sum[R] - sum[L - 1]);
        printf("%.10f\n", query(rt[R], rt[L - 1], 1, 100000, now, 0, 0));
    }
    return 0;
}

I.KM and M

考虑按位贡献。
那么对于每个二进制位,假设为\(2^B\),其对答案的贡献为\(\sum_{k=0}^n(km>>B\&1)\cdot2^B\)
对于每个二进制位,我们可以用取模的方法得出,上式即为:\(\sum_{k=0}^nkm\%2^{B+1}-km\%2^B\)
现在考虑对\(\sum_{k=0}^{n} km\%2^B\)求解,式子可变换为\(\sum_{k=0}^{n}km-(km>>B<<B)\),表示把二进制高位减去。
之后进一步化简:\(\frac{mk(k+1)}{2}-2^B\sum_{k=0}^{n}\frac{km}{2^B}\)
后面部分类欧即可解决。

Code
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int MOD = 1000000007, inv2 = (MOD + 1) / 2;
ll f(ll n, ll a, ll b, ll c) {
    if(a <= 0) return 0;
    if(a >= c || b >= c) {
        return (n * (n + 1) % MOD * inv2 % MOD * (a / c) % MOD
        + (n + 1) * (b / c) % MOD + f(n, a % c, b % c, c)) % MOD;
    }
    ll m = (a * n + b) / c;
    return (m * n % MOD - f(m - 1, c, c - b - 1, a) + MOD) % MOD;
}
ll calc(ll n, ll m, ll B) {
    ll ans = m * n % MOD * (n + 1) % MOD * inv2 % MOD;
    ll tmp = (1ll << B);
    return ((ans - tmp * f(n, m, 0, tmp) % MOD) + MOD) % MOD;
}
long long n, m;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    ll ans = 0;
    for(int i = 0; i <= 40; i++) if(m >> i & 1) {
        ll ans1 = calc(n, m, i);
        ll ans2 = calc(n, m, i + 1);
        ans = (ans + (ans2 - ans1 + MOD) % MOD) % MOD;
    }
    cout << (long long)ans;
    return 0;
}

J.Symmetrical Painting

首先判断出答案一定位于某处的中线,否则假如答案位于两条中线之间,那么往两边走不会丢失最优解。
那么最直接的思路就是枚举中线计算答案。
但怎么计算面积?
注意到对面积有贡献的线为矩形的上下或中线,那么我们将所有矩形的这三条线拿出来排序。之后从低到高扫描,统计当前位于多少个矩形下半部及上半部,根据这个计算答案即可。
可以维护一个变量表示当前的累加值。
具体见代码:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int MAXN = 3e5 + 5, INF = 0x3f3f3f3f, MOD = 1e9 + 7;
const ll inf = 1000000000000000010LL, INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-9;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
int n, l[MAXN], r[MAXN], m;
inline int sign(db x) {
    if (fabs(x) < eps)return 0;
    return x > eps ? 1 : -1;
}
struct node {
    int type;
    db y;
    bool operator <(const node &rhs)const {
        if (sign(y - rhs.y) == 0)return type > rhs.type;
        return y < rhs.y;
    }
}a[MAXN * 3];
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n; m = n * 3;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
        a[1 + (i - 1) * 3] = { 0, (db)l[i] };
        a[2 + (i - 1) * 3] = { 1, (l[i] + r[i]) / 2.0 };
        a[3 + (i - 1) * 3] = { 2 ,(db)r[i] };
    }
    sort(a + 1, a + 1 + m);
    int cnt1 = 0, cnt2 = 0;
    db ans = 0, sum = 0;
    for (int i = 1; i <= m; i++) {
        if (i > 1) {
            db d = a[i].y - a[i - 1].y;
            sum += cnt1 * d - cnt2 * d;
        }
        if (a[i].type == 0) cnt1++;
        else if (a[i].type == 1)cnt1--, cnt2++;
        else cnt2--;
 
        if (cnt1 < cnt2) {
            ans = max(ans, 2 * sum);
        }
        else {
            db d = a[i + 1].y - a[i].y;
            ans = max(ans, 2 * (sum + cnt1 * d - cnt2 * d));
        }
    }
    cout << (ll)(ans + eps) << '\n';
    return 0;
}
posted @ 2019-08-17 09:51  heyuhhh  阅读(314)  评论(0编辑  收藏  举报