【总结】IOI2020 Day1
T1: [IOI2020]植物比较
难度很高的思维题。
构造出一种可行方案满足 \(r\) 的限制是不难的。\(r\) 中存在若干个 \(0\),如果 \(r_i = 0\) 表示它后面 \(k-1\) 个数都比它小,所以我们找到一个 \(0\),它前面 \(k-1\) 个位置都不为 \(0\),那么它就是整个序列的最大值。将最大值删掉归纳下去即可。
当 \(2k>n\) 时满足条件的 \(0\) 只有 \(1\) 个,当 \(2k\le n\) 时可能有多个,但它们之间距离都 \(\ge k\),也就意味着它们没有直接比较,谁更大一点没有区别。
这样我们就解决了 Sub2,3。对于 Sub 4,由于没有 \(Ans = 0\) 的情况,我们构造出的排列的大小关系就是答案。
回到 Sub1,\(k=2\) 表示邻项比较。所以 \(i,j\) 能比较大小当且仅当 \(a_i>a_{i+1}>\cdots>a_{j}\) 或者 \(a_i>a_{i-1}>\cdots>a_{j}\) 或者 \(a_i<a_{i+1}<\cdots<a_{j}\) 或者 \(a_i<a_{i-1}<\cdots<a_{j}\)。也就是存在一条链,可以直接 \(O(N)\) 预处理每条链左右端点即可。
那么对于一般情况,能够比较大小关系就是存在 \(a_{i}\to a_{k_1}\to \cdots\to a_{k_e}\to a_{j}\) 的关系,当 \(|i-j|<k\) 能够直接比较大小关系,所以我们从点 \(i\) 开始,每次在后面 \(k-1\) 个数中找最大的小于自己的数/最小的大于自己的数,一直往后跳,直到点 \(j\) 的 \(k\) 范围以内。如果能跳到则能比较大小关系,否则答案为 \(0\)。
对于跳的过程我们可以倍增优化。
#define N 200005
int n, m, k, u[N], w[N];
#define L a[x].l
#define R a[x].r
#define ls (x << 1)
#define rs (ls | 1)
#define S a[x].val
#define T a[x].tag
struct seg{
struct node{int l, r, val, tag;}a[N << 2];
void pushup(int x,int val){T += val, S += val;}
void down(int x){if(T)pushup(ls, T), pushup(rs, T), T = 0;}
void build(int x,int l,int r){
L = l, R = r, S = inf;
if(l == r)return;
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
}
void add(int x,int l,int r,int val){
if(l > r)return;
if(L >= l && R <= r)pushup(x, val);
else{
down(x); int mid = (L + R) >> 1;
if(mid >= l)add(ls, l, r, val);
if(mid < r)add(rs, l, r, val);
S = min(a[ls].val, a[rs].val);
}
}
void ins(int x,int pos,int op){
if(L == R)S += op;
else{
down(x); int mid = (L + R) >> 1;
if(mid >= pos)ins(ls, pos, op); else ins(rs, pos, op);
S = min(a[ls].val, a[rs].val);
}
}
int ask(int x){if(L == R)return L; down(x); return a[ls].val == 0 ? ask(ls) : ask(rs);}
void calc(int x,int op){
ins(1, x, -op * inf);
if(x + k <= n)add(1, x + 1, x + k - 1, op);
else add(1, x + 1, n - 1, op), add(1, 0, k - 1 - (n - x), op);
}
}c;
struct Seg{
struct node{int l, r, val, tag;}a[N << 2];
void pushup(int x,int val){T += val, S += val;}
void down(int x){if(T)pushup(ls, T), pushup(rs, T), T = 0;}
void build(int x,int l,int r){
L = l, R = r;
if(l == r)S = u[l];
else{
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
S = min(a[ls].val, a[rs].val);
}
}
void add(int x,int l,int r,int val){
if(L >= l && R <= r)pushup(x, val);
else{
down(x); int mid = (L + R) >> 1;
if(mid >= l)add(ls, l, r, val);
if(mid < r)add(rs, l, r, val);
S = min(a[ls].val, a[rs].val);
}
}
void del(int x){
if(x >= k - 1)add(1, x - k + 1, x, -1);
else add(1, 0, x, -1), add(1, n - (k - 1 - x), n - 1, -1);
}
void maintain(int x){
if(S)return;
if(L == R)c.calc(L, 1), S = inf;
else down(x), maintain(ls), maintain(rs), S = min(a[ls].val, a[rs].val);
}
}a;
int f[2][N][18], g[2][N][18], t; set<Pr>s;
bool ckf(int x,int y){
int l = y < x ? x - y : x - y + n, p = 0;
int r = y > x ? y - x : y - x + n, q = 0;
pre(i, t, 0){
if(p + f[0][(x - p + n) % n][i] <= l - k) p += f[0][(x - p + n) % n][i];
if(q + f[1][(x + q) % n][i] <= r - k)q += f[1][(x + q) % n][i];
}
if(p + k <= l)p += f[0][(x - p + n) % n][0];
if(q + k <= r)q += f[1][(x + q) % n][0];
if(p + k > l && w[(x - p + n) % n] < w[y])return true;
if(q + k > r && w[(x + q) % n] < w[y])return true;
return false;
}
bool ckg(int x,int y){
int l = y < x ? x - y : x - y + n, p = 0;
int r = y > x ? y - x : y - x + n, q = 0;
pre(i, t, 0){
if(p + g[0][(x - p + n) % n][i] <= l - k) p += g[0][(x - p + n) % n][i];
if(q + g[1][(x + q) % n][i] <= r - k)q += g[1][(x + q) % n][i];
}
if(p + k <= l)p += g[0][(x - p + n) % n][0];
if(q + k <= r)q += g[1][(x + q) % n][0];
if(p + k > l && w[(x - p + n) % n] > w[y])return true;
if(q + k > r && w[(x + q) % n] > w[y])return true;
return false;
}
int compare_plants(int x,int y){
if(ckf(x, y))return -1;
if(ckg(x, y))return 1;
return 0;
}
void init(int K, vector<int> r){
k = K, n = si(r);
rep(i, 0, n - 1)u[i] = r[i];
a.build(1, 0, n - 1), c.build(1, 0, n - 1);
pre(i, n - 1, 0){
a.maintain(1); int x = c.ask(1);
c.calc(x, -1), a.del(x), w[x] = i;
}
rp(i, n + n){
if(i < k)s.insert(mp(w[i % n], i));
else{
auto cur = s.upper_bound(mp(w[i % n], 0));
if(cur == s.end())f[0][i % n][0] = 0; else f[0][i % n][0] = i - (*cur).se;
if(cur == s.begin())g[0][i % n][0] = 0; else g[0][i % n][0] = i - (*--cur).se;
s.insert(mp(w[i % n], i)), s.erase(mp(w[(i - k + 1) % n], i - k + 1));
}
}s.clear();
pr(i, n + n){
if(i + k > n + n + 1)s.insert(mp(w[i % n], i));
else{
auto cur = s.upper_bound(mp(w[i % n], 0));
if(cur == s.end())f[1][i % n][0] = 0; else f[1][i % n][0] = (*cur).se - i;
if(cur == s.begin())g[1][i % n][0] = 0; else g[1][i % n][0] = (*--cur).se - i;
s.insert(mp(w[i % n], i)), s.erase(mp(w[(i + k - 1) % n], i + k - 1));
}
}
t = log2(n);
rp(j, t)rep(i, 0, n - 1)
f[0][i][j] = min(inf, f[0][i][j - 1] + f[0][(i - f[0][i][j - 1] % n + n) % n][j - 1]),
g[0][i][j] = min(inf, g[0][i][j - 1] + g[0][(i - g[0][i][j - 1] % n + n) % n][j - 1]),
f[1][i][j] = min(inf, f[1][i][j - 1] + f[1][(i + f[1][i][j - 1]) % n][j - 1]),
g[1][i][j] = min(inf, g[1][i][j - 1] + g[1][(i + g[1][i][j - 1]) % n][j - 1]);
}
T2:[IOI2020]连接擎天树
较为简单的构造,显然满足条件的只有森林和基环树森林,简单讨论一下即可构造。
#include<vector>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 1005
using std::vector;
typedef vector<vector<int> > vv;
int get(int *u,int x){return u[x]==x?x:u[x]=get(u,u[x]);}
vector<int>c[N];
void build(vector<vector<int> > b);
int construct(vv p){
vv e;int n=p.size(),fa[N],ga[N];
e.resize(n);rep(i,0,n)fa[i]=ga[i]=i;
rep(i,0,n-1)e[i].resize(n);
rep(i,0,n-1)rep(j,i+1,n-1)if(p[i][j]==3)return 0;
rep(i,0,n-1)rep(j,i+1,n-1)if(p[i][j]==1)fa[get(fa,i+1)]=get(fa,j+1);
rep(i,0,n-1)rep(j,i+1,n-1)if(p[i][j]==2){
int l=get(fa,i+1),r=get(fa,j+1);
if(l == r)return 0;
ga[get(ga,l)] = get(ga,r);
}
rep(i,0,n-1)rep(j,i+1,n-1)
if(!p[i][j]&&(get(fa,i+1)==get(fa,j+1)||get(ga,get(fa,i+1))==get(ga,get(fa,j+1))))return 0;
rep(i,1,n)if(fa[i]!=i)e[i-1][get(fa,i)-1]=e[get(fa,i)-1][i-1]=1;else c[get(ga,i)].push_back(i-1);
rep(i,1,n)if(c[i].size()>1){
if(c[i].size()==2)return 0;
int m=c[i].size();
rep(j,0,m-2)e[c[i][j]][c[i][j+1]]=e[c[i][j+1]][c[i][j]]=1;
e[c[i][0]][c[i][m-1]]=e[c[i][m-1]][c[i][0]]=1;
}
build(e);return 1;
}
T3:[IOI2020]嘉年华奖券
有一定难度的贪心题。
先考虑 Sub1,对于商家的策略,一定是选择中位数最优。所以等价于将 \(n\) 个数,一半取负号,一半取正号的最优值,直接排序即可。
再考虑 Sub2,每行选择一个数,一半取正一半取负的最优值。如果取正则一定是这一行的最大值,否则是最小值。先每一行都取负,然后选择一半取正,直接按最大值和最小值的和排序即可。
对于 Sub3,就是考虑 0/1 匹配方案使得 \(0\) 和 \(1\) 的匹配尽量多。直接贪心,每次选择 \(1\) 最多的 \(\dfrac{n}{2}\) 行选 \(1\) 取正,剩下的行选 \(0\) 取负。
这启发了 Sub4 的解法,如果所有数都要选,那么对于最大的 \(\dfrac{nm}{2}\) 个数取正,剩下的取负,然后问题就变成了 \(0/1\) 匹配,将所有行按所剩正数个数排序,每一轮前面一半的正数和后面一半的负数匹配即可构造出最优解。因为每一轮 \(1\) 的个数都是 \(size\) 的一半,所以不存在 \(>\dfrac{n}{2}\) 行每行都是 \(1\)。
前面子任务的分析已经将问题解决了,对于所有数据,我们先按 Sub2 的贪心,选出 \(\dfrac{nk}{2}\) 个数使得较大的一半减去较小的一半最大,然后用 Sub4 的方法构造方案。
时间复杂度 \(\mathcal{O}(nm\log n)\)。
#define N 1505
int n, m, k, p[N], u[N][N]; Pr a[N][N]; LL ans = 0;
priority_queue<Pr>q; Pr c[N];
LL find_maximum(int K, std::vector<std::vector<int>> w){
k = K, n = si(w), m = si(w[0]);
//read(n, m, k);
rp(i, n){
p[i] = 0;
rp(j, m)a[i][j].fi = w[i - 1][j - 1], a[i][j].se = j;
sort(a[i] + 1, a[i] + m + 1);
rp(j, k)ans -= a[i][j].fi;
q.push(mp(a[i][m].fi + a[i][k].fi, i));
}
pr(i, n * k / 2){
ans += q.top().fi; int x = q.top().se; q.pop();
p[x]++;
if(p[x] < k)q.push(mp(a[x][m - p[x]].fi + a[x][k - p[x]].fi, x));
}
memset(u, ~0, sizeof(u));
pr(id, k){
rp(i, n)c[i] = mp(p[i], i);
sort(c + 1, c + n + 1);
rp(i, n / 2){
int x = c[i].se;
u[x][a[x][id - p[x]].se] = id - 1;
x = c[n - i + 1].se;
u[x][a[x][m - (--p[x])].se] = id - 1;
}
}
std::vector<std::vector<int>> s(n);
rep(i, 0, n - 1)s[i].resize(m);
rp(i, n)rp(j, m)s[i - 1][j - 1] = u[i][j];
allocate_tickets(s);
return ans;
}