NOIP模拟5
A. 战争
做法
-
把影响到的点和岛上另外一个点拿出来,然后如果一个岛屿只有一个关键点,就不用另外一个点,跑 , 或者折半
-
枚举,删边的钦定一段不选,加边的钦定两端必选, 合法性后构造方案
我使用的第二种,因为学不会
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 100005;
int p, n, c[maxn], k, k1, k2;
vector<pii>link, cut;
vector<int>vec, oth, ans;
set<int>part[maxn];
bool del[maxn], vis[maxn], app[maxn];
vector<int> node[maxn];
int edge[maxn], count[maxn], now;
int siz[maxn];
void Del(int x){
if(!del[x])--siz[c[x]];
del[x] = true;
}
void Add(int x){
if(del[x])++siz[c[x]];
del[x] = false;
}
int find(int p){
for(int x : part[p])if(!del[x])return x;
}
int main(){
freopen("war.in","r",stdin);
freopen("war.out","w",stdout);
p = read(), n = read();
for(int i = 1; i <= n; ++i)part[c[i] = read()].insert(i);
k = read();
for(int i = 1; i <= k; ++i){
int u = read(), v = read();
if(!vis[c[u]]){vis[c[u]] = true; vec.push_back(c[u]);}
if(!vis[c[v]]){vis[c[v]] = true; vec.push_back(c[v]);}
if(c[u] == c[v])link.push_back(pii(u, v)), ++k2;
else{
cut.push_back(pii(u, v)), ++k1;
if(part[c[u]].count(u))part[c[u]].erase(u);
if(part[c[v]].count(v))part[c[v]].erase(v);
}
}
for(int i = 1; i <= p; ++i)if(!vis[i] && part[i].size()){oth.push_back(*part[i].begin());}
for(int v : vec)while(part[v].size() > 1)part[v].erase(part[v].begin());
for(pii v : cut)part[c[v.first]].insert(v.first),part[c[v.second]].insert(v.second);
for(int i : vec)siz[i] = part[i].size();
for(ll i = 0; i < (1ll << k1); ++i){
for(int j = 0; j < k1; ++j)
if((1ll << j) & i)Del(cut[j].first);
else Del(cut[j].second);
for(ll j = 0; j < (1ll << k2); ++j){
for(int p = 0; p < k2; ++p)app[link[p].first] = app[link[p].second] = false;
for(int p : vec)node[p].clear(), edge[p] = 0;
for(int p = 0; p < k2; ++p)if((1ll << p) & j){
if(del[link[p].first] || del[link[p].second])goto X;
++edge[c[link[p].first]];
if(!app[link[p].first]){app[link[p].first] = true; node[c[link[p].first]].push_back(link[p].first);}
if(!app[link[p].second]){app[link[p].second] = true; node[c[link[p].second]].push_back(link[p].second);}
}
for(int p : vec)if(node[p].size() * (node[p].size() - 1) != edge[p] * 2)goto X;
now = 0;
for(int p : vec)
if(node[p].size())now += node[p].size();
else if(siz[p]) ++now;
if(now > ans.size()){
ans.clear();
for(int p : vec)
if(node[p].size())for(int x : node[p])ans.push_back(x);
else if(siz[p])ans.push_back(find(p));
}
X:;
}
for(ll j = 0; j < k1; ++j)
if((1ll << j) & i)Add(cut[j].first);
else Add(cut[j].second);
}
printf("%d\n",oth.size() + ans.size());
for(int x : oth)printf("%d ",x);
for(int x : ans)printf("%d ",x);
printf("\n");
return 0;
}
B. 肥胖
发现对于最优的策略,一定存在一种方案先吃完最小边一侧的点,再去另一边
于是断开最小边,可以划分成子问题
直接干肯定不行,于是考虑
建出最大生成树的克鲁斯卡尔重构树,然后进行
为什么不用跟 取 $min ? ,一种解释是 但是我觉得证明不是很严谨
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 400005;
int n, m;
int val[maxn], c[maxn], cnt;
struct DSU{
int f[maxn];
void init(){for(int i = 1; i <= n + n; ++i)f[i] = i;}
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
}S;
struct EDGE{
int u, v, w;
friend bool operator < (const EDGE &x, const EDGE &y){
return x.w > y.w;
}
}E[maxn];
int ans, lson[maxn], rson[maxn];
ll sval[maxn], f[maxn];
void dfs(int x, int fa){
if(x <= n){sval[x] = c[x]; f[x] = INT_MAX; return;}
dfs(lson[x], x);
dfs(rson[x], x);
sval[x] += sval[lson[x]];
sval[x] += sval[rson[x]];
// f[x] = max(min({f[lson[x]], val[x] - sval[lson[x]], f[rson[x]] - sval[lson[x]]}), min({f[rson[x]], val[x] - sval[rson[x]], f[lson[x]] - sval[rson[x]]}));
f[x] = max(min(val[x] - sval[lson[x]], f[rson[x]] - sval[lson[x]]), min(val[x] - sval[rson[x]], f[lson[x]] - sval[rson[x]]));
}
int main(){
//freopen("fat.in","r",stdin);
//freopen("fat.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= n; ++i)c[i] = read();
for(int i = 1; i <= m; ++i){int u = read(), v = read(), w = read(); E[i] = {u, v, w};}
sort(E + 1, E + m + 1); S.init(); cnt = n;
for(int i = 1; i <= m; ++i)if(S.fa(E[i].u) != S.fa(E[i].v)){
val[++cnt] = E[i].w;
lson[cnt] = S.fa(E[i].u);
rson[cnt] = S.fa(E[i].v);
S.f[S.fa(E[i].u)] = cnt;
S.f[S.fa(E[i].v)] = cnt;
}
dfs(cnt, 0);
printf("%lld\n",f[cnt] > 0 ? f[cnt] : -1);
return 0;
}
C. 分摊
暴跳父亲,发现式子为
一个显然的东西是
于是如果取 取到 , 那么他至少扩大 倍,他最大为
所以这样的操作不会超过 次
于是倍增, 表示从从 向上跳到 级祖先,最小为多少不会有 的情况
表示贡献,对于出现了的情况,可以预处理前缀和+二分快速解决
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 300005;
int n, f[maxn][20];
ll v[maxn], sv[maxn][20], mi[maxn][20];
vector<int>son[maxn];
vector<ll>sum[maxn], val[maxn];
ll sol(int x){
ll ans = v[x];
while(x){
for(int i = 19; i >= 0; --i)if(x && ans >= mi[x][i]){
ans += sv[x][i]; x = f[x][i];
}
if(x){
int pos = upper_bound(val[f[x][0]].begin(), val[f[x][0]].end(), ans) - val[f[x][0]].begin() - 1;
ll las = ans;
if(pos < 0){ans += las * (son[f[x][0]].size() - 1);}
else {ans += las * (son[f[x][0]].size() - pos - 2); ans += sum[f[x][0]][pos];}
x = f[x][0];
}
}
return ans;
}
int main(){
freopen("share.in","r",stdin);
freopen("share.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)son[f[i][0] = read()].push_back(i), v[i] = read();
for(int i = n; i >= 1; --i)v[f[i][0]] += v[i];
for(int i = 1; i <= n; ++i)val[f[i][0]].push_back(v[i]), sum[f[i][0]].push_back(0);
for(int i = 0; i <= n; ++i){
sort(val[i].begin(), val[i].end());
int s = val[i].size(); ll pre = 0;
for(int j = 0; j < s; ++j)pre += val[i][j], sum[i][j] = pre;
}
for(int i = 1; i <= n; ++i){
int sf = son[f[i][0]].size();
if(sf != 1)mi[i][0] = val[f[i][0]][sf - 1 - (val[f[i][0]][sf - 1] == v[i])];
sv[i][0] = sum[f[i][0]][sf - 1] - v[i];
}
for(int j = 1; j <= 19; ++j)
for(int i = 1; i <= n; ++i){
f[i][j] = f[f[i][j - 1]][j - 1];
sv[i][j] = sv[i][j - 1] + sv[f[i][j - 1]][j - 1];
mi[i][j] = max(mi[i][j - 1], mi[f[i][j - 1]][j - 1]);
}
for(int i = 1; i <= n; ++i)printf("%lld\n",sol(i));
return 0;
}
D. 修路
一个连通块无论如何连边,都能等价于顺次链接这些点构成的多边形
你发现等价完了之后新加一条边与某个连通块的交点就是 级别,每次合并必然减少连通块,所以这里是 级别的
考虑如何维护这个东西,记录 , 维护在多边形上他的前驱后继
然后每次查询时候在线段树上找到所有有交的边即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
int n, m, top, sta[maxn];
struct DSU{
int f[maxn];
void init(){for(int i = 1; i <= n; ++i)f[i] = i;}
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){x = fa(x); y = fa(y); f[y] = x;}
}S;
struct seg1{
int t[maxn << 2 | 1], pos[maxn];
#define push_up(x) (t[x] = min(t[x << 1], t[x << 1 | 1]))
void built(int x, int l, int r){
if(l == r){t[x] = l; pos[l] = x; return;}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
void modify(int p, int val){int x = pos[p]; for(t[x] = val, x >>= 1; x; x >>= 1)push_up(x);}
void query(int x, int l, int r, int L, int R, int lim){
if(t[x] > lim)return;
if(l == r){sta[++top] = t[x]; sta[++top] = l; return;}
int mid = (l + r) >> 1;
if(L <= mid)query(x << 1, l, mid, L, R, lim);
if(R > mid)query(x << 1 | 1, mid + 1, r, L, R, lim);
}
#undef push_up
}pre1;
struct seg2{
int t[maxn << 2 | 1], pos[maxn];
#define push_up(x) (t[x] = max(t[x << 1], t[x << 1 | 1]))
void built(int x, int l, int r){
if(l == r){t[x] = l; pos[l] = x; return;}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
void modify(int p, int val){int x = pos[p]; for(t[x] = val, x >>= 1; x; x >>= 1)push_up(x);}
void query(int x, int l, int r, int L, int R, int lim){
if(t[x] < lim)return;
if(l == r){sta[++top] = t[x]; sta[++top] = l; return;}
int mid = (l + r) >> 1;
if(L <= mid)query(x << 1, l, mid, L, R, lim);
if(R > mid)query(x << 1 | 1, mid + 1, r, L, R, lim);
}
#undef push_up
}suf1;
int main(){
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
n = read(), m = read();
pre1.built(1, 1, n);
suf1.built(1, 1, n);
S.init();
for(int i = 1; i <= m; ++i){
int op = read(), u = read(), v = read();
if(op & 1){
top = 0;
if(u > v)swap(u, v);
if(S.fa(u) == S.fa(v))continue;
pre1.query(1, 1, n, u, v, u);
suf1.query(1, 1, n, u, v, v);
sort(sta + 1, sta + top + 1);
top = unique(sta + 1, sta + top + 1) - sta - 1;
for(int j = 1; j < top; ++j)S.merge(sta[j], sta[top]);
for(int j = 1; j < top; ++j)pre1.modify(sta[j + 1], sta[j]);
for(int j = 2; j <= top; ++j)suf1.modify(sta[j - 1], sta[j]);
pre1.modify(sta[top], sta[1]);
suf1.modify(sta[1], sta[top]);
}else printf("%d",S.fa(u) == S.fa(v));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】