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;
}
重要的是自信,一旦有了自信,人就会赢得一切。