2019牛客多校第一场 2019牛客多校第一场

D还没补,G看起来做不了

A Equivalent Prefixes

solved at 00:21

题意是有两个长为n的数组a,b,每个数组都是1到n的一个排列

询问一个最长的前缀p,使得对于任意的(1 <= l <= r <= q),都有(min\_element(a, l, r) = min\_element(b, l, r))(最小值的位置对应相等,而非值相等)

首先显然(p=1)时满足

(p=i)满足且(pa[i+1]=pb[i+1]),则(p=i+1)也满足

其中(pa[i] = max_{a[j] < a[i] && j < i}(j)),即比a[i]左边第一个比a[i]小的数的位置,(pb)类似

证明如下:

(p=i)满足,则对于(r<i+1), (min\_element(a, l, r) == min\_element(b, l, r))

对于(r=i+1),若(l>pa[i+1]),则(min\_element(a, l, r)=r=i+1)

(l<=pa[i+1])

(min\_element(a, l, r)!=r), (min\_element(a, l, r)=min\_element(a, l, r-1)=min\_element(a, l, i))

(pa[i+1]=pb[i+1])(p=i+1)也满足

(pa, pb)可以用单调栈线性处理出来,总时间复杂度为(O(n))

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int a[N], b[N], n, pa[N], pb[N];


int main() {
    while(~scanf("%d", &n)) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &b[i]);
        }
        a[0] = b[0] = -1e9;
        stack<int> sa, sb;
        sa.push(0); sb.push(0);
        for(int i = 1; i <= n; ++i) {
        	while(!sa.empty() && a[sa.top()] > a[i]) sa.pop();
        	pa[i] = sa.top();
   	     	sa.push(i);
   			while(!sb.empty() && b[sb.top()] > b[i]) sb.pop();
        	pb[i] = sb.top();
        	sb.push(i);
        }
        int p = 1;
        for(int i = 1; i <= n; ++i) {
        	if(pa[i] == pb[i])
        		p = i;
        	else
        		break;
        }
        printf("%d
", p);
    }
    return 0;
}

B Integration

upsolved

题意就是求

[frac {1}{pi}int_0^{+infty}frac 1 {prod_{i=1}^{n}(a_i^2+x^2)}{ m d}x ]

(1<=n<=1000)

并不会数学,照着题解实现的

令$$c_i=frac 1 {prod_{(j eq i)}(a_j2-a_i2)}$$

则$$frac 1 {prod(a_i2+x2)} = sum frac {c_i} {a_i2+x2}$$

而$$int_0^{+infty}frac {c_i} {a_i2+x2} { m d}x = frac {c_i} {2a_i} pi$$

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e3 + 10, mod = 1e9 + 7;

int a[N], n;
LL ans;

LL qp(LL a, LL n) {
	LL res = 1;
	while(n) {
		if(n & 1)
			res = res * a % mod;
		a = a * a % mod;
		n >>= 1;
	}
	return res;
}


