CSP-S模拟12
A. 开挂
排序后从大到小考虑,并查集记录值为 \(val\) 时最小的能放的
这样小的数跳的步数尽量多,\(b\) 排序后倒着乘起来
或者用栈也可以,参考 \(Delov\) 大佬的题解吧
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 1000005;
int n, a[maxn], b[maxn], c[maxn];
unordered_map<int, int>mp;
int main(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)b[i] = read();
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
for(int i = n; i > 0; --i){
int val = a[i], x = mp[val];
while(x){val = x; x = mp[x];}
mp[a[i]] = val + 1;
c[i] = val - a[i];
}
sort(c + 1, c + n + 1);
ull ans = 0;
for(int i = 1; i <= n; ++i)ans = ans + (ull)c[i] * (ull)b[n - i + 1];
printf("%llu\n",ans);
return 0;
}
B. 叁仟柒佰万
首先能证明 \(mex\) 是固定的
然后,就能得到一个转移 \(f_i\) 表示以 \(i\) 为结尾的方案数
发现需要找到一个 \(j\) 满足 \(mex(j, i) == mex\) \(mex(j + 1, i) != mex\)
那么 \(f_i\) 就是 \(f_{0} - f_{j - 1}\) 的和
发现把 \(f\) 改成前缀和,然后双指针扫一下即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 300005;
const int mod = 1e9 + 7;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int n, cnt[maxn];
int f[37000005], a[37000005];
void solve(){
int mex = 0; while(cnt[mex])++mex;
int up = min(maxn - 2, mex);
for(int i = 0; i <= up; ++i)cnt[i] = 0;
if(mex == 0){
printf("%d\n",qpow(2, n - 1));
return;
}
f[0] = 1;
int ls = 0, pr = 1;
for(; pr <= n; ++pr){
f[pr] = 1;
if(a[pr] < mex){
ls += (cnt[a[pr]] == 0); ++cnt[a[pr]];
if(ls == mex)break;
}
}
++f[pr++];
int pl = 0;
for(; pr <= n; ++pr){
if(a[pr] < mex){++cnt[a[pr]];}
while(pl < pr && (a[pl + 1] > mex || cnt[a[pl + 1]] > 1)){
++pl; if(a[pl] < mex)--cnt[a[pl]];
}
f[pr] = f[pl];
f[pr] = (f[pr] + f[pr - 1]) % mod;
}
printf("%d\n", (f[n] - f[n - 1] + mod) % mod);
}
int main(){
int t = read();
for(int ask = 1; ask <= t; ++ask){
n = read();
if(n == 37000000){
ll x = read(), y = read();
for(int i = 2; i <= n; ++i)++cnt[a[i] = (a[i - 1] * x + y + i) & 262143];
}
else {
for(int i = 0; i <= n; ++i)cnt[i] = 0;
for(int i = 1; i <= n; ++i)++cnt[a[i] = read()];
}
solve();
}
return 0;
}
C. 超级加倍
建立两棵笛卡尔树,一棵满足大根堆,一棵满足小根堆(但是建出来的不是二叉树)
类比数组建立笛卡尔树按照权值顺序加的那种
然后我们要做的就是找在两棵树上都是祖先关系的点对数量
发现按照其中一棵树的 \(dfs\) 序建立树状数组
在另一棵树上 \(dfs\) ,遇到一个点查询对应子树区间得到答案,把他加进 \(BIT\) 对其子树贡献
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 2000005;
int n, fa[maxn];
struct graph{
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
}T;
ll ans = 0;
struct dkr{
graph d;
int f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
int dfn[maxn], tim, dfr[maxn];
void dfs(int x){
dfn[x] = ++tim;
for(int i = d.head[x]; i; i = d.e[i].net){
int v = d.e[i].to;
dfs(v);
}
dfr[x] = tim;
}
}mi, mx;
struct BIT{
int t[maxn];
int lowbit(int x){return x & -x;}
void add(int x, int val){
while(x <= n){
t[x] += val;
x += lowbit(x);
}
}
int query(int x){
int ans = 0;
while(x){
ans += t[x];
x -= lowbit(x);
}
return ans;
}
}t;
void dfs(int x){
ans += t.query(mx.dfr[x]) - t.query(mx.dfn[x] - 1);
t.add(mx.dfn[x], 1);
for(int i = mi.d.head[x]; i; i = mi.d.e[i].net){
int v = mi.d.e[i].to;
dfs(v);
}
t.add(mx.dfn[x], -1);
}
int main(){
n = read();
for(int i = 1; i <= n; ++i)fa[i] = read();
for(int i = 2; i <= n; ++i)T.add(fa[i], i), T.add(i, fa[i]);
for(int x = 1; x <= n; ++x){
mx.f[x] = x;
for(int i = T.head[x]; i; i = T.e[i].net){
int v = T.e[i].to;
if(v < x){
v = mx.fa(v);
mx.d.add(x, v);
mx.f[v] = x;
}
}
}
for(int x = n; x >= 1; --x){
mi.f[x] = x;
for(int i = T.head[x]; i; i = T.e[i].net){
int v = T.e[i].to;
if(v > x){
v = mi.fa(v);
mi.d.add(x, v);
mi.f[v] = x;
}
}
}
mx.dfs(n);
dfs(1);
printf("%lld\n",ans);
return 0;
}
D. 欢乐豆
发现更改边权的点有限,考虑把他们拎出来
把边看成无向的,得到一些联通块,每次处理一个联通块
考虑 \(Dij\) 的过程,我们用线段树进行加速
处理出某个点作为源点时到达联通块内其他点的最短路
联通块内的点可以通过联通块内边更新,也可以通过不直接相连的点的点权更新
还有就是跳到联通块外最小点再跳回,这个出去统计答案时候取 \(min\) 即可
处理完后再处理到联通块外的
源点到外面的点是直接到和通过联通块间接到取 \(min\)
孤点贡献可以直接算
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 100005;
const ll inf = 0x3f3f3f3f;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
ll ans;
int n, m, a[maxn];
multiset<int>s;
vector<int>block[maxn];
vector<pii>g[maxn];
int f[maxn], size[maxn];
int fa(int x){return x == f[x] ? x : f[x] = fa(f[x]);}
void merge(int x, int y){
x = fa(x); y = fa(y);
if(x != y){
if(size[y] > size[x])swap(x, y);
size[x] += size[y];
f[y] = x;
}
}
struct tree{
struct node{
int mi, pos, tag;
bool del;
}t[maxn << 2 | 1];
void built(int x, int l, int r){
t[x] = {inf, l, inf, 0};
if(l == r)return;
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
}
void push_up(int x){
int ls = x << 1, rs = x << 1 | 1;
t[x].mi = min(t[ls].mi, t[rs].mi);
if(t[x].mi == t[ls].mi && !t[ls].del)t[x].pos = t[ls].pos;
else t[x].pos = t[rs].pos;
t[x].del = t[ls].del & t[rs].del;
}
void push_down(int x){
int ls = x << 1, rs = x << 1 | 1;
if(!t[ls].del){
t[ls].mi = min(t[ls].mi, t[x].tag);
t[ls].tag = min(t[ls].tag, t[x].tag);
}
if(!t[rs].del){
t[rs].mi = min(t[rs].mi, t[x].tag);
t[rs].tag = min(t[rs].tag, t[x].tag);
}
t[x].tag = inf;
}
void delet(int x, int l, int r, int pos){
if(l == r){t[x].mi = inf; t[x].del = 1; return;}
if(t[x].tag != inf)push_down(x);
int mid = (l + r) >> 1;
if(pos <= mid)delet(x << 1, l, mid, pos);
else delet(x << 1 | 1, mid + 1, r, pos);
push_up(x);
}
void modify(int x, int l, int r, int L, int R, int val){
if(t[x].del || L > R)return;
if(L <= l && r <= R){
t[x].mi = min(t[x].mi, val);
t[x].tag = min(t[x].tag, val);
return;
}
if(t[x].tag != inf)push_down(x);
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
}t;
int cnt, id[maxn];
int nid[maxn], dis[maxn];
void dij(int i, int s, int si){
t.built(1, 1, si);
t.modify(1, 1, si, nid[s], nid[s], 0);
dis[nid[s]] = 0;
while(t.t[1].mi != inf){
int x = t.t[1].pos, vx = t.t[1].mi;
dis[x] = vx; t.delet(1, 1, si, x);
x = block[i][x - 1];
int nl = 1;
for(pii v : g[x]){
t.modify(1, 1, si, nl, nid[v.first] - 1, vx + a[x]);
t.modify(1, 1, si, nid[v.first], nid[v.first], vx + v.second);
nl = nid[v.first] + 1;
}
t.modify(1, 1, si, nl, si, vx + a[x]);
}
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)f[i] = i, size[i] = 1, s.insert(a[i]);
for(int i = 1; i <= m; ++i){
int u = read(), v = read(),w = read();
g[u].push_back(pii(v, w)); merge(u, v);
}
for(int i = 1; i <= n; ++i){
sort(g[i].begin(), g[i].end());
if(fa(i) == i && size[i] > 1)id[i] = ++cnt;
}
for(int i = 1; i <= n; ++i){
if(size[fa(i)] > 1)block[id[fa(i)]].push_back(i);
else ans += 1ll * (n - 1) * a[i];
}
for(int i = 1; i <= cnt; ++i){
int tmp = 0;
for(int v : block[i]){
nid[v] = ++tmp; s.erase(s.find(a[v]));
}
int mi = s.size() ? (*s.begin()) : inf, si = block[i].size();
for(int v : block[i]){
dij(i, v, si);
int mii = inf;
for(int j = 1; j <= si; ++j)mii = min(mii, dis[j] + a[block[i][j - 1]]);
for(int j = 1; j <= si; ++j)ans += min(dis[j], mi + mii);
ans += 1ll * mii * (n - si);
}
for(int v : block[i])s.insert(a[v]);
}
printf("%lld\n",ans);
return 0;
}