【总结】「ROIR 2020」
比较简单的比赛。
T1
直接平方差得到 \((x+y)(x-y) = n\)。
所以我们将 \(n\) 拆成两个数 \(a,b\) 的乘积,然后 \(x=\frac{a+b}{2},y=\frac{a-b}{2}\)。
如果 \(n\) 为奇数,直接拆成 \(n\times 1\)。
如果 \(n\) 为偶数,且 \(\frac{n}{2}\) 也是偶数,就拆成 \(\frac{n}{2}\times 2\),否则无解。
int main() {
//int T = read();while(T--)solve();
LL n = Read();
if(n == 0)cout << "YES\n1 1\n";
else if(n == 1 || n == 4)cout << "NO\n";
else if(n & 1)cout << "YES\n" << (n + 1) / 2 << " " << (n - 1) / 2 << "\n";
else{
if((n / 2) & 1)cout << "NO\n";
else {
LL w = n / 2;
cout << "YES\n" << (w + 2) / 2 << " " << (w - 2) / 2 << "\n";
}
}
return 0;
}
T2
\(n\) 小 \(m\) 大,显然二分 \(v\) 的值即可。时间复杂度 \(\mathcal{O}(qn\log m)\)。
#define N 200005
int n, m, q, v[11], len[11], a[N], f[N];
int main(){
scanf("%d", &n);
rep(i, 1, n)scanf("%d", &v[i]);
rep(i, 1, n)scanf("%d", &len[i]);
scanf("%d", &m);
rep(i, 1, m - 1)scanf("%d", &a[i]);
rep(i, 1, m)scanf("%d", &f[i]);
scanf("%d", &q);
while(q--){
int x, y;
scanf("%d%d", &y, &x);
x -= y;
int l = 0, r = m - 1, ed = m;
while(l <= r){
int mid = (l + r) >> 1;
double sum = 0;
rep(i, 1, n)sum += 1.00 * len[i] / (v[i] + a[mid]);
if(sum <= x)ed = mid, r = mid - 1;
else l = mid + 1;
}
cout << f[ed] << endl;
}
return 0;
}
T3
对每种相同颜色算贡献。
对于两个同色位置 \(i,j\),如果不存在 \(i<k<j\) 使得 \(k\) 与之颜色相同,那么所有包含 \((i,j)\) 的区间的答案都要 \(-1\)。
由于有 \(\mathcal{O}(N^2)\) 个区间,但是我们只用统一计算长度相同的区间,所以直接差分即可。线性复杂度。
#define N 200005
int n, a[N], b[N], c[N];LL p[N], sz[N], w[N];
int main(){
scanf("%d", &n);
rep(i, 1, n)scanf("%d", &a[i]), b[i] = a[i];
sort(b + 1, b + n + 1);
int T = unique(b + 1, b + n + 1) - b - 1;
rep(i, 1, n)a[i] = lower_bound(b + 1, b + T + 1, a[i]) - b;
auto ins = [=](int l,int r){
if(l > n - r + 1)swap(l, r), l = n - l + 1, r = n - r + 1;
int p1 = r - l + 1, p2 = n - l + 1;
sz[p1] ++, w[p1] += p1 - 1, sz[r] --, w[r] -= p1 - 1;
w[r] -= l, w[p2] += l, p[p2] ++;
};
rep(i, 1, n){
if(c[a[i]])ins(c[a[i]], i);
c[a[i]] = i;
}
rep(i, 1, n){
w[i] += w[i - 1], sz[i] += sz[i - 1], p[i] += p[i - 1];
printf("%lld ", (i - p[i]) * (n - i + 1) + w[i] - sz[i] * i);
}
return 0;
}
T4
题面看起来比较繁琐,但也不是不可做。
根据题意每一行都是一颗树,且任意非叶子节点都有两个儿子,所以我们先把表达式树建出来。
首先我们先把每个位置都填上 \(0\),显然每一行答案都是 \(0\)。接着我们依次将一个 \(0\) 变成 \(1\),由于每变动一次,答案最多只会 \(+1\),所以一定会经过 \(s\)。
所以我们在表达式树上均摊搞一搞即可,时间复杂度 \(\mathcal{O}(NM)\)。
#define N 300005
#define pb push_back
int n, m, s, z[N];
vector<int>f[N], op[N], mat[N], c[N];
int read(){
int x = 0; char ch = getchar();
while(ch > '9' || ch < '0')ch = getchar();
while(ch <= '9' && ch >= '0')x = (x << 1) + (x << 3) + (ch - '0'), ch = getchar();
return x;
}
int main(){
n = read(), m = read(), s = read();
int sum = 0;
rep(i, 0, m - 1){
rep(j, 1, n)f[i].pb(0), op[i].pb(0), c[i].pb(0);
rep(j, 1, n - 1){
int x = read() - 1, y = read() - 1, z = read() - 1;
f[i].pb(0), op[i].pb(z), c[i].pb(0);
f[i][x] = f[i][y] = j + n - 1;
sum += z;
}
}
rep(i, 0, n - 1)mat[i].resize(m);
rep(i, 0, m - 1)rep(j, 0, n - 1)mat[j][read()] = i;
auto del = [](int x,int y){
bool fg = y == 1;
while(y != n + n - 2){
c[x][y] = 1;
if(c[x][f[x][y]] == 1)break;
if(op[x][f[x][y]] == 1)y = f[x][y];
else{
if(c[x][f[x][y]] == 0){c[x][f[x][y]] = ~0;break;}
else y = f[x][y];
}
}
if(y == n + n - 2)c[x][y] = 1, s --;
};
if(s){
rep(i, 0, n - 1){
rep(j, 0, m - 1){
z[i]++, del(mat[i][j], i);
if(!s)break;
}
if(!s)break;
}
}
rep(i, 0, n - 1)printf("%d ", z[i]);
return 0;
}
T5
根据均值不等式,左右差最小时乘积最大,线性扫一遍即可。
int n;long long a[N];
int main(){
n = read();
rep(i, 1, n)a[i] = read() + a[i - 1];
auto w = [](int x){return abs(a[x] - (a[n] - a[x]));};
int ed = 1;
rep(j, 2, n - 1)if(w(j) < w(ed))ed = j;
cout << ed << endl;
return 0;
}
T6
很神奇一数学题。
由于 \(ab-(a-1)(b-1) = a+b-1=n\),所以 \(a+b \le n + 1\),每个数的范围都是 \(3000\) 之内。
如果直接枚举 \(a,b\),再枚举一个 \(c\) 就能算出来,本以为是 \(\mathcal{O}(N^3)\),没想到剪枝后跑的飞快。
但还是写了个奇怪的东西,我们枚举 \(a,c\),然后扩欧解二元一次方程 \(ab-cd=n\) 即可。
int n, x;
int exgcd(int a,int b,int &x,int &y){
if(!b){x = 1, y = 0; return a;}
int xx = 0, yy = 0;
int g = exgcd(b, a % b, xx, yy);
x = yy, y = xx - a / b * yy; return g;
}
int main(){
scanf("%d%d", &n, &x);
int ans = 0;
rep(a, 2, n)if(a != x)rep(c, 1, a - 1){
int b = 0, d = 0;
int g = exgcd(a, c, b, d);
if(n % g)continue;
int k = n / g, lb = c / g, ld = a / g;
b *= k, b = (b % lb + lb) % lb;
d = (n - a * b) / c;
while(b > -d){
if(d < 0 && b != x)ans++;
b += lb, d -= ld;
}
}
cout << ans << endl;
return 0;
}
T7
先考虑没有 \(b\) 的限制怎么做。
显然我们可以从小到大递推,\(f_i\) 表示 \(<a_i\) 的数中答案最大的数,\(g_i\) 表示对应的答案,一遍递推即可。
如果有 \(b\),我们二分出小于等于 \(b\) 的最大的 \(a_i\),先加上 \(f_i\),剩下的加 \(a_i\)。
#define N 200005
int n, m;LL a[N], u[N], v[N];
int main(){
scanf("%d", &n);
rep(i, 1, n)scanf("%lld", &a[i]);
rep(i, 1, n - 1){
LL p = (a[i + 1] - 1 - u[i]) / a[i];
u[i + 1] = u[i] + p * a[i], v[i + 1] = v[i] + p;
}
scanf("%d", &m);
while(m--){
LL s; scanf("%lld", &s);
int p = upper_bound(a + 1, a + n + 1, s) - a - 1;
LL w = (s - u[p]) / a[p];
printf("%lld %lld\n", u[p] + w * a[p], w + v[p]);
}
return 0;
}
T8
线段树,状态用 \(f_{l,r}\) 表示最左边选了 \(l\) 个,最右边选了 \(r\) 个,然后合并即可。
#define N 40005
int n, u[N];
inline void cmx(LL &x,LL y){if(x < y)x = y;}
struct node{
LL a[4][4]; int len;
node(){memset(a, 0xcf, sizeof(a));}
node operator+(node o){
node cur; cur.len = len + o.len;
rep(l, 0, 3)rep(r, 0, 3)rep(p, 0, 3)rep(q, 0, 3 - p){
int x = l + (l == len) * q, y = r + (r == o.len) * p;
if(x <= 3 && y <= 3)cmx(cur.a[x][y], a[l][p] + o.a[q][r]);
}
return cur;
}
};
struct Node{
int l, r; node sum;
}a[N << 2];
#define L a[x].l
#define R a[x].r
#define ls (x << 1)
#define rs (ls | 1)
#define S a[x].sum
void build(int x,int l,int r){
L = l, R = r;
if(l == r){
S.len = 1;
S.a[0][0] = 0, S.a[1][1] = u[l];
return ;
}
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
S = a[ls].sum + a[rs].sum;
}
void ins(int x,int pos,int val){
if(L == R)S.a[1][1] = val;
else{
int mid = (L + R) >> 1;
if(mid >= pos)ins(ls, pos, val);
else ins(rs, pos, val);
S = a[ls].sum + a[rs].sum;
}
}
int main(){
scanf("%d", &n);
rep(i, 1, n)scanf("%d", &u[i]);
auto out = [](){
LL ans = 0;
rep(l, 0, 3)rep(r, 0, 3 - l)cmx(ans, a[1].sum.a[l][r]);
printf("%lld\n", ans);
};
int q;scanf("%d", &q);
build(1, 1, n), out();
while(q--){
int x, y;
scanf("%d%d", &x, &y);
ins(1, x, y), out();
}
return 0;
}