int main() {
	while(~scanf("%d", &n)) {
		for(int i = 1; i <= n; ++i) 
			scanf("%d", &a[i]);
		ans = 0;
		for(int i = 1; i <= n; ++i) {
			LL q = qp(2 * a[i] % mod, mod - 2), p = 1;
			for(int j = 1; j <= n; ++j) {
				if(i == j) continue;
				p = (((1LL * a[j] * a[j] % mod - 1LL * a[i] * a[i] % mod) % mod) + mod) % mod * p % mod;
			}
			ans = (ans + qp(p, mod - 2) * q % mod) % mod;
		}
		printf("%lld
", ans);
	}
	return 0;
}

C Euclidean Distance

upsolved

你有一个({ m n})维空间中的点(a),(a)的每一维坐标都可以表示为$ {a_i}/ m$

(a_i)是绝对值不大于(m)的整数

你要找到另一个点(p), 其中(p)每一维坐标都是非负实数,且各维坐标和为1

输出(p)(a)的最小欧式距离

题解看不懂,个人是贪心做的

(p)的每一位坐标都表示成(p_i/m),则(p)各维坐标和为(m)

首先对于小于等于(0)(a_i), 将(p_i)设为(0),否则将(p_i)设为(a_i)

然后每次去调整(p_i)使得和为(m), 每次贪心地选取改了(p_i)新增代价最小的位置

注意没有保证(p_i)是整点,最后一次处理时可能是很多数变成同一个小数

另外要注意修改(p_i)使其变小时不能小于(0)

#include <bits/stdc++.h>
using namespace std;

const int N = 1e4 + 10;

int a[N], p[N], n, m, cnt[N], lim[N];
long long x, y, tot, res, g;

long long gcd(long long x, long long y) {
  return y == 0 ? x : gcd(y, x % y);
}

void modify(long long a, long long b, long long c, long long d) {
  x = b * d * x + a * d + b * c;
  y = b * y * d;
}

int main() {
  while(~scanf("%d%d", &n, &m)) {
    y = m * m;
    tot = 0;
    x = 0;
    for(int i = 0; i <= 2*m; ++i)
      cnt[i] = lim[i] = 0;
    for(int i = 1; i <= n; ++i) {
      scanf("%d", &a[i]);
      if(a[i] < 0) {
        p[i] = 0;
        x += 1LL * a[i] * a[i];
      }
      else {
        tot += a[i];
        p[i] = a[i];
      }
    }
    if(tot < m) {
      res = m - tot;
      for(int i = 1; i <= n; ++i) {
        if(a[i] < 0)
          cnt[-a[i]]++;
        else
          cnt[0]++;
      }
      for(int i = 0; i <= 2*m; ++i) {
        if(i)
          cnt[i] += cnt[i - 1];
        if(cnt[i] >= res) {
          modify(res * res, 1LL * cnt[i], res * i * 2LL, 1);
          break;
        }
        else {
          res -= cnt[i];
          x += cnt[i] * 1LL * ((i + 1) * (i + 1) - (i * i));
        }
      }
    }
    else if(tot > m) {
      res = tot - m;
      for(int i = 1; i <= n; ++i) {
        if(a[i] > 0) {
          lim[a[i]]++;
          cnt[0]++;
        }
      }
      for(int i = 0; i <= 2*m; ++i) {
        if(i)
          cnt[i] += cnt[i - 1];
        cnt[i] -= lim[i];
        if(cnt[i] >= res) {
          modify(res * res, 1LL * cnt[i], res * i * 2LL, 1LL);
          break;
        }
        else {
          res -= cnt[i];
          x += cnt[i] * 1LL * ((i + 1) * (i + 1) - (i * i));
        }
      }
    }
    g = gcd(x, y);
    x /= g;
    y /= g;
    printf("%lld", x);
    if(y != 1)
      printf("/%lld", y);
    printf("
");
  }
  return 0;
}

E ABBA

solved at 00:42

你有一个长为(2*(n+m))的字符串,每个位置填上A或B, 问你有多少个这样的字符串使得最终能提取出(n)个"AB"子序列和(m)个"BA"子序列

dp即可,(dp[i][j])表示前(i)个位置用了(j)个A的方案数

通过(n, m)来计算当前位置最多已经用了多少个A

#include <bits/stdc++.h>
using namespace std;

const int N = 1010, mod = 1e9 + 7;

int dp[N << 2][N << 1], n, m;

int main() {
	while(~scanf("%d%d", &n, &m)) {
		for(int i = 0; i <= 2 * (n + m); ++i) {
			for(int j = 0; j <= n + m; ++j)
				dp[i][j] = 0; 
		}
		if(n == 0 && m == 0) {
			puts("0");
			continue;
		}
		if(n != 0)
			dp[1][1] = 1;
		if(m != 0)
			dp[1][0] = 1;
		for(int i = 1; i < 2 * (n + m); ++i) {
			for(int j = 0; j <= i && j <= n + m; ++j) {
				if(j + 1 <= n || j + 1 <= n + (i + 1 - n) / 2) {
					dp[i + 1][j + 1] = (dp[i + 1][j + 1] + dp[i][j]) % mod;
				}
				if(i + 1 - j <= m || i + 1 - j <= m + (i + 1 - m) / 2) {
					dp[i + 1][j] = (dp[i + 1][j] + dp[i][j]) % mod;
				}
			}
		}
		printf("%d
", dp[2 * (n + m)][n + m]);
	}
	return 0;
}

F Random Point in Triangle

solved at 03:02(+43)

是的,44发才过

有一个格点三角形(ABC), 从三角形内随机选一个点(D), 求(ABD, ACD, BCD)三者面积的最大值的期望的36倍(可以证明这是个整数)

答案就是三角形面积的22倍, 队友一直在积分一直炸精度,然后化简之后发现特别简单。。。

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

int main(void){
    long long ax,ay,bx,by,cx,cy, SS;
    while(cin >> ax >> ay >> bx >> by >> cx >> cy){
        SS = abs(bx * cy + ax * by + cx * ay - cx * by - bx * ay - ax * cy);
        cout << SS * 11 << endl;
    }
    return 0;
}

H XOR

upsolved

(n)个数(a_i, 1le a_i le 1e18),你可以任取一个子集,询问子集异或和为零的子集的大小的和

看的大熊软糖队的代码才看懂

可以转为计算每一个数被多少个集合包含

对于每一个数(a_i),构建除了这个数之外的(n-1)个数的线性基,然后往这个线性基里插入(a_i),如果插入失败则(a_i)(2^{n-1-x})个集合包含,(x)为线性基的元素个数,插入成功则(a_i)对答案没有贡献

因为如果插入成功的话说明原来的(n-1)个数不可能有一个子集异或和为(a_i),而插入失败的话,任选其他插入失败的数,线性基里都能选出一个子集异或和与其相等

但是这样做的复杂度是(O(nlog^2(a)))的,会TLE

考虑(n)个数的线性基,记录每个数能否被插入,显然最多只有(log(a))个数被插入,对于这些数合并前缀线性基和后缀线性基用之前的方法做,对于插入失败的数,除了它本身的线性基就是(n)个数的线性基

考虑到要维护后缀或前缀,空间复杂度为(O(nlog(a))),时间复杂度为(nlog(a)+log^3(a))

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e5 + 10, mod = 1e9 + 7;

LL a[N], ans;
int n, p[N], ok[N];

struct Base {
    LL a[63];
    int m;
    Base() {
        memset(a, 0, sizeof(a));
        m = 0;
    }
    bool insert(LL x) {
        for(int i = 62; ~i; --i) {
            if((x >> i) & 1) {
                if(a[i] == 0) {
                    a[i] = x;
                    m++;
                    return 1;
                }
                else {
                    x ^= a[i];
                }
            }
        }
        return 0;
    }
}suf[N];

int main() {
    p[0] = 1;
    for(int i = 1; i < N; ++i)
        p[i] = 2LL * p[i - 1] % mod;
    while(~scanf("%d", &n)) {
        ans = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
        }
        Base s, s2;
        suf[n + 1] = s;
        for(int i = n; ~i; --i) {
            suf[i] = suf[i + 1];
            suf[i].insert(a[i]);
        }
        for(int i = 1; i <= n; ++i) ok[i] = s.insert(a[i]);
        for(int i = 1; i <= n; ++i) {
            if(!ok[i]) ans = (ans + p[n - 1 - s.m]) % mod;
            else {
                Base t = s2;
                for(int j = 0; j < 63; ++j)
                    t.insert(suf[i + 1].a[j]);
                if(!t.insert(a[i])) ans = (ans + p[n - 1 - t.m]) % mod;
            }
            s2.insert(a[i]);
        }
        printf("%lld
", ans);
    }
    return 0;
}

I Points Division

upsloved

二维平面上有很多点,每个点有两个属性(a)(b),你需要将这些点划分成两个集合(A, B),其中(A)中没有点在(B)中任意一个点的右下方(非严格),贡献定义为(A)中点的(a)属性和和(B)中点的(b)属性和的和,求最大贡献

看着题解以及“你以为你CF过了四题”的代码想通的,不得不说cslnb

首先将点按(x)从小到大,(y)从大到小排序,然后将(y)坐标离散化一下(常规操作)

在划分完之后,一定存在一条单调不降的折线使得(A)中的点都在其上方,(B)中的点在线上或者下方

考虑dp, (dp[i][j])代表前(i)个点,(j)为第(i)个点所在横坐标的纵坐标分割线(即折线经过((x_i, j)))时的最大贡献

由于这条折线单调不降的性质

(dp[i + 1][j]_{j=y_i} = max_{k <=j} (dp[i][k]) + b_i)

(dp[i + 1][j]_{1<=j<y_i}=dp[i][j] + a_i)

(dp[i+1][j]_{y_i<j<=tot}=dp[i][j] + b_i)

可以用线段树加速

这也就是为什么要在(x)相同时把(y)大的放前面,否则的话就会导致查询最大值单点更新时会查询到包含同一个横坐标的更低点的(a)值,这是不可能发生的

最后要注意因为有所有点都在(A)里的情况,需要在离散化纵坐标时加入一个极小值

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e5 + 10;

struct Point {
	int x, y, a, b;
	bool operator<(const Point &rhs) const {
		if(x == rhs.x) return y > rhs.y;
		return x < rhs.x;
	}
}p[N];
int n, b[N];
LL lazy[N << 2], mx[N << 2];

void pushup(int rt) {
	mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
}

void pushdown(int rt) {
	if(lazy[rt]) {
		lazy[rt << 1] += lazy[rt];
		mx[rt << 1] += lazy[rt];
		lazy[rt << 1 | 1] += lazy[rt];
		mx[rt << 1 | 1] += lazy[rt];
		lazy[rt] = 0;
	}
}

void build(int rt, int l, int r) {
	mx[rt] = lazy[rt] = 0;
	if(l == r)
		return;
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
}

void update(int rt, int l, int r, int pos, LL val) {
	if(l == r) {
		mx[rt] = val;
		return;
	}
	int mid = l + r >> 1;
	pushdown(rt);
	if(pos <= mid)
		update(rt << 1, l, mid, pos, val);
	else
		update(rt << 1 | 1, mid + 1, r, pos, val);
	pushup(rt);
}

void update(int rt, int l, int r, int L, int R, LL val) {
	if(L <= l && r <= R) {
		mx[rt] += val;
		lazy[rt] += val;
		return;
	}
	pushdown(rt);
	int mid = l + r >> 1;
	if(L <= mid)
		update(rt << 1, l, mid, L, R, val);
	if(R > mid)
		update(rt << 1 | 1, mid + 1, r, L, R, val);
	pushup(rt);
}

LL query(int rt, int l, int r, int L, int R) {
	if(L <= l && r <= R)
		return mx[rt];
	pushdown(rt);
	int mid = l + r >> 1;
	LL ans = -1e18;
	if(L <= mid)
		ans = max(ans, query(rt << 1, l, mid, L, R));
	if(R > mid)
		ans = max(ans, query(rt << 1 | 1, mid + 1, r, L, R));
	return ans;
}

int main() {
	while(~scanf("%d", &n)) {
		for(int i = 1; i <= n; ++i) {
			scanf("%d%d%d%d", &p[i].x, &p[i].y, &p[i].a, &p[i].b);
			b[i] = p[i].y;
		}
		sort(p + 1, p + n + 1);
		b[n + 1] = 0;
		sort(b + 1, b + n + 2);
		int m = unique(b + 1, b + n + 2) - b - 1;
		build(1, 1, m);
		for(int i = 1; i <= n; ++i) {
			int y = lower_bound(b + 1, b + m + 1, p[i].y) - b;
			LL r = query(1, 1, m, 1, y);
			update(1, 1, m, y, r + p[i].b);
			if(y > 1) {
				update(1, 1, m, 1, y - 1, p[i].a);
			}
			if(y < m) {
				update(1, 1, m, y + 1, m, p[i].b);
			}
		}
		printf("%lld
", query(1, 1, m, 1, m));
	}
	return 0;
}

J Fraction Comparision

solved at 00:08

给你两个分数(x/a, y/b), 输出其大小关系

py水过

while(True):
    try:
        x, a, y, b = map(int, input().split())
        if(x * b == a * y):
            print("=")
        elif (x * b > a * y):
            print(">")
        else:
            print("<")
    except:
        exit